Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataEntity / System / Data / EntityClient / EntityConnection.cs / 4 / EntityConnection.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....], [....] //--------------------------------------------------------------------- using System.Collections; using System.Collections.Generic; using System.Text; using System.Data; using System.Data.Common; using System.Data.Common.EntitySql; using System.Configuration; using System.IO; using System.Diagnostics; using System.Data.Metadata; using System.Xml; using System.Data.Mapping; using System.Transactions; namespace System.Data.EntityClient { using Metadata.Edm; ////// 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. /// 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_dataDirectory = "|datadirectory|"; private const string s_metadataid = "metadata id"; private const string s_providerInvariantName = "provider"; private const string s_providerConnectionString = "provider connection string"; #endregion private readonly object _connectionStringLock = new object(); private static readonly DbConnectionOptions s_emptyConnectionOptions = new DbConnectionOptions(String.Empty, null, false); // 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 MetadataWorkspace _metadataWorkspace; private EntityTransaction _currentTransaction; private Guid _metadataID; //set only when constructed using MetadataWorkspace 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; private static int _objectTypeCount; // Bid counter internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); ////// Constructs the EntityConnection object with a connection not yet associated to a particular store /// public EntityConnection() : this(String.Empty) { } ////// Constructs the EntityConnection object with a connection string /// /// The connection string, may contain a list of settings for the connection or /// just the name of the connection to use public EntityConnection(string connectionString) { GC.SuppressFinalize(this); this.ChangeConnectionString(connectionString); } ////// Constructs the EntityConnection from Metadata loaded in memory /// /// Workspace containing metadata information. public EntityConnection(MetadataWorkspace workspace, DbConnection connection) { 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); } if (connection.ProviderFactory == null) { throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_DbConnectionHasNoProvider(connection)); } StoreItemCollection collection = (StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace); ValidateThatConnectionAndMetadataProvidersAreEqual(connection.ProviderFactory, null, collection.StoreProviderFactory); GC.SuppressFinalize(this); _providerFactory = connection.ProviderFactory; _storeConnection = connection; _metadataWorkspace = workspace; _initialized = true; _metadataID = System.Guid.NewGuid(); } ////// Get or set the entity connection string associated with this connection object /// 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(_storeConnection.ProviderFactory, out invariantName)) { Debug.Fail("Provider Invariant Name not found"); invariantName = ""; } return string.Format(Globalization.CultureInfo.InvariantCulture, "{0}={3};{1}={4};{2}=\"{5}\";", s_metadataid, s_providerInvariantName, s_providerConnectionString, _metadataID.ToString(), invariantName, FormatProviderString(_storeConnection.ConnectionString)); } string userConnectionString = this._userConnectionOptions.UsersConnectionString(false); // 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; } set { ValidateChangesPermitted(); this.ChangeConnectionString(value); } } ////// Formats provider string to replace " with \" so it can be appended within quotation marks "..." /// private static string FormatProviderString(string providerString) { return providerString.Trim().Replace("\"", "\\\""); } ////// Get the time to wait when attempting to establish a connection before ending the try and generating an error /// 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; } } } ////// Get the name of the current database or the database that will be used after a connection is opened /// public override string Database { get { return String.Empty; } } ////// Gets the ConnectionState property of the EntityConnection /// 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; } } } ////// Gets the name or network address of the data source to connect to /// 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; } } } ////// Gets a string that contains the version of the data store to which the client is connected /// 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; } } } ////// Gets the provider factory associated with EntityConnection /// override protected DbProviderFactory DbProviderFactory { get { return EntityProviderFactory.Instance; } } ////// Gets the DbProviderFactory for the underlying provider /// internal DbProviderFactory StoreProviderFactory { get { return this._providerFactory; } } ////// Gets the DbConnection for the underlying provider connection /// public DbConnection StoreConnection { get { return this._storeConnection; } } ////// Gets the metadata workspace used by this connection /// public MetadataWorkspace GetMetadataWorkspace() { return GetMetadataWorkspace(true /* initializeAllCollections */); } 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(); _artifactLoader = SplitPaths(_effectiveConnectionOptions[EntityConnectionStringBuilder.MetadataParameterName]); 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; } ////// Gets the current transaction that this connection is enlisted in /// 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; } } ////// Establish a connection to the data store by calling the Open method on the underlying data provider /// 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(); } ////// Helper method that conditionally opens a specified store connection /// /// The condition to evaluate /// The store connection to open /// The original store connection associated with the entity client /// A flag that is set on if the connection is opened /// successfully 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 // transaction (if there is one) ClearCurrentTransaction(); } 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; } } ////// Helper method to initialize the metadata workspace and reset the store connection /// associated with the entity client /// /// The new connection to associate with the entity client /// The original connection associated with the entity client /// A flag to indicate whether the original /// store connection needs to be closed on failure 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; } } ////// Set the entity client connection state to Open, and raise an appropriate event /// private void SetEntityClientConnectionStateToOpen() { this._entityClientConnectionState = ConnectionState.Open; OnStateChange(System.Data.ProviderBase.DbConnectionInternal.StateChangeOpen); } ////// This method sets the store connection and hooks up the event /// /// The DbConnection to set /// The original DbConnection to be closed - this argument could be null /// Indicates whether the original store connection should be closed private void ResetStoreConnection(DbConnection newConnection, DbConnection originalConnection, bool closeOriginalConnection) { this._storeConnection = newConnection; if (closeOriginalConnection && (originalConnection != null)) { originalConnection.Close(); } } ////// Create a new command object that uses this connection object /// public new EntityCommand CreateCommand() { return new EntityCommand(null, this); } ////// Create a new command object that uses this connection object /// protected override DbCommand CreateDbCommand() { return this.CreateCommand(); } internal EntityConnection Clone() { return new EntityConnection(_userConnectionOptions.UsersConnectionString(false)); } ////// Close the connection to the data store /// public override void Close() { // It's a no-op if there isn't an underlying connection if (this._storeConnection == null) return; this.CloseHelper(); } ////// Changes the current database for this connection /// /// The name of the database to change to public override void ChangeDatabase(string databaseName) { throw EntityUtil.NotSupported(); } ////// Begins a database transaction /// ///An object representing the new transaction public new EntityTransaction BeginTransaction() { return base.BeginTransaction() as EntityTransaction; } ////// Begins a database transaction /// /// The isolation level of the transaction ///An object representing the new transaction public new EntityTransaction BeginTransaction(IsolationLevel isolationLevel) { return base.BeginTransaction(isolationLevel) as EntityTransaction; } ////// Begins a database transaction /// /// The isolation level of the transaction ///An object representing the new transaction 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; } ////// Enlist in the given transaction /// /// The transaction object to enlist into 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); } catch (Exception e) { if (EntityUtil.IsCatchableExceptionType(e)) { throw EntityUtil.Provider(@"EnlistTransaction", e); } throw; } } ////// Cleans up this connection object /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_currentTransaction")] protected override void Dispose(bool disposing) { ClearCurrentTransaction(); bool raiseStateChangeEvent = EntityCloseHelper(false, this.State); if (disposing) { if (this._storeConnection != null) { StoreCloseHelper(); // closes store connection if (this._storeConnection != null) { 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(System.Data.ProviderBase.DbConnectionInternal.StateChangeClosed); } base.Dispose(disposing); } ////// Reinitialize this connection object to use the new connection string /// /// The new connection string private void ChangeConnectionString(string newConnectionString) { DbConnectionOptions userConnectionOptions = s_emptyConnectionOptions; if (!String.IsNullOrEmpty(newConnectionString)) { userConnectionOptions = new DbConnectionOptions(newConnectionString, EntityConnectionStringBuilder.Synonyms, false); } 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, false); // 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; ClearCurrentTransaction(); 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); ValidateThatConnectionAndMetadataProvidersAreEqual(factory, connectionOptions[EntityConnectionStringBuilder.ProviderParameterName], mappingCollection.StoreItemCollection.StoreProviderFactory); workspace.RegisterItemCollection(mappingCollection.StoreItemCollection); workspace.RegisterItemCollection(mappingCollection); // Adding the store metadata entry token to the workspace workspace.AddMetadataEntryToken(entryToken); } private static void ValidateThatConnectionAndMetadataProvidersAreEqual(DbProviderFactory connectionFactory, string connectionProviderName, DbProviderFactory metadataFactory) { if (metadataFactory.GetType() != connectionFactory.GetType()) { string metadataProviderName = GetErrorMessageWorthyProviderName(metadataFactory); if (string.IsNullOrEmpty(connectionProviderName)) { connectionProviderName = GetErrorMessageWorthyProviderName(connectionFactory); } throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionAndMetadataProviderMismatch(metadataProviderName, connectionProviderName)); } } private static string GetErrorMessageWorthyProviderName(DbProviderFactory factory) { EntityUtil.CheckArgumentNull(factory, "factory"); string providerName; if (!EntityUtil.TryGetProviderInvariantName(factory, out providerName)) { providerName = factory.GetType().FullName; } return providerName; } ////// Create a key to be used with the MetadataCache from a connection options object /// /// A list of metadata file paths /// The provider name /// The provider connection string ///The key private static string CreateMetadataCacheKey(IListpaths, 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; } /// /// Create a key to be used with the MetadataCache from a connection options /// object. /// /// A list of metadata file paths /// The provider name /// The provider connection string /// Whether the result variable should be built. /// /// On entry, the expected size of the result (unused if buildResult is false). /// After execution, the effective result. /// The key. ////// 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. /// private static void CreateMetadataCacheKeyWithCount(IListpaths, 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)); } /// /// A helper function for splitting up a string that is a concatenation of strings delimited by the metadata /// path separator into a string list. The resulting list is NOT sorted. /// /// The paths to split ///An array of strings private static MetadataArtifactLoader SplitPaths(string paths) { string[] results; // This is the registry of all URIs in the global collection. HashSeturiRegistry = new HashSet (StringComparer.OrdinalIgnoreCase); List loaders = new List (); if (!string.IsNullOrEmpty(paths)) { // If the argument contains one or more occurrences of the macro '|DataDirectory|', we // pull those paths out so that we don't lose them in the string-splitting logic below. // Note that the macro '|DataDirectory|' cannot have any whitespace between the pipe // symbols and the macro name. Also note that the macro must appear at the beginning of // a path (else we will eventually fail with an invalid path exception, because in that // case the macro is not expanded). If a real/physical folder named 'DataDirectory' needs // to be included in the metadata path, whitespace should be used on either or both sides // of the name. // List dataDirPaths = new List (); int indexStart = paths.IndexOf(EntityConnection.s_dataDirectory, StringComparison.OrdinalIgnoreCase); while (indexStart != -1) { int prevSeparatorIndex = indexStart == 0 ? -1 : paths.LastIndexOf( EntityConnection.s_metadataPathSeparator, indexStart - 1, // start looking here StringComparison.Ordinal ); int macroPathBeginIndex = prevSeparatorIndex + 1; // The '|DataDirectory|' macro is composable, so identify the complete path, like // '|DataDirectory|\foo\bar'. If the macro appears anywhere other than at the // beginning, splice out the entire path, e.g. 'C:\foo\|DataDirectory|\bar'. In this // latter case the macro will not be expanded, and downstream code will throw an exception. // int indexEnd = paths.IndexOf(EntityConnection.s_metadataPathSeparator, indexStart + EntityConnection.s_dataDirectory.Length, StringComparison.Ordinal); if (indexEnd == -1) { dataDirPaths.Add(paths.Substring(macroPathBeginIndex)); paths = paths.Remove(macroPathBeginIndex); // update the concatenated list of paths break; } dataDirPaths.Add(paths.Substring(macroPathBeginIndex, indexEnd - macroPathBeginIndex)); // Update the concatenated list of paths by removing the one containing the macro. // paths = paths.Remove(macroPathBeginIndex, indexEnd - macroPathBeginIndex); indexStart = paths.IndexOf(EntityConnection.s_dataDirectory, StringComparison.OrdinalIgnoreCase); } // Split the string on the separator and remove all spaces around each parameter value results = paths.Split(new string[] { EntityConnection.s_metadataPathSeparator }, StringSplitOptions.RemoveEmptyEntries); // Now that the non-macro paths have been identified, merge the paths containing the macro // into the complete list. // if (dataDirPaths.Count > 0) { dataDirPaths.AddRange(results); results = dataDirPaths.ToArray(); } for (int i = 0; i < results.Length; i++) { // Trim out all the spaces for this parameter and add it only if it's not blank results[i] = results[i].Trim(); if (results[i].Length > 0) { loaders.Add(MetadataArtifactLoader.Create( results[i], MetadataArtifactLoader.ExtensionCheck.All, // validate the extension against all acceptable values null, uriRegistry )); } } } return MetadataArtifactLoader.Create(loaders); } /// /// Clears the current transaction for this connection /// internal void ClearCurrentTransaction() { _currentTransaction = null; } ////// Helper method invoked as part of Close()/Dispose() that releases the underlying /// store connection and raises the appropriate event. /// private void CloseHelper() { ConnectionState previousState = this.State; // the public connection state before cleanup StoreCloseHelper(); EntityCloseHelper( true, // raise the state change event previousState ); } ////// Store-specific helper method invoked as part of Close()/Dispose(). /// private void StoreCloseHelper() { try { if (this._storeConnection != null && (this._storeConnection.State != ConnectionState.Closed)) { this._storeConnection.Close(); } // Need to disassociate the transaction object with this connection ClearCurrentTransaction(); } catch (Exception e) { if (EntityUtil.IsCatchableExceptionType(e)) { throw EntityUtil.ProviderExceptionWithMessage( System.Data.Entity.Strings.EntityClient_ErrorInClosingConnection, e ); } throw; } } ////// Entity-specific helper method invoked as part of Close()/Dispose(). /// /// Indicates whether we need to raise the state change event here /// The public state of the connection before cleanup began ///true if the caller needs to raise the state change event private bool EntityCloseHelper(bool fireEventOnStateChange, ConnectionState previousState) { bool result = false; this._entityClientConnectionState = ConnectionState.Closed; if (previousState == ConnectionState.Open) { if (fireEventOnStateChange) { OnStateChange(System.Data.ProviderBase.DbConnectionInternal.StateChangeClosed); } else { result = true; // we didn't raise the event here; the caller should do that } } return result; } ////// Call to determine if changes to the entity object are currently permitted. /// private void ValidateChangesPermitted() { if (_initialized) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_SettingsCannotBeChangedOnOpenConnection); } } ////// Returns the DbProviderFactory associated with specified provider string /// private DbProviderFactory GetFactory(string providerString) { try { return DbProviderFactories.GetFactory(providerString); } catch (ArgumentException e) { throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidStoreProvider, e); } } ////// Uses DbProviderFactory to create a DbConnection /// 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; } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....], [....] //--------------------------------------------------------------------- using System.Collections; using System.Collections.Generic; using System.Text; using System.Data; using System.Data.Common; using System.Data.Common.EntitySql; using System.Configuration; using System.IO; using System.Diagnostics; using System.Data.Metadata; using System.Xml; using System.Data.Mapping; using System.Transactions; namespace System.Data.EntityClient { using Metadata.Edm; ////// 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. /// 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_dataDirectory = "|datadirectory|"; private const string s_metadataid = "metadata id"; private const string s_providerInvariantName = "provider"; private const string s_providerConnectionString = "provider connection string"; #endregion private readonly object _connectionStringLock = new object(); private static readonly DbConnectionOptions s_emptyConnectionOptions = new DbConnectionOptions(String.Empty, null, false); // 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 MetadataWorkspace _metadataWorkspace; private EntityTransaction _currentTransaction; private Guid _metadataID; //set only when constructed using MetadataWorkspace 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; private static int _objectTypeCount; // Bid counter internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); ////// Constructs the EntityConnection object with a connection not yet associated to a particular store /// public EntityConnection() : this(String.Empty) { } ////// Constructs the EntityConnection object with a connection string /// /// The connection string, may contain a list of settings for the connection or /// just the name of the connection to use public EntityConnection(string connectionString) { GC.SuppressFinalize(this); this.ChangeConnectionString(connectionString); } ////// Constructs the EntityConnection from Metadata loaded in memory /// /// Workspace containing metadata information. public EntityConnection(MetadataWorkspace workspace, DbConnection connection) { 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); } if (connection.ProviderFactory == null) { throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_DbConnectionHasNoProvider(connection)); } StoreItemCollection collection = (StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace); ValidateThatConnectionAndMetadataProvidersAreEqual(connection.ProviderFactory, null, collection.StoreProviderFactory); GC.SuppressFinalize(this); _providerFactory = connection.ProviderFactory; _storeConnection = connection; _metadataWorkspace = workspace; _initialized = true; _metadataID = System.Guid.NewGuid(); } ////// Get or set the entity connection string associated with this connection object /// 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(_storeConnection.ProviderFactory, out invariantName)) { Debug.Fail("Provider Invariant Name not found"); invariantName = ""; } return string.Format(Globalization.CultureInfo.InvariantCulture, "{0}={3};{1}={4};{2}=\"{5}\";", s_metadataid, s_providerInvariantName, s_providerConnectionString, _metadataID.ToString(), invariantName, FormatProviderString(_storeConnection.ConnectionString)); } string userConnectionString = this._userConnectionOptions.UsersConnectionString(false); // 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; } set { ValidateChangesPermitted(); this.ChangeConnectionString(value); } } ////// Formats provider string to replace " with \" so it can be appended within quotation marks "..." /// private static string FormatProviderString(string providerString) { return providerString.Trim().Replace("\"", "\\\""); } ////// Get the time to wait when attempting to establish a connection before ending the try and generating an error /// 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; } } } ////// Get the name of the current database or the database that will be used after a connection is opened /// public override string Database { get { return String.Empty; } } ////// Gets the ConnectionState property of the EntityConnection /// 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; } } } ////// Gets the name or network address of the data source to connect to /// 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; } } } ////// Gets a string that contains the version of the data store to which the client is connected /// 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; } } } ////// Gets the provider factory associated with EntityConnection /// override protected DbProviderFactory DbProviderFactory { get { return EntityProviderFactory.Instance; } } ////// Gets the DbProviderFactory for the underlying provider /// internal DbProviderFactory StoreProviderFactory { get { return this._providerFactory; } } ////// Gets the DbConnection for the underlying provider connection /// public DbConnection StoreConnection { get { return this._storeConnection; } } ////// Gets the metadata workspace used by this connection /// public MetadataWorkspace GetMetadataWorkspace() { return GetMetadataWorkspace(true /* initializeAllCollections */); } 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(); _artifactLoader = SplitPaths(_effectiveConnectionOptions[EntityConnectionStringBuilder.MetadataParameterName]); 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; } ////// Gets the current transaction that this connection is enlisted in /// 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; } } ////// Establish a connection to the data store by calling the Open method on the underlying data provider /// 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(); } ////// Helper method that conditionally opens a specified store connection /// /// The condition to evaluate /// The store connection to open /// The original store connection associated with the entity client /// A flag that is set on if the connection is opened /// successfully 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 // transaction (if there is one) ClearCurrentTransaction(); } 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; } } ////// Helper method to initialize the metadata workspace and reset the store connection /// associated with the entity client /// /// The new connection to associate with the entity client /// The original connection associated with the entity client /// A flag to indicate whether the original /// store connection needs to be closed on failure 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; } } ////// Set the entity client connection state to Open, and raise an appropriate event /// private void SetEntityClientConnectionStateToOpen() { this._entityClientConnectionState = ConnectionState.Open; OnStateChange(System.Data.ProviderBase.DbConnectionInternal.StateChangeOpen); } ////// This method sets the store connection and hooks up the event /// /// The DbConnection to set /// The original DbConnection to be closed - this argument could be null /// Indicates whether the original store connection should be closed private void ResetStoreConnection(DbConnection newConnection, DbConnection originalConnection, bool closeOriginalConnection) { this._storeConnection = newConnection; if (closeOriginalConnection && (originalConnection != null)) { originalConnection.Close(); } } ////// Create a new command object that uses this connection object /// public new EntityCommand CreateCommand() { return new EntityCommand(null, this); } ////// Create a new command object that uses this connection object /// protected override DbCommand CreateDbCommand() { return this.CreateCommand(); } internal EntityConnection Clone() { return new EntityConnection(_userConnectionOptions.UsersConnectionString(false)); } ////// Close the connection to the data store /// public override void Close() { // It's a no-op if there isn't an underlying connection if (this._storeConnection == null) return; this.CloseHelper(); } ////// Changes the current database for this connection /// /// The name of the database to change to public override void ChangeDatabase(string databaseName) { throw EntityUtil.NotSupported(); } ////// Begins a database transaction /// ///An object representing the new transaction public new EntityTransaction BeginTransaction() { return base.BeginTransaction() as EntityTransaction; } ////// Begins a database transaction /// /// The isolation level of the transaction ///An object representing the new transaction public new EntityTransaction BeginTransaction(IsolationLevel isolationLevel) { return base.BeginTransaction(isolationLevel) as EntityTransaction; } ////// Begins a database transaction /// /// The isolation level of the transaction ///An object representing the new transaction 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; } ////// Enlist in the given transaction /// /// The transaction object to enlist into 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); } catch (Exception e) { if (EntityUtil.IsCatchableExceptionType(e)) { throw EntityUtil.Provider(@"EnlistTransaction", e); } throw; } } ////// Cleans up this connection object /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_currentTransaction")] protected override void Dispose(bool disposing) { ClearCurrentTransaction(); bool raiseStateChangeEvent = EntityCloseHelper(false, this.State); if (disposing) { if (this._storeConnection != null) { StoreCloseHelper(); // closes store connection if (this._storeConnection != null) { 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(System.Data.ProviderBase.DbConnectionInternal.StateChangeClosed); } base.Dispose(disposing); } ////// Reinitialize this connection object to use the new connection string /// /// The new connection string private void ChangeConnectionString(string newConnectionString) { DbConnectionOptions userConnectionOptions = s_emptyConnectionOptions; if (!String.IsNullOrEmpty(newConnectionString)) { userConnectionOptions = new DbConnectionOptions(newConnectionString, EntityConnectionStringBuilder.Synonyms, false); } 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, false); // 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; ClearCurrentTransaction(); 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); ValidateThatConnectionAndMetadataProvidersAreEqual(factory, connectionOptions[EntityConnectionStringBuilder.ProviderParameterName], mappingCollection.StoreItemCollection.StoreProviderFactory); workspace.RegisterItemCollection(mappingCollection.StoreItemCollection); workspace.RegisterItemCollection(mappingCollection); // Adding the store metadata entry token to the workspace workspace.AddMetadataEntryToken(entryToken); } private static void ValidateThatConnectionAndMetadataProvidersAreEqual(DbProviderFactory connectionFactory, string connectionProviderName, DbProviderFactory metadataFactory) { if (metadataFactory.GetType() != connectionFactory.GetType()) { string metadataProviderName = GetErrorMessageWorthyProviderName(metadataFactory); if (string.IsNullOrEmpty(connectionProviderName)) { connectionProviderName = GetErrorMessageWorthyProviderName(connectionFactory); } throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionAndMetadataProviderMismatch(metadataProviderName, connectionProviderName)); } } private static string GetErrorMessageWorthyProviderName(DbProviderFactory factory) { EntityUtil.CheckArgumentNull(factory, "factory"); string providerName; if (!EntityUtil.TryGetProviderInvariantName(factory, out providerName)) { providerName = factory.GetType().FullName; } return providerName; } ////// Create a key to be used with the MetadataCache from a connection options object /// /// A list of metadata file paths /// The provider name /// The provider connection string ///The key private static string CreateMetadataCacheKey(IListpaths, 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; } /// /// Create a key to be used with the MetadataCache from a connection options /// object. /// /// A list of metadata file paths /// The provider name /// The provider connection string /// Whether the result variable should be built. /// /// On entry, the expected size of the result (unused if buildResult is false). /// After execution, the effective result. /// The key. ////// 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. /// private static void CreateMetadataCacheKeyWithCount(IListpaths, 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)); } /// /// A helper function for splitting up a string that is a concatenation of strings delimited by the metadata /// path separator into a string list. The resulting list is NOT sorted. /// /// The paths to split ///An array of strings private static MetadataArtifactLoader SplitPaths(string paths) { string[] results; // This is the registry of all URIs in the global collection. HashSeturiRegistry = new HashSet (StringComparer.OrdinalIgnoreCase); List loaders = new List (); if (!string.IsNullOrEmpty(paths)) { // If the argument contains one or more occurrences of the macro '|DataDirectory|', we // pull those paths out so that we don't lose them in the string-splitting logic below. // Note that the macro '|DataDirectory|' cannot have any whitespace between the pipe // symbols and the macro name. Also note that the macro must appear at the beginning of // a path (else we will eventually fail with an invalid path exception, because in that // case the macro is not expanded). If a real/physical folder named 'DataDirectory' needs // to be included in the metadata path, whitespace should be used on either or both sides // of the name. // List dataDirPaths = new List (); int indexStart = paths.IndexOf(EntityConnection.s_dataDirectory, StringComparison.OrdinalIgnoreCase); while (indexStart != -1) { int prevSeparatorIndex = indexStart == 0 ? -1 : paths.LastIndexOf( EntityConnection.s_metadataPathSeparator, indexStart - 1, // start looking here StringComparison.Ordinal ); int macroPathBeginIndex = prevSeparatorIndex + 1; // The '|DataDirectory|' macro is composable, so identify the complete path, like // '|DataDirectory|\foo\bar'. If the macro appears anywhere other than at the // beginning, splice out the entire path, e.g. 'C:\foo\|DataDirectory|\bar'. In this // latter case the macro will not be expanded, and downstream code will throw an exception. // int indexEnd = paths.IndexOf(EntityConnection.s_metadataPathSeparator, indexStart + EntityConnection.s_dataDirectory.Length, StringComparison.Ordinal); if (indexEnd == -1) { dataDirPaths.Add(paths.Substring(macroPathBeginIndex)); paths = paths.Remove(macroPathBeginIndex); // update the concatenated list of paths break; } dataDirPaths.Add(paths.Substring(macroPathBeginIndex, indexEnd - macroPathBeginIndex)); // Update the concatenated list of paths by removing the one containing the macro. // paths = paths.Remove(macroPathBeginIndex, indexEnd - macroPathBeginIndex); indexStart = paths.IndexOf(EntityConnection.s_dataDirectory, StringComparison.OrdinalIgnoreCase); } // Split the string on the separator and remove all spaces around each parameter value results = paths.Split(new string[] { EntityConnection.s_metadataPathSeparator }, StringSplitOptions.RemoveEmptyEntries); // Now that the non-macro paths have been identified, merge the paths containing the macro // into the complete list. // if (dataDirPaths.Count > 0) { dataDirPaths.AddRange(results); results = dataDirPaths.ToArray(); } for (int i = 0; i < results.Length; i++) { // Trim out all the spaces for this parameter and add it only if it's not blank results[i] = results[i].Trim(); if (results[i].Length > 0) { loaders.Add(MetadataArtifactLoader.Create( results[i], MetadataArtifactLoader.ExtensionCheck.All, // validate the extension against all acceptable values null, uriRegistry )); } } } return MetadataArtifactLoader.Create(loaders); } /// /// Clears the current transaction for this connection /// internal void ClearCurrentTransaction() { _currentTransaction = null; } ////// Helper method invoked as part of Close()/Dispose() that releases the underlying /// store connection and raises the appropriate event. /// private void CloseHelper() { ConnectionState previousState = this.State; // the public connection state before cleanup StoreCloseHelper(); EntityCloseHelper( true, // raise the state change event previousState ); } ////// Store-specific helper method invoked as part of Close()/Dispose(). /// private void StoreCloseHelper() { try { if (this._storeConnection != null && (this._storeConnection.State != ConnectionState.Closed)) { this._storeConnection.Close(); } // Need to disassociate the transaction object with this connection ClearCurrentTransaction(); } catch (Exception e) { if (EntityUtil.IsCatchableExceptionType(e)) { throw EntityUtil.ProviderExceptionWithMessage( System.Data.Entity.Strings.EntityClient_ErrorInClosingConnection, e ); } throw; } } ////// Entity-specific helper method invoked as part of Close()/Dispose(). /// /// Indicates whether we need to raise the state change event here /// The public state of the connection before cleanup began ///true if the caller needs to raise the state change event private bool EntityCloseHelper(bool fireEventOnStateChange, ConnectionState previousState) { bool result = false; this._entityClientConnectionState = ConnectionState.Closed; if (previousState == ConnectionState.Open) { if (fireEventOnStateChange) { OnStateChange(System.Data.ProviderBase.DbConnectionInternal.StateChangeClosed); } else { result = true; // we didn't raise the event here; the caller should do that } } return result; } ////// Call to determine if changes to the entity object are currently permitted. /// private void ValidateChangesPermitted() { if (_initialized) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_SettingsCannotBeChangedOnOpenConnection); } } ////// Returns the DbProviderFactory associated with specified provider string /// private DbProviderFactory GetFactory(string providerString) { try { return DbProviderFactories.GetFactory(providerString); } catch (ArgumentException e) { throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidStoreProvider, e); } } ////// Uses DbProviderFactory to create a DbConnection /// 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; } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ItemsControl.cs
- FunctionParameter.cs
- ExtensionDataObject.cs
- XmlDesigner.cs
- TextRenderingModeValidation.cs
- Sentence.cs
- ParallelTimeline.cs
- DataGridViewControlCollection.cs
- WindowsAltTab.cs
- StateWorkerRequest.cs
- DataGridViewRowHeaderCell.cs
- FixUpCollection.cs
- RenderContext.cs
- RtfFormatStack.cs
- WithParamAction.cs
- InkSerializer.cs
- WmlPageAdapter.cs
- StrongTypingException.cs
- DataBindingList.cs
- CreatingCookieEventArgs.cs
- ProviderConnectionPointCollection.cs
- BooleanConverter.cs
- BufferedWebEventProvider.cs
- SecurityTokenProviderContainer.cs
- DataConnectionHelper.cs
- ExtractedStateEntry.cs
- Rules.cs
- AVElementHelper.cs
- BamlReader.cs
- CommandID.cs
- Oci.cs
- activationcontext.cs
- LowerCaseStringConverter.cs
- ColorComboBox.cs
- XsdValidatingReader.cs
- _IPv6Address.cs
- User.cs
- HashHelper.cs
- DataGridViewButtonCell.cs
- ReliableChannelListener.cs
- SynchronizationLockException.cs
- MultiAsyncResult.cs
- GenericAuthenticationEventArgs.cs
- SecurityBindingElement.cs
- CodeExporter.cs
- DragDeltaEventArgs.cs
- LassoHelper.cs
- DrawTreeNodeEventArgs.cs
- TCEAdapterGenerator.cs
- UriPrefixTable.cs
- FormatControl.cs
- TimeIntervalCollection.cs
- PathGradientBrush.cs
- ParameterElement.cs
- SqlEnums.cs
- PresentationTraceSources.cs
- Geometry3D.cs
- PeerFlooder.cs
- HtmlElementEventArgs.cs
- WmlValidatorAdapter.cs
- WebRequestModuleElementCollection.cs
- HitTestWithPointDrawingContextWalker.cs
- PhoneCall.cs
- DataGridViewCellCancelEventArgs.cs
- DataBindingList.cs
- Expression.cs
- DataGridItemCollection.cs
- SHA1CryptoServiceProvider.cs
- LongPath.cs
- OracleCommand.cs
- Graph.cs
- QualificationDataAttribute.cs
- ToolStripSettings.cs
- OperatorExpressions.cs
- XhtmlTextWriter.cs
- PersonalizationEntry.cs
- HtmlInputRadioButton.cs
- SystemInformation.cs
- DictionaryKeyPropertyAttribute.cs
- TraceSection.cs
- Literal.cs
- SqlParameterCollection.cs
- CategoryNameCollection.cs
- MaterialGroup.cs
- SimplePropertyEntry.cs
- Transform3D.cs
- CapabilitiesPattern.cs
- WebZoneDesigner.cs
- DecoderBestFitFallback.cs
- ComNativeDescriptor.cs
- PropertyItemInternal.cs
- Facet.cs
- SignedXmlDebugLog.cs
- HostingEnvironmentException.cs
- SizeAnimationClockResource.cs
- ValidationSummary.cs
- ModelVisual3D.cs
- RedistVersionInfo.cs
- Matrix3DConverter.cs
- LineMetrics.cs