1283 lines
57 KiB
C#
1283 lines
57 KiB
C#
|
//---------------------------------------------------------------------
|
||
|
// <copyright file="EntityConnection.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//
|
||
|
// @owner [....]
|
||
|
// @backupOwner [....]
|
||
|
//---------------------------------------------------------------------
|
||
|
|
||
|
using System.Collections.Generic;
|
||
|
using System.Configuration;
|
||
|
using System.Data.Common;
|
||
|
using System.Data.Common.CommandTrees;
|
||
|
using System.Data.Common.EntitySql;
|
||
|
using System.Data.Entity;
|
||
|
using System.Data.Mapping;
|
||
|
using System.Data.Metadata.Edm;
|
||
|
using System.Diagnostics;
|
||
|
using System.Linq;
|
||
|
using System.Runtime.Versioning;
|
||
|
using System.Text;
|
||
|
using System.Transactions;
|
||
|
|
||
|
namespace System.Data.EntityClient
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Class representing a connection for the conceptual layer. An entity connection may only
|
||
|
/// be initialized once (by opening the connection). It is subsequently not possible to change
|
||
|
/// the connection string, attach a new store connection, or change the store connection string.
|
||
|
/// </summary>
|
||
|
public sealed class EntityConnection : DbConnection
|
||
|
{
|
||
|
#region Constants
|
||
|
|
||
|
private const string s_metadataPathSeparator = "|";
|
||
|
private const string s_semicolonSeparator = ";";
|
||
|
private const string s_entityClientProviderName = "System.Data.EntityClient";
|
||
|
private const string s_providerInvariantName = "provider";
|
||
|
private const string s_providerConnectionString = "provider connection string";
|
||
|
private const string s_readerPrefix = "reader://";
|
||
|
internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed);
|
||
|
internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open);
|
||
|
#endregion
|
||
|
|
||
|
private readonly object _connectionStringLock = new object();
|
||
|
private static readonly DbConnectionOptions s_emptyConnectionOptions = new DbConnectionOptions(String.Empty, null);
|
||
|
|
||
|
// The connection options object having the connection settings needed by this connection
|
||
|
private DbConnectionOptions _userConnectionOptions;
|
||
|
private DbConnectionOptions _effectiveConnectionOptions;
|
||
|
|
||
|
// The internal connection state of the entity client, which is distinct from that of the
|
||
|
// store connection it aggregates.
|
||
|
private ConnectionState _entityClientConnectionState = ConnectionState.Closed;
|
||
|
|
||
|
private DbProviderFactory _providerFactory;
|
||
|
private DbConnection _storeConnection;
|
||
|
private readonly bool _userOwnsStoreConnection;
|
||
|
private MetadataWorkspace _metadataWorkspace;
|
||
|
// DbTransaction started using BeginDbTransaction() method
|
||
|
private EntityTransaction _currentTransaction;
|
||
|
// Transaction the user enlisted in using EnlistTransaction() method
|
||
|
private Transaction _enlistedTransaction;
|
||
|
private bool _initialized;
|
||
|
// will only have a value while waiting for the ssdl to be loaded. we should
|
||
|
// never have a value for this when _initialized == true
|
||
|
private MetadataArtifactLoader _artifactLoader;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs the EntityConnection object with a connection not yet associated to a particular store
|
||
|
/// </summary>
|
||
|
[ResourceExposure(ResourceScope.None)] //We are not exposing any resource
|
||
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For EntityConnection constructor. But since the connection string we pass in is an Empty String,
|
||
|
//we consume the resource and do not expose it any further.
|
||
|
public EntityConnection()
|
||
|
: this(String.Empty)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs the EntityConnection object with a connection string
|
||
|
/// </summary>
|
||
|
/// <param name="connectionString">The connection string, may contain a list of settings for the connection or
|
||
|
/// just the name of the connection to use</param>
|
||
|
[ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
|
||
|
[ResourceConsumption(ResourceScope.Machine)] //For ChangeConnectionString method call. But the paths are not created in this method.
|
||
|
public EntityConnection(string connectionString)
|
||
|
{
|
||
|
GC.SuppressFinalize(this);
|
||
|
this.ChangeConnectionString(connectionString);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs the EntityConnection from Metadata loaded in memory
|
||
|
/// </summary>
|
||
|
/// <param name="workspace">Workspace containing metadata information.</param>
|
||
|
public EntityConnection(MetadataWorkspace workspace, DbConnection connection)
|
||
|
{
|
||
|
GC.SuppressFinalize(this);
|
||
|
|
||
|
EntityUtil.CheckArgumentNull(workspace, "workspace");
|
||
|
EntityUtil.CheckArgumentNull(connection, "connection");
|
||
|
|
||
|
|
||
|
if (!workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace))
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("EdmItemCollection"));
|
||
|
}
|
||
|
if(!workspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("StoreItemCollection"));
|
||
|
}
|
||
|
if(!workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSSpace))
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("StorageMappingItemCollection"));
|
||
|
}
|
||
|
|
||
|
if (connection.State != ConnectionState.Closed)
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ConnectionMustBeClosed);
|
||
|
}
|
||
|
|
||
|
// Verify that a factory can be retrieved
|
||
|
if (DbProviderFactories.GetFactory(connection) == null)
|
||
|
{
|
||
|
throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_DbConnectionHasNoProvider(connection));
|
||
|
}
|
||
|
|
||
|
StoreItemCollection collection = (StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace);
|
||
|
|
||
|
_providerFactory = collection.StoreProviderFactory;
|
||
|
_storeConnection = connection;
|
||
|
_userOwnsStoreConnection = true;
|
||
|
_metadataWorkspace = workspace;
|
||
|
_initialized = true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get or set the entity connection string associated with this connection object
|
||
|
/// </summary>
|
||
|
public override string ConnectionString
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
//EntityConnection created using MetadataWorkspace
|
||
|
// _userConnectionOptions is not null when empty Constructor is used
|
||
|
// Therefore it is sufficient to identify whether EC(MW, DbConnection) is used
|
||
|
if (this._userConnectionOptions == null)
|
||
|
{
|
||
|
Debug.Assert(_storeConnection != null);
|
||
|
|
||
|
string invariantName;
|
||
|
if (!EntityUtil.TryGetProviderInvariantName(DbProviderFactories.GetFactory(_storeConnection), out invariantName))
|
||
|
{
|
||
|
Debug.Fail("Provider Invariant Name not found");
|
||
|
invariantName = "";
|
||
|
}
|
||
|
|
||
|
return string.Format(Globalization.CultureInfo.InvariantCulture,
|
||
|
"{0}={3}{4};{1}={5};{2}=\"{6}\";",
|
||
|
EntityConnectionStringBuilder.MetadataParameterName,
|
||
|
s_providerInvariantName,
|
||
|
s_providerConnectionString,
|
||
|
s_readerPrefix,
|
||
|
_metadataWorkspace.MetadataWorkspaceId,
|
||
|
invariantName,
|
||
|
FormatProviderString(_storeConnection.ConnectionString));
|
||
|
}
|
||
|
|
||
|
string userConnectionString = this._userConnectionOptions.UsersConnectionString;
|
||
|
|
||
|
// In here, we ask the store connection for the connection string only if the user didn't specify a name
|
||
|
// connection (meaning effective connection options == user connection options). If the user specified a
|
||
|
// named connection, then we return just that. Otherwise, if the connection string is different from what
|
||
|
// we have in the connection options, which is possible if the store connection changed the connection
|
||
|
// string to hide the password, then we use the builder to reconstruct the string. The parameters will be
|
||
|
// shuffled, which is unavoidable but it's ok because the connection string cannot be the same as what the
|
||
|
// user originally passed in anyway. However, if the store connection string is still the same, then we
|
||
|
// simply return what the user originally passed in.
|
||
|
if (object.ReferenceEquals(_userConnectionOptions, _effectiveConnectionOptions) && this._storeConnection != null)
|
||
|
{
|
||
|
string storeConnectionString = null;
|
||
|
try
|
||
|
{
|
||
|
storeConnectionString = this._storeConnection.ConnectionString;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"ConnectionString", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
|
||
|
// SQLBU 514721, 515024 - Defer connection string parsing to ConnectionStringBuilder
|
||
|
// if the 'userStoreConnectionString' and 'storeConnectionString' are unequal, except
|
||
|
// when they are both null or empty (we treat null and empty as equivalent here).
|
||
|
//
|
||
|
string userStoreConnectionString = this._userConnectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
|
||
|
if ((storeConnectionString != userStoreConnectionString)
|
||
|
&& !(string.IsNullOrEmpty(storeConnectionString) && string.IsNullOrEmpty(userStoreConnectionString)))
|
||
|
{
|
||
|
// Feeds the connection string into the connection string builder, then plug in the provider connection string into
|
||
|
// the builder, and then extract the string from the builder
|
||
|
EntityConnectionStringBuilder connectionStringBuilder = new EntityConnectionStringBuilder(userConnectionString);
|
||
|
connectionStringBuilder.ProviderConnectionString = storeConnectionString;
|
||
|
return connectionStringBuilder.ConnectionString;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return userConnectionString;
|
||
|
}
|
||
|
[ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
|
||
|
[ResourceConsumption(ResourceScope.Machine)] //For ChangeConnectionString method call. But the paths are not created in this method.
|
||
|
set
|
||
|
{
|
||
|
ValidateChangesPermitted();
|
||
|
this.ChangeConnectionString(value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Formats provider string to replace " with \" so it can be appended within quotation marks "..."
|
||
|
/// </summary>
|
||
|
private static string FormatProviderString(string providerString)
|
||
|
{
|
||
|
return providerString.Trim().Replace("\"", "\\\"");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the time to wait when attempting to establish a connection before ending the try and generating an error
|
||
|
/// </summary>
|
||
|
public override int ConnectionTimeout
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this._storeConnection == null)
|
||
|
return 0;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
return this._storeConnection.ConnectionTimeout;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"ConnectionTimeout", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the name of the current database or the database that will be used after a connection is opened
|
||
|
/// </summary>
|
||
|
public override string Database
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return String.Empty;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the ConnectionState property of the EntityConnection
|
||
|
/// </summary>
|
||
|
public override ConnectionState State
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (this._entityClientConnectionState == ConnectionState.Open)
|
||
|
{
|
||
|
Debug.Assert(this.StoreConnection != null);
|
||
|
if (this.StoreConnection.State != ConnectionState.Open)
|
||
|
{
|
||
|
return ConnectionState.Broken;
|
||
|
}
|
||
|
}
|
||
|
return this._entityClientConnectionState;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"State", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the name or network address of the data source to connect to
|
||
|
/// </summary>
|
||
|
public override string DataSource
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this._storeConnection == null)
|
||
|
return String.Empty;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
return this._storeConnection.DataSource;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"DataSource", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a string that contains the version of the data store to which the client is connected
|
||
|
/// </summary>
|
||
|
public override string ServerVersion
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this._storeConnection == null)
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
|
||
|
|
||
|
if (this.State != ConnectionState.Open)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
return this._storeConnection.ServerVersion;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"ServerVersion", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the provider factory associated with EntityConnection
|
||
|
/// </summary>
|
||
|
override protected DbProviderFactory DbProviderFactory
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return EntityProviderFactory.Instance;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the DbProviderFactory for the underlying provider
|
||
|
/// </summary>
|
||
|
internal DbProviderFactory StoreProviderFactory
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this._providerFactory;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the DbConnection for the underlying provider connection
|
||
|
/// </summary>
|
||
|
public DbConnection StoreConnection
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this._storeConnection;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the metadata workspace used by this connection
|
||
|
/// </summary>
|
||
|
[CLSCompliant(false)]
|
||
|
public MetadataWorkspace GetMetadataWorkspace()
|
||
|
{
|
||
|
return GetMetadataWorkspace(true /* initializeAllCollections */);
|
||
|
}
|
||
|
|
||
|
private bool ShouldRecalculateMetadataArtifactLoader(List<MetadataArtifactLoader> loaders)
|
||
|
{
|
||
|
if (loaders.Any(loader => loader.GetType() == typeof(MetadataArtifactLoaderCompositeFile)))
|
||
|
{
|
||
|
// the loaders had folders in it
|
||
|
return true;
|
||
|
}
|
||
|
// in the case that loaders only contains resources or file name, we trust the cache
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
[ResourceExposure(ResourceScope.None)] //The resource( path name) is not exposed to the callers of this method
|
||
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //Fir SplitPaths call and we pick the file names from class variable.
|
||
|
internal MetadataWorkspace GetMetadataWorkspace(bool initializeAllCollections)
|
||
|
{
|
||
|
Debug.Assert(_metadataWorkspace != null || _effectiveConnectionOptions != null, "The effective connection options is null, which should never be");
|
||
|
if (_metadataWorkspace == null ||
|
||
|
(initializeAllCollections && !_metadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace)))
|
||
|
{
|
||
|
// This lock is to ensure that the connection string and the metadata workspace are in a consistent state, that is, you
|
||
|
// don't get a metadata workspace not matching what's described by the connection string
|
||
|
lock (_connectionStringLock)
|
||
|
{
|
||
|
EdmItemCollection edmItemCollection = null;
|
||
|
if (_metadataWorkspace == null)
|
||
|
{
|
||
|
MetadataWorkspace workspace = new MetadataWorkspace();
|
||
|
List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
|
||
|
string paths = _effectiveConnectionOptions[EntityConnectionStringBuilder.MetadataParameterName];
|
||
|
|
||
|
if (!string.IsNullOrEmpty(paths))
|
||
|
{
|
||
|
loaders = MetadataCache.GetOrCreateMetdataArtifactLoader(paths);
|
||
|
|
||
|
if(!ShouldRecalculateMetadataArtifactLoader(loaders))
|
||
|
{
|
||
|
_artifactLoader = MetadataArtifactLoader.Create(loaders);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// the loaders contains folders that might get updated during runtime, so we have to recalculate the loaders again
|
||
|
_artifactLoader = MetadataArtifactLoader.Create(MetadataCache.SplitPaths(paths));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_artifactLoader = MetadataArtifactLoader.Create(loaders);
|
||
|
}
|
||
|
|
||
|
edmItemCollection = LoadEdmItemCollection(workspace, _artifactLoader);
|
||
|
_metadataWorkspace = workspace;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
edmItemCollection = (EdmItemCollection)_metadataWorkspace.GetItemCollection(DataSpace.CSpace);
|
||
|
}
|
||
|
|
||
|
if (initializeAllCollections && !_metadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
|
||
|
{
|
||
|
LoadStoreItemCollections(_metadataWorkspace, _storeConnection, _providerFactory, _effectiveConnectionOptions, edmItemCollection, _artifactLoader);
|
||
|
_artifactLoader = null;
|
||
|
_initialized = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return _metadataWorkspace;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the current transaction that this connection is enlisted in
|
||
|
/// </summary>
|
||
|
internal EntityTransaction CurrentTransaction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
// Null out the current transaction if the state is closed or zombied
|
||
|
if ((null != _currentTransaction) && ((null == _currentTransaction.StoreTransaction.Connection) || (this.State == ConnectionState.Closed)))
|
||
|
{
|
||
|
ClearCurrentTransaction();
|
||
|
}
|
||
|
return _currentTransaction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Whether the user has enlisted in transaction using EnlistTransaction method
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// To avoid threading issues the <see cref="_enlistedTransaction"/> field is not reset when the transaction is completed.
|
||
|
/// Therefore it is possible that the transaction has completed but the field is not null. As a result we need to check
|
||
|
/// the actual transaction status. However it can happen that the transaction has already been disposed and trying to get
|
||
|
/// transaction status will cause ObjectDisposedException. This would also mean that the transaction has completed and can be reset.
|
||
|
/// </remarks>
|
||
|
internal bool EnlistedInUserTransaction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
return _enlistedTransaction != null && _enlistedTransaction.TransactionInformation.Status == TransactionStatus.Active;
|
||
|
}
|
||
|
catch (ObjectDisposedException)
|
||
|
{
|
||
|
_enlistedTransaction = null;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Establish a connection to the data store by calling the Open method on the underlying data provider
|
||
|
/// </summary>
|
||
|
public override void Open()
|
||
|
{
|
||
|
if (this._storeConnection == null)
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
|
||
|
|
||
|
if (this.State != ConnectionState.Closed)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotReopenConnection);
|
||
|
}
|
||
|
|
||
|
bool closeStoreConnectionOnFailure = false;
|
||
|
OpenStoreConnectionIf(this._storeConnection.State != ConnectionState.Open,
|
||
|
this._storeConnection,
|
||
|
null,
|
||
|
EntityRes.EntityClient_ProviderSpecificError,
|
||
|
@"Open",
|
||
|
ref closeStoreConnectionOnFailure);
|
||
|
|
||
|
// the following guards against the case when the user closes the underlying store connection
|
||
|
// in the state change event handler, as a consequence of which we are in the 'Broken' state
|
||
|
if (this._storeConnection == null || this._storeConnection.State != ConnectionState.Open)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
|
||
|
}
|
||
|
|
||
|
InitializeMetadata(this._storeConnection, this._storeConnection, closeStoreConnectionOnFailure);
|
||
|
SetEntityClientConnectionStateToOpen();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method that conditionally opens a specified store connection
|
||
|
/// </summary>
|
||
|
/// <param name="openCondition">The condition to evaluate</param>
|
||
|
/// <param name="storeConnectionToOpen">The store connection to open</param>
|
||
|
/// <param name="originalConnection">The original store connection associated with the entity client</param>
|
||
|
/// <param name="closeStoreConnectionOnFailure">A flag that is set on if the connection is opened
|
||
|
/// successfully</param>
|
||
|
private void OpenStoreConnectionIf(bool openCondition,
|
||
|
DbConnection storeConnectionToOpen,
|
||
|
DbConnection originalConnection,
|
||
|
string exceptionCode,
|
||
|
string attemptedOperation,
|
||
|
ref bool closeStoreConnectionOnFailure)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (openCondition)
|
||
|
{
|
||
|
storeConnectionToOpen.Open();
|
||
|
closeStoreConnectionOnFailure = true;
|
||
|
}
|
||
|
|
||
|
ResetStoreConnection(storeConnectionToOpen, originalConnection, false);
|
||
|
|
||
|
// With every successful open of the store connection, always null out the current db transaction and enlistedTransaction
|
||
|
ClearTransactions();
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
string exceptionMessage = string.IsNullOrEmpty(attemptedOperation) ?
|
||
|
EntityRes.GetString(exceptionCode) :
|
||
|
EntityRes.GetString(exceptionCode, attemptedOperation);
|
||
|
|
||
|
throw EntityUtil.ProviderExceptionWithMessage(exceptionMessage, e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method to initialize the metadata workspace and reset the store connection
|
||
|
/// associated with the entity client
|
||
|
/// </summary>
|
||
|
/// <param name="newConnection">The new connection to associate with the entity client</param>
|
||
|
/// <param name="originalConnection">The original connection associated with the entity client</param>
|
||
|
/// <param name="closeOriginalConnectionOnFailure">A flag to indicate whether the original
|
||
|
/// store connection needs to be closed on failure</param>
|
||
|
private void InitializeMetadata(DbConnection newConnection,
|
||
|
DbConnection originalConnection,
|
||
|
bool closeOriginalConnectionOnFailure)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
// Ensure metadata is loaded and the workspace is appropriately initialized.
|
||
|
GetMetadataWorkspace();
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
// Undo the open if something failed
|
||
|
if (EntityUtil.IsCatchableExceptionType(ex))
|
||
|
{
|
||
|
ResetStoreConnection(newConnection, originalConnection, closeOriginalConnectionOnFailure);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Set the entity client connection state to Open, and raise an appropriate event
|
||
|
/// </summary>
|
||
|
private void SetEntityClientConnectionStateToOpen()
|
||
|
{
|
||
|
this._entityClientConnectionState = ConnectionState.Open;
|
||
|
OnStateChange(StateChangeOpen);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This method sets the store connection and hooks up the event
|
||
|
/// </summary>
|
||
|
/// <param name="newConnection">The DbConnection to set</param>
|
||
|
/// <param name="originalConnection">The original DbConnection to be closed - this argument could be null</param>
|
||
|
/// <param name="closeOriginalConnection">Indicates whether the original store connection should be closed</param>
|
||
|
private void ResetStoreConnection(DbConnection newConnection, DbConnection originalConnection, bool closeOriginalConnection)
|
||
|
{
|
||
|
this._storeConnection = newConnection;
|
||
|
|
||
|
if (closeOriginalConnection && (originalConnection != null))
|
||
|
{
|
||
|
originalConnection.Close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Create a new command object that uses this connection object.
|
||
|
/// </summary>
|
||
|
public new EntityCommand CreateCommand()
|
||
|
{
|
||
|
return new EntityCommand(null, this);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Create a new command object that uses this connection object
|
||
|
/// </summary>
|
||
|
protected override DbCommand CreateDbCommand()
|
||
|
{
|
||
|
return this.CreateCommand();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Close the connection to the data store
|
||
|
/// </summary>
|
||
|
public override void Close()
|
||
|
{
|
||
|
// It's a no-op if there isn't an underlying connection
|
||
|
if (this._storeConnection == null)
|
||
|
return;
|
||
|
|
||
|
this.CloseHelper();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Changes the current database for this connection
|
||
|
/// </summary>
|
||
|
/// <param name="databaseName">The name of the database to change to</param>
|
||
|
public override void ChangeDatabase(string databaseName)
|
||
|
{
|
||
|
throw EntityUtil.NotSupported();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Begins a database transaction
|
||
|
/// </summary>
|
||
|
/// <returns>An object representing the new transaction</returns>
|
||
|
public new EntityTransaction BeginTransaction()
|
||
|
{
|
||
|
return base.BeginTransaction() as EntityTransaction;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Begins a database transaction
|
||
|
/// </summary>
|
||
|
/// <param name="isolationLevel">The isolation level of the transaction</param>
|
||
|
/// <returns>An object representing the new transaction</returns>
|
||
|
public new EntityTransaction BeginTransaction(IsolationLevel isolationLevel)
|
||
|
{
|
||
|
return base.BeginTransaction(isolationLevel) as EntityTransaction;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Begins a database transaction
|
||
|
/// </summary>
|
||
|
/// <param name="isolationLevel">The isolation level of the transaction</param>
|
||
|
/// <returns>An object representing the new transaction</returns>
|
||
|
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
|
||
|
{
|
||
|
if (CurrentTransaction != null)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_TransactionAlreadyStarted);
|
||
|
}
|
||
|
|
||
|
if (this._storeConnection == null)
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
|
||
|
|
||
|
if (this.State != ConnectionState.Open)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
|
||
|
}
|
||
|
|
||
|
DbTransaction storeTransaction = null;
|
||
|
try
|
||
|
{
|
||
|
storeTransaction = this._storeConnection.BeginTransaction(isolationLevel);
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.ProviderExceptionWithMessage(
|
||
|
System.Data.Entity.Strings.EntityClient_ErrorInBeginningTransaction,
|
||
|
e
|
||
|
);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
|
||
|
// The provider is problematic if it succeeded in beginning a transaction but returned a null
|
||
|
// for the transaction object
|
||
|
if (storeTransaction == null)
|
||
|
{
|
||
|
throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_ReturnedNullOnProviderMethod("BeginTransaction", _storeConnection.GetType().Name));
|
||
|
}
|
||
|
|
||
|
_currentTransaction = new EntityTransaction(this, storeTransaction);
|
||
|
return _currentTransaction;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Enlist in the given transaction
|
||
|
/// </summary>
|
||
|
/// <param name="transaction">The transaction object to enlist into</param>
|
||
|
public override void EnlistTransaction(Transaction transaction)
|
||
|
{
|
||
|
if (_storeConnection == null)
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
|
||
|
|
||
|
if (this.State != ConnectionState.Open)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
_storeConnection.EnlistTransaction(transaction);
|
||
|
|
||
|
// null means "Unenlist transaction". It is fine if no transaction is in progress (no op). Otherwise
|
||
|
// _storeConnection.EnlistTransaction should throw and we would not get here.
|
||
|
Debug.Assert(transaction != null || !EnlistedInUserTransaction,
|
||
|
"DbConnection should not allow unenlist from a transaction that has not completed.");
|
||
|
|
||
|
// It is OK to enlist in null transaction or multiple times in the same transaction.
|
||
|
// In the latter case we don't need to be called multiple times when the transaction completes
|
||
|
// so subscribe only when enlisting for the first time. Note that _storeConnection.EnlistTransaction
|
||
|
// will throw in invalid cases (like enlisting the connection in a transaction when another
|
||
|
// transaction has not completed) so when we get here we are sure that either no transactions are
|
||
|
// active or the transaction the caller tries enlisting to
|
||
|
// is the active transaction.
|
||
|
if (transaction != null && !EnlistedInUserTransaction)
|
||
|
{
|
||
|
transaction.TransactionCompleted += EnlistedTransactionCompleted;
|
||
|
}
|
||
|
|
||
|
_enlistedTransaction = transaction;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"EnlistTransaction", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Cleans up this connection object
|
||
|
/// </summary>
|
||
|
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources</param>
|
||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_currentTransaction")]
|
||
|
[ResourceExposure(ResourceScope.None)] //We are not exposing any resource
|
||
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For ChangeConnectionString method call. But since the connection string we pass in is an Empty String,
|
||
|
//we consume the resource and do not expose it any further.
|
||
|
protected override void Dispose(bool disposing)
|
||
|
{
|
||
|
// It is possible for the EntityConnection to be finalized even if the object was not actually
|
||
|
// created due to a "won't fix" bug in the x86 JITer--see Dev10 bug 892884.
|
||
|
// Even without this bug, a stack overflow trying to allocate space to run the constructor can
|
||
|
// result in effectively the same situation. This means we can end up finalizing objects that
|
||
|
// have not even been fully initialized. In order for this to work we have to be very careful
|
||
|
// what we do in Dispose and we need to stick rigidly to the "only dispose unmanaged resources
|
||
|
// if disposing is false" rule. We don't actually have any unmanaged resources--these are
|
||
|
// handled by the base class or other managed classes that we have references to. These classes
|
||
|
// will dispose of their unmanaged resources on finalize, so we shouldn't try to do it here.
|
||
|
if (disposing)
|
||
|
{
|
||
|
ClearTransactions();
|
||
|
bool raiseStateChangeEvent = EntityCloseHelper(false, this.State);
|
||
|
|
||
|
if (this._storeConnection != null)
|
||
|
{
|
||
|
StoreCloseHelper(); // closes store connection
|
||
|
if (this._storeConnection != null)
|
||
|
{
|
||
|
if (!this._userOwnsStoreConnection) // only dispose it if we didn't get it from the user...
|
||
|
{
|
||
|
this._storeConnection.Dispose();
|
||
|
}
|
||
|
this._storeConnection = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Change the connection string to just an empty string, ChangeConnectionString should always succeed here,
|
||
|
// it's unnecessary to pass in the connection string parameter name in the second argument, which we don't
|
||
|
// have anyway
|
||
|
this.ChangeConnectionString(String.Empty);
|
||
|
|
||
|
if (raiseStateChangeEvent) // we need to raise the event explicitly
|
||
|
{
|
||
|
OnStateChange(StateChangeClosed);
|
||
|
}
|
||
|
}
|
||
|
base.Dispose(disposing);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reinitialize this connection object to use the new connection string
|
||
|
/// </summary>
|
||
|
/// <param name="newConnectionString">The new connection string</param>
|
||
|
[ResourceExposure(ResourceScope.Machine)] //Exposes the file names which are a Machine resource as part of the connection string
|
||
|
private void ChangeConnectionString(string newConnectionString)
|
||
|
{
|
||
|
DbConnectionOptions userConnectionOptions = s_emptyConnectionOptions;
|
||
|
if (!String.IsNullOrEmpty(newConnectionString))
|
||
|
{
|
||
|
userConnectionOptions = new DbConnectionOptions(newConnectionString, EntityConnectionStringBuilder.Synonyms);
|
||
|
}
|
||
|
|
||
|
DbProviderFactory factory = null;
|
||
|
DbConnection storeConnection = null;
|
||
|
DbConnectionOptions effectiveConnectionOptions = userConnectionOptions;
|
||
|
|
||
|
if (!userConnectionOptions.IsEmpty)
|
||
|
{
|
||
|
// Check if we have the named connection, if yes, then use the connection string from the configuration manager settings
|
||
|
string namedConnection = userConnectionOptions[EntityConnectionStringBuilder.NameParameterName];
|
||
|
if (!string.IsNullOrEmpty(namedConnection))
|
||
|
{
|
||
|
// There cannot be other parameters when the named connection is specified
|
||
|
if (1 < userConnectionOptions.Parsetable.Count)
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ExtraParametersWithNamedConnection);
|
||
|
}
|
||
|
|
||
|
// Find the named connection from the configuration, then extract the settings
|
||
|
ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings[namedConnection];
|
||
|
if (setting == null || setting.ProviderName != EntityConnection.s_entityClientProviderName)
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidNamedConnection);
|
||
|
}
|
||
|
|
||
|
effectiveConnectionOptions = new DbConnectionOptions(setting.ConnectionString, EntityConnectionStringBuilder.Synonyms);
|
||
|
|
||
|
// Check for a nested Name keyword
|
||
|
string nestedNamedConnection = effectiveConnectionOptions[EntityConnectionStringBuilder.NameParameterName];
|
||
|
if (!string.IsNullOrEmpty(nestedNamedConnection))
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_NestedNamedConnection(namedConnection));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Validate the connection string has the required Keywords( Provider and Metadata)
|
||
|
//We trim the values for both the Keywords, so a string value with only spaces will throw an exception
|
||
|
//reporting back to the user that the Keyword was missing.
|
||
|
ValidateValueForTheKeyword(effectiveConnectionOptions, EntityConnectionStringBuilder.MetadataParameterName);
|
||
|
|
||
|
string providerName = ValidateValueForTheKeyword(effectiveConnectionOptions, EntityConnectionStringBuilder.ProviderParameterName);
|
||
|
// Get the correct provider factory
|
||
|
factory = GetFactory(providerName);
|
||
|
|
||
|
// Create the underlying provider specific connection and give it the connection string from the DbConnectionOptions object
|
||
|
storeConnection = GetStoreConnection(factory);
|
||
|
|
||
|
try
|
||
|
{
|
||
|
// When the value of 'Provider Connection String' is null, it means it has not been present in the entity connection string at all.
|
||
|
// Providers should still be able handle empty connection strings since those may be explicitly passed by clients.
|
||
|
string providerConnectionString = effectiveConnectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
|
||
|
if (providerConnectionString != null)
|
||
|
{
|
||
|
storeConnection.ConnectionString = providerConnectionString;
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.Provider(@"ConnectionString", e);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This lock is to ensure that the connection string matches with the provider connection and metadata workspace that's being
|
||
|
// managed by this EntityConnection, so states in this connection object are not messed up.
|
||
|
// It's not for security, but just to help reduce user error.
|
||
|
lock (_connectionStringLock)
|
||
|
{
|
||
|
// Now we have sufficient information and verified the configuration string is good, use them for this connection object
|
||
|
// Failure should not occur from this point to the end of this method
|
||
|
this._providerFactory = factory;
|
||
|
this._metadataWorkspace = null;
|
||
|
|
||
|
ClearTransactions();
|
||
|
ResetStoreConnection(storeConnection, null, false);
|
||
|
|
||
|
// Remembers the connection options objects with the connection string set by the user
|
||
|
this._userConnectionOptions = userConnectionOptions;
|
||
|
this._effectiveConnectionOptions = effectiveConnectionOptions;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static string ValidateValueForTheKeyword(DbConnectionOptions effectiveConnectionOptions,
|
||
|
string keywordName)
|
||
|
{
|
||
|
string keywordValue = effectiveConnectionOptions[keywordName];
|
||
|
if (!string.IsNullOrEmpty(keywordValue))
|
||
|
keywordValue = keywordValue.Trim(); // be nice to user, always trim the value
|
||
|
|
||
|
// Check that we have a non-null and non-empty value for the keyword
|
||
|
if (string.IsNullOrEmpty(keywordValue))
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ConnectionStringMissingInfo(keywordName));
|
||
|
}
|
||
|
return keywordValue;
|
||
|
}
|
||
|
|
||
|
private static EdmItemCollection LoadEdmItemCollection(MetadataWorkspace workspace, MetadataArtifactLoader artifactLoader)
|
||
|
{
|
||
|
// Build a string as the key and look up the MetadataCache for a match
|
||
|
string edmCacheKey = CreateMetadataCacheKey(artifactLoader.GetOriginalPaths(DataSpace.CSpace), null, null);
|
||
|
|
||
|
// Check the MetadataCache for an entry with this key
|
||
|
object entryToken;
|
||
|
EdmItemCollection edmItemCollection = MetadataCache.GetOrCreateEdmItemCollection(edmCacheKey,
|
||
|
artifactLoader,
|
||
|
out entryToken);
|
||
|
workspace.RegisterItemCollection(edmItemCollection);
|
||
|
|
||
|
// Adding the edm metadata entry token to the workspace, to make sure that this token remains alive till workspace is alive
|
||
|
workspace.AddMetadataEntryToken(entryToken);
|
||
|
|
||
|
return edmItemCollection;
|
||
|
}
|
||
|
|
||
|
private static void LoadStoreItemCollections(MetadataWorkspace workspace,
|
||
|
DbConnection storeConnection,
|
||
|
DbProviderFactory factory,
|
||
|
DbConnectionOptions connectionOptions,
|
||
|
EdmItemCollection edmItemCollection,
|
||
|
MetadataArtifactLoader artifactLoader)
|
||
|
{
|
||
|
Debug.Assert(workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace), "C-Space must be loaded before loading S or C-S space");
|
||
|
|
||
|
// The provider connection string is optional; if it has not been specified,
|
||
|
// we pick up the store's connection string.
|
||
|
//
|
||
|
string providerConnectionString = connectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
|
||
|
if (string.IsNullOrEmpty(providerConnectionString) && (storeConnection != null))
|
||
|
{
|
||
|
providerConnectionString = storeConnection.ConnectionString;
|
||
|
}
|
||
|
|
||
|
// Build a string as the key and look up the MetadataCache for a match
|
||
|
string storeCacheKey = CreateMetadataCacheKey(artifactLoader.GetOriginalPaths(),
|
||
|
connectionOptions[EntityConnectionStringBuilder.ProviderParameterName],
|
||
|
providerConnectionString);
|
||
|
|
||
|
// Load store metadata.
|
||
|
object entryToken;
|
||
|
StorageMappingItemCollection mappingCollection =
|
||
|
MetadataCache.GetOrCreateStoreAndMappingItemCollections(storeCacheKey,
|
||
|
artifactLoader,
|
||
|
edmItemCollection,
|
||
|
out entryToken);
|
||
|
|
||
|
workspace.RegisterItemCollection(mappingCollection.StoreItemCollection);
|
||
|
workspace.RegisterItemCollection(mappingCollection);
|
||
|
|
||
|
// Adding the store metadata entry token to the workspace
|
||
|
workspace.AddMetadataEntryToken(entryToken);
|
||
|
}
|
||
|
|
||
|
private static string GetErrorMessageWorthyProviderName(DbProviderFactory factory)
|
||
|
{
|
||
|
EntityUtil.CheckArgumentNull(factory, "factory");
|
||
|
|
||
|
string providerName;
|
||
|
if (!EntityUtil.TryGetProviderInvariantName(factory, out providerName))
|
||
|
{
|
||
|
providerName = factory.GetType().FullName;
|
||
|
}
|
||
|
return providerName;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Create a key to be used with the MetadataCache from a connection options object
|
||
|
/// </summary>
|
||
|
/// <param name="paths">A list of metadata file paths</param>
|
||
|
/// <param name="providerName">The provider name</param>
|
||
|
/// <param name="providerConnectionString">The provider connection string</param>
|
||
|
/// <returns>The key</returns>
|
||
|
private static string CreateMetadataCacheKey(IList<string> paths, string providerName, string providerConnectionString)
|
||
|
{
|
||
|
int resultCount = 0;
|
||
|
string result;
|
||
|
|
||
|
// Do a first pass to calculate the output size of the metadata cache key,
|
||
|
// then another pass to populate a StringBuilder with the exact size and
|
||
|
// get the result.
|
||
|
CreateMetadataCacheKeyWithCount(paths, providerName, providerConnectionString,
|
||
|
false, ref resultCount, out result);
|
||
|
CreateMetadataCacheKeyWithCount(paths, providerName, providerConnectionString,
|
||
|
true, ref resultCount, out result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Create a key to be used with the MetadataCache from a connection options
|
||
|
/// object.
|
||
|
/// </summary>
|
||
|
/// <param name="paths">A list of metadata file paths</param>
|
||
|
/// <param name="providerName">The provider name</param>
|
||
|
/// <param name="providerConnectionString">The provider connection string</param>
|
||
|
/// <param name="buildResult">Whether the result variable should be built.</param>
|
||
|
/// <param name="resultCount">
|
||
|
/// On entry, the expected size of the result (unused if buildResult is false).
|
||
|
/// After execution, the effective result.</param>
|
||
|
/// <param name="result">The key.</param>
|
||
|
/// <remarks>
|
||
|
/// This method should be called once with buildResult=false, to get
|
||
|
/// the size of the resulting key, and once with buildResult=true
|
||
|
/// and the size specification.
|
||
|
/// </remarks>
|
||
|
private static void CreateMetadataCacheKeyWithCount(IList<string> paths,
|
||
|
string providerName, string providerConnectionString,
|
||
|
bool buildResult, ref int resultCount, out string result)
|
||
|
{
|
||
|
// Build a string as the key and look up the MetadataCache for a match
|
||
|
StringBuilder keyString;
|
||
|
if (buildResult)
|
||
|
{
|
||
|
keyString = new StringBuilder(resultCount);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
keyString = null;
|
||
|
}
|
||
|
|
||
|
// At this point, we've already used resultCount. Reset it
|
||
|
// to zero to make the final debug assertion that our computation
|
||
|
// is correct.
|
||
|
resultCount = 0;
|
||
|
|
||
|
if (!string.IsNullOrEmpty(providerName))
|
||
|
{
|
||
|
resultCount += providerName.Length + 1;
|
||
|
if (buildResult)
|
||
|
{
|
||
|
keyString.Append(providerName);
|
||
|
keyString.Append(EntityConnection.s_semicolonSeparator);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (paths != null)
|
||
|
{
|
||
|
for (int i = 0; i < paths.Count; i++)
|
||
|
{
|
||
|
if (paths[i].Length > 0)
|
||
|
{
|
||
|
if (i > 0)
|
||
|
{
|
||
|
resultCount++;
|
||
|
if (buildResult)
|
||
|
{
|
||
|
keyString.Append(EntityConnection.s_metadataPathSeparator);
|
||
|
}
|
||
|
}
|
||
|
resultCount += paths[i].Length;
|
||
|
if (buildResult)
|
||
|
{
|
||
|
keyString.Append(paths[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
resultCount++;
|
||
|
if (buildResult)
|
||
|
{
|
||
|
keyString.Append(EntityConnection.s_semicolonSeparator);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!string.IsNullOrEmpty(providerConnectionString))
|
||
|
{
|
||
|
resultCount += providerConnectionString.Length;
|
||
|
if (buildResult)
|
||
|
{
|
||
|
keyString.Append(providerConnectionString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (buildResult)
|
||
|
{
|
||
|
result = keyString.ToString();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = null;
|
||
|
}
|
||
|
|
||
|
System.Diagnostics.Debug.Assert(
|
||
|
!buildResult || (result.Length == resultCount));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Clears the current DbTransaction and the transaction the user enlisted the connection in
|
||
|
/// with EnlistTransaction() method.
|
||
|
/// </summary>
|
||
|
private void ClearTransactions()
|
||
|
{
|
||
|
ClearCurrentTransaction();
|
||
|
ClearEnlistedTransaction();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Clears the current DbTransaction for this connection
|
||
|
/// </summary>
|
||
|
internal void ClearCurrentTransaction()
|
||
|
{
|
||
|
_currentTransaction = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Clears the transaction the user elinsted in using EnlistTransaction() method.
|
||
|
/// </summary>
|
||
|
private void ClearEnlistedTransaction()
|
||
|
{
|
||
|
if (EnlistedInUserTransaction)
|
||
|
{
|
||
|
_enlistedTransaction.TransactionCompleted -= EnlistedTransactionCompleted;
|
||
|
}
|
||
|
|
||
|
_enlistedTransaction = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Event handler invoked when the transaction has completed (either by committing or rolling back).
|
||
|
/// </summary>
|
||
|
/// <param name="sender">The source of the event.</param>
|
||
|
/// <param name="e">The <see cref="TransactionEventArgs"/> that contains the event data.</param>
|
||
|
/// <remarks>Note that to avoid threading issues we never reset the <see cref=" _enlistedTransaction"/> field here.</remarks>
|
||
|
private void EnlistedTransactionCompleted(object sender, TransactionEventArgs e)
|
||
|
{
|
||
|
e.Transaction.TransactionCompleted -= EnlistedTransactionCompleted;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method invoked as part of Close()/Dispose() that releases the underlying
|
||
|
/// store connection and raises the appropriate event.
|
||
|
/// </summary>
|
||
|
private void CloseHelper()
|
||
|
{
|
||
|
ConnectionState previousState = this.State; // the public connection state before cleanup
|
||
|
StoreCloseHelper();
|
||
|
EntityCloseHelper(
|
||
|
true, // raise the state change event
|
||
|
previousState
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Store-specific helper method invoked as part of Close()/Dispose().
|
||
|
/// </summary>
|
||
|
private void StoreCloseHelper()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (this._storeConnection != null && (this._storeConnection.State != ConnectionState.Closed))
|
||
|
{
|
||
|
this._storeConnection.Close();
|
||
|
}
|
||
|
// Need to disassociate the transaction objects with this connection
|
||
|
ClearTransactions();
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (EntityUtil.IsCatchableExceptionType(e))
|
||
|
{
|
||
|
throw EntityUtil.ProviderExceptionWithMessage(
|
||
|
System.Data.Entity.Strings.EntityClient_ErrorInClosingConnection,
|
||
|
e
|
||
|
);
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Entity-specific helper method invoked as part of Close()/Dispose().
|
||
|
/// </summary>
|
||
|
/// <param name="fireEventOnStateChange">Indicates whether we need to raise the state change event here</param>
|
||
|
/// <param name="previousState">The public state of the connection before cleanup began</param>
|
||
|
/// <returns>true if the caller needs to raise the state change event</returns>
|
||
|
private bool EntityCloseHelper(bool fireEventOnStateChange, ConnectionState previousState)
|
||
|
{
|
||
|
bool result = false;
|
||
|
|
||
|
this._entityClientConnectionState = ConnectionState.Closed;
|
||
|
|
||
|
if (previousState == ConnectionState.Open)
|
||
|
{
|
||
|
if (fireEventOnStateChange)
|
||
|
{
|
||
|
OnStateChange(StateChangeClosed);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = true; // we didn't raise the event here; the caller should do that
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Call to determine if changes to the entity object are currently permitted.
|
||
|
/// </summary>
|
||
|
private void ValidateChangesPermitted()
|
||
|
{
|
||
|
if (_initialized)
|
||
|
{
|
||
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_SettingsCannotBeChangedOnOpenConnection);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the DbProviderFactory associated with specified provider string
|
||
|
/// </summary>
|
||
|
private DbProviderFactory GetFactory(string providerString)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
return DbProviderFactories.GetFactory(providerString);
|
||
|
}
|
||
|
catch (ArgumentException e)
|
||
|
{
|
||
|
throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidStoreProvider, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Uses DbProviderFactory to create a DbConnection
|
||
|
/// </summary>
|
||
|
private DbConnection GetStoreConnection(DbProviderFactory factory)
|
||
|
{
|
||
|
DbConnection storeConnection = factory.CreateConnection();
|
||
|
if (storeConnection == null)
|
||
|
{
|
||
|
throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_ReturnedNullOnProviderMethod("CreateConnection", factory.GetType().Name));
|
||
|
}
|
||
|
return storeConnection;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|