1 //---------------------------------------------------------------------
2 // <copyright file="EntityConnection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 using System.Collections.Generic;
11 using System.Configuration;
12 using System.Data.Common;
13 using System.Data.Common.CommandTrees;
14 using System.Data.Common.EntitySql;
15 using System.Data.Entity;
16 using System.Data.Mapping;
17 using System.Data.Metadata.Edm;
18 using System.Diagnostics;
20 using System.Runtime.Versioning;
22 using System.Transactions;
24 namespace System.Data.EntityClient
27 /// Class representing a connection for the conceptual layer. An entity connection may only
28 /// be initialized once (by opening the connection). It is subsequently not possible to change
29 /// the connection string, attach a new store connection, or change the store connection string.
31 public sealed class EntityConnection : DbConnection
35 private const string s_metadataPathSeparator = "|";
36 private const string s_semicolonSeparator = ";";
37 private const string s_entityClientProviderName = "System.Data.EntityClient";
38 private const string s_providerInvariantName = "provider";
39 private const string s_providerConnectionString = "provider connection string";
40 private const string s_readerPrefix = "reader://";
41 internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed);
42 internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open);
45 private readonly object _connectionStringLock = new object();
46 private static readonly DbConnectionOptions s_emptyConnectionOptions = new DbConnectionOptions(String.Empty, null);
48 // The connection options object having the connection settings needed by this connection
49 private DbConnectionOptions _userConnectionOptions;
50 private DbConnectionOptions _effectiveConnectionOptions;
52 // The internal connection state of the entity client, which is distinct from that of the
53 // store connection it aggregates.
54 private ConnectionState _entityClientConnectionState = ConnectionState.Closed;
56 private DbProviderFactory _providerFactory;
57 private DbConnection _storeConnection;
58 private readonly bool _userOwnsStoreConnection;
59 private MetadataWorkspace _metadataWorkspace;
60 // DbTransaction started using BeginDbTransaction() method
61 private EntityTransaction _currentTransaction;
62 // Transaction the user enlisted in using EnlistTransaction() method
63 private Transaction _enlistedTransaction;
64 private bool _initialized;
65 // will only have a value while waiting for the ssdl to be loaded. we should
66 // never have a value for this when _initialized == true
67 private MetadataArtifactLoader _artifactLoader;
70 /// Constructs the EntityConnection object with a connection not yet associated to a particular store
72 [ResourceExposure(ResourceScope.None)] //We are not exposing any resource
73 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For EntityConnection constructor. But since the connection string we pass in is an Empty String,
74 //we consume the resource and do not expose it any further.
75 public EntityConnection()
81 /// Constructs the EntityConnection object with a connection string
83 /// <param name="connectionString">The connection string, may contain a list of settings for the connection or
84 /// just the name of the connection to use</param>
85 [ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
86 [ResourceConsumption(ResourceScope.Machine)] //For ChangeConnectionString method call. But the paths are not created in this method.
87 public EntityConnection(string connectionString)
89 GC.SuppressFinalize(this);
90 this.ChangeConnectionString(connectionString);
94 /// Constructs the EntityConnection from Metadata loaded in memory
96 /// <param name="workspace">Workspace containing metadata information.</param>
97 public EntityConnection(MetadataWorkspace workspace, DbConnection connection)
99 GC.SuppressFinalize(this);
101 EntityUtil.CheckArgumentNull(workspace, "workspace");
102 EntityUtil.CheckArgumentNull(connection, "connection");
105 if (!workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace))
107 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("EdmItemCollection"));
109 if(!workspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
111 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("StoreItemCollection"));
113 if(!workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSSpace))
115 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("StorageMappingItemCollection"));
118 if (connection.State != ConnectionState.Closed)
120 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ConnectionMustBeClosed);
123 // Verify that a factory can be retrieved
124 if (DbProviderFactories.GetFactory(connection) == null)
126 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_DbConnectionHasNoProvider(connection));
129 StoreItemCollection collection = (StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace);
131 _providerFactory = collection.StoreProviderFactory;
132 _storeConnection = connection;
133 _userOwnsStoreConnection = true;
134 _metadataWorkspace = workspace;
139 /// Get or set the entity connection string associated with this connection object
141 public override string ConnectionString
145 //EntityConnection created using MetadataWorkspace
146 // _userConnectionOptions is not null when empty Constructor is used
147 // Therefore it is sufficient to identify whether EC(MW, DbConnection) is used
148 if (this._userConnectionOptions == null)
150 Debug.Assert(_storeConnection != null);
152 string invariantName;
153 if (!EntityUtil.TryGetProviderInvariantName(DbProviderFactories.GetFactory(_storeConnection), out invariantName))
155 Debug.Fail("Provider Invariant Name not found");
159 return string.Format(Globalization.CultureInfo.InvariantCulture,
160 "{0}={3}{4};{1}={5};{2}=\"{6}\";",
161 EntityConnectionStringBuilder.MetadataParameterName,
162 s_providerInvariantName,
163 s_providerConnectionString,
165 _metadataWorkspace.MetadataWorkspaceId,
167 FormatProviderString(_storeConnection.ConnectionString));
170 string userConnectionString = this._userConnectionOptions.UsersConnectionString;
172 // In here, we ask the store connection for the connection string only if the user didn't specify a name
173 // connection (meaning effective connection options == user connection options). If the user specified a
174 // named connection, then we return just that. Otherwise, if the connection string is different from what
175 // we have in the connection options, which is possible if the store connection changed the connection
176 // string to hide the password, then we use the builder to reconstruct the string. The parameters will be
177 // shuffled, which is unavoidable but it's ok because the connection string cannot be the same as what the
178 // user originally passed in anyway. However, if the store connection string is still the same, then we
179 // simply return what the user originally passed in.
180 if (object.ReferenceEquals(_userConnectionOptions, _effectiveConnectionOptions) && this._storeConnection != null)
182 string storeConnectionString = null;
185 storeConnectionString = this._storeConnection.ConnectionString;
189 if (EntityUtil.IsCatchableExceptionType(e))
191 throw EntityUtil.Provider(@"ConnectionString", e);
196 // SQLBU 514721, 515024 - Defer connection string parsing to ConnectionStringBuilder
197 // if the 'userStoreConnectionString' and 'storeConnectionString' are unequal, except
198 // when they are both null or empty (we treat null and empty as equivalent here).
200 string userStoreConnectionString = this._userConnectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
201 if ((storeConnectionString != userStoreConnectionString)
202 && !(string.IsNullOrEmpty(storeConnectionString) && string.IsNullOrEmpty(userStoreConnectionString)))
204 // Feeds the connection string into the connection string builder, then plug in the provider connection string into
205 // the builder, and then extract the string from the builder
206 EntityConnectionStringBuilder connectionStringBuilder = new EntityConnectionStringBuilder(userConnectionString);
207 connectionStringBuilder.ProviderConnectionString = storeConnectionString;
208 return connectionStringBuilder.ConnectionString;
212 return userConnectionString;
214 [ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
215 [ResourceConsumption(ResourceScope.Machine)] //For ChangeConnectionString method call. But the paths are not created in this method.
218 ValidateChangesPermitted();
219 this.ChangeConnectionString(value);
224 /// Formats provider string to replace " with \" so it can be appended within quotation marks "..."
226 private static string FormatProviderString(string providerString)
228 return providerString.Trim().Replace("\"", "\\\"");
232 /// Get the time to wait when attempting to establish a connection before ending the try and generating an error
234 public override int ConnectionTimeout
238 if (this._storeConnection == null)
243 return this._storeConnection.ConnectionTimeout;
247 if (EntityUtil.IsCatchableExceptionType(e))
249 throw EntityUtil.Provider(@"ConnectionTimeout", e);
257 /// Get the name of the current database or the database that will be used after a connection is opened
259 public override string Database
268 /// Gets the ConnectionState property of the EntityConnection
270 public override ConnectionState State
276 if (this._entityClientConnectionState == ConnectionState.Open)
278 Debug.Assert(this.StoreConnection != null);
279 if (this.StoreConnection.State != ConnectionState.Open)
281 return ConnectionState.Broken;
284 return this._entityClientConnectionState;
288 if (EntityUtil.IsCatchableExceptionType(e))
290 throw EntityUtil.Provider(@"State", e);
298 /// Gets the name or network address of the data source to connect to
300 public override string DataSource
304 if (this._storeConnection == null)
309 return this._storeConnection.DataSource;
313 if (EntityUtil.IsCatchableExceptionType(e))
315 throw EntityUtil.Provider(@"DataSource", e);
323 /// Gets a string that contains the version of the data store to which the client is connected
325 public override string ServerVersion
329 if (this._storeConnection == null)
330 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
332 if (this.State != ConnectionState.Open)
334 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
339 return this._storeConnection.ServerVersion;
343 if (EntityUtil.IsCatchableExceptionType(e))
345 throw EntityUtil.Provider(@"ServerVersion", e);
353 /// Gets the provider factory associated with EntityConnection
355 override protected DbProviderFactory DbProviderFactory
359 return EntityProviderFactory.Instance;
364 /// Gets the DbProviderFactory for the underlying provider
366 internal DbProviderFactory StoreProviderFactory
370 return this._providerFactory;
375 /// Gets the DbConnection for the underlying provider connection
377 public DbConnection StoreConnection
381 return this._storeConnection;
386 /// Gets the metadata workspace used by this connection
388 [CLSCompliant(false)]
389 public MetadataWorkspace GetMetadataWorkspace()
391 return GetMetadataWorkspace(true /* initializeAllCollections */);
394 private bool ShouldRecalculateMetadataArtifactLoader(List<MetadataArtifactLoader> loaders)
396 if (loaders.Any(loader => loader.GetType() == typeof(MetadataArtifactLoaderCompositeFile)))
398 // the loaders had folders in it
401 // in the case that loaders only contains resources or file name, we trust the cache
405 [ResourceExposure(ResourceScope.None)] //The resource( path name) is not exposed to the callers of this method
406 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //Fir SplitPaths call and we pick the file names from class variable.
407 internal MetadataWorkspace GetMetadataWorkspace(bool initializeAllCollections)
409 Debug.Assert(_metadataWorkspace != null || _effectiveConnectionOptions != null, "The effective connection options is null, which should never be");
410 if (_metadataWorkspace == null ||
411 (initializeAllCollections && !_metadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace)))
413 // This lock is to ensure that the connection string and the metadata workspace are in a consistent state, that is, you
414 // don't get a metadata workspace not matching what's described by the connection string
415 lock (_connectionStringLock)
417 EdmItemCollection edmItemCollection = null;
418 if (_metadataWorkspace == null)
420 MetadataWorkspace workspace = new MetadataWorkspace();
421 List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
422 string paths = _effectiveConnectionOptions[EntityConnectionStringBuilder.MetadataParameterName];
424 if (!string.IsNullOrEmpty(paths))
426 loaders = MetadataCache.GetOrCreateMetdataArtifactLoader(paths);
428 if(!ShouldRecalculateMetadataArtifactLoader(loaders))
430 _artifactLoader = MetadataArtifactLoader.Create(loaders);
434 // the loaders contains folders that might get updated during runtime, so we have to recalculate the loaders again
435 _artifactLoader = MetadataArtifactLoader.Create(MetadataCache.SplitPaths(paths));
440 _artifactLoader = MetadataArtifactLoader.Create(loaders);
443 edmItemCollection = LoadEdmItemCollection(workspace, _artifactLoader);
444 _metadataWorkspace = workspace;
448 edmItemCollection = (EdmItemCollection)_metadataWorkspace.GetItemCollection(DataSpace.CSpace);
451 if (initializeAllCollections && !_metadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
453 LoadStoreItemCollections(_metadataWorkspace, _storeConnection, _providerFactory, _effectiveConnectionOptions, edmItemCollection, _artifactLoader);
454 _artifactLoader = null;
460 return _metadataWorkspace;
464 /// Gets the current transaction that this connection is enlisted in
466 internal EntityTransaction CurrentTransaction
470 // Null out the current transaction if the state is closed or zombied
471 if ((null != _currentTransaction) && ((null == _currentTransaction.StoreTransaction.Connection) || (this.State == ConnectionState.Closed)))
473 ClearCurrentTransaction();
475 return _currentTransaction;
480 /// Whether the user has enlisted in transaction using EnlistTransaction method
483 /// To avoid threading issues the <see cref="_enlistedTransaction"/> field is not reset when the transaction is completed.
484 /// Therefore it is possible that the transaction has completed but the field is not null. As a result we need to check
485 /// the actual transaction status. However it can happen that the transaction has already been disposed and trying to get
486 /// transaction status will cause ObjectDisposedException. This would also mean that the transaction has completed and can be reset.
488 internal bool EnlistedInUserTransaction
494 return _enlistedTransaction != null && _enlistedTransaction.TransactionInformation.Status == TransactionStatus.Active;
496 catch (ObjectDisposedException)
498 _enlistedTransaction = null;
505 /// Establish a connection to the data store by calling the Open method on the underlying data provider
507 public override void Open()
509 if (this._storeConnection == null)
510 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
512 if (this.State != ConnectionState.Closed)
514 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotReopenConnection);
517 bool closeStoreConnectionOnFailure = false;
518 OpenStoreConnectionIf(this._storeConnection.State != ConnectionState.Open,
519 this._storeConnection,
521 EntityRes.EntityClient_ProviderSpecificError,
523 ref closeStoreConnectionOnFailure);
525 // the following guards against the case when the user closes the underlying store connection
526 // in the state change event handler, as a consequence of which we are in the 'Broken' state
527 if (this._storeConnection == null || this._storeConnection.State != ConnectionState.Open)
529 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
532 InitializeMetadata(this._storeConnection, this._storeConnection, closeStoreConnectionOnFailure);
533 SetEntityClientConnectionStateToOpen();
537 /// Helper method that conditionally opens a specified store connection
539 /// <param name="openCondition">The condition to evaluate</param>
540 /// <param name="storeConnectionToOpen">The store connection to open</param>
541 /// <param name="originalConnection">The original store connection associated with the entity client</param>
542 /// <param name="closeStoreConnectionOnFailure">A flag that is set on if the connection is opened
543 /// successfully</param>
544 private void OpenStoreConnectionIf(bool openCondition,
545 DbConnection storeConnectionToOpen,
546 DbConnection originalConnection,
547 string exceptionCode,
548 string attemptedOperation,
549 ref bool closeStoreConnectionOnFailure)
555 storeConnectionToOpen.Open();
556 closeStoreConnectionOnFailure = true;
559 ResetStoreConnection(storeConnectionToOpen, originalConnection, false);
561 // With every successful open of the store connection, always null out the current db transaction and enlistedTransaction
566 if (EntityUtil.IsCatchableExceptionType(e))
568 string exceptionMessage = string.IsNullOrEmpty(attemptedOperation) ?
569 EntityRes.GetString(exceptionCode) :
570 EntityRes.GetString(exceptionCode, attemptedOperation);
572 throw EntityUtil.ProviderExceptionWithMessage(exceptionMessage, e);
579 /// Helper method to initialize the metadata workspace and reset the store connection
580 /// associated with the entity client
582 /// <param name="newConnection">The new connection to associate with the entity client</param>
583 /// <param name="originalConnection">The original connection associated with the entity client</param>
584 /// <param name="closeOriginalConnectionOnFailure">A flag to indicate whether the original
585 /// store connection needs to be closed on failure</param>
586 private void InitializeMetadata(DbConnection newConnection,
587 DbConnection originalConnection,
588 bool closeOriginalConnectionOnFailure)
592 // Ensure metadata is loaded and the workspace is appropriately initialized.
593 GetMetadataWorkspace();
597 // Undo the open if something failed
598 if (EntityUtil.IsCatchableExceptionType(ex))
600 ResetStoreConnection(newConnection, originalConnection, closeOriginalConnectionOnFailure);
607 /// Set the entity client connection state to Open, and raise an appropriate event
609 private void SetEntityClientConnectionStateToOpen()
611 this._entityClientConnectionState = ConnectionState.Open;
612 OnStateChange(StateChangeOpen);
616 /// This method sets the store connection and hooks up the event
618 /// <param name="newConnection">The DbConnection to set</param>
619 /// <param name="originalConnection">The original DbConnection to be closed - this argument could be null</param>
620 /// <param name="closeOriginalConnection">Indicates whether the original store connection should be closed</param>
621 private void ResetStoreConnection(DbConnection newConnection, DbConnection originalConnection, bool closeOriginalConnection)
623 this._storeConnection = newConnection;
625 if (closeOriginalConnection && (originalConnection != null))
627 originalConnection.Close();
632 /// Create a new command object that uses this connection object.
634 public new EntityCommand CreateCommand()
636 return new EntityCommand(null, this);
640 /// Create a new command object that uses this connection object
642 protected override DbCommand CreateDbCommand()
644 return this.CreateCommand();
648 /// Close the connection to the data store
650 public override void Close()
652 // It's a no-op if there isn't an underlying connection
653 if (this._storeConnection == null)
660 /// Changes the current database for this connection
662 /// <param name="databaseName">The name of the database to change to</param>
663 public override void ChangeDatabase(string databaseName)
665 throw EntityUtil.NotSupported();
669 /// Begins a database transaction
671 /// <returns>An object representing the new transaction</returns>
672 public new EntityTransaction BeginTransaction()
674 return base.BeginTransaction() as EntityTransaction;
678 /// Begins a database transaction
680 /// <param name="isolationLevel">The isolation level of the transaction</param>
681 /// <returns>An object representing the new transaction</returns>
682 public new EntityTransaction BeginTransaction(IsolationLevel isolationLevel)
684 return base.BeginTransaction(isolationLevel) as EntityTransaction;
688 /// Begins a database transaction
690 /// <param name="isolationLevel">The isolation level of the transaction</param>
691 /// <returns>An object representing the new transaction</returns>
692 protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
694 if (CurrentTransaction != null)
696 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_TransactionAlreadyStarted);
699 if (this._storeConnection == null)
700 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
702 if (this.State != ConnectionState.Open)
704 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
707 DbTransaction storeTransaction = null;
710 storeTransaction = this._storeConnection.BeginTransaction(isolationLevel);
714 if (EntityUtil.IsCatchableExceptionType(e))
716 throw EntityUtil.ProviderExceptionWithMessage(
717 System.Data.Entity.Strings.EntityClient_ErrorInBeginningTransaction,
724 // The provider is problematic if it succeeded in beginning a transaction but returned a null
725 // for the transaction object
726 if (storeTransaction == null)
728 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_ReturnedNullOnProviderMethod("BeginTransaction", _storeConnection.GetType().Name));
731 _currentTransaction = new EntityTransaction(this, storeTransaction);
732 return _currentTransaction;
736 /// Enlist in the given transaction
738 /// <param name="transaction">The transaction object to enlist into</param>
739 public override void EnlistTransaction(Transaction transaction)
741 if (_storeConnection == null)
742 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
744 if (this.State != ConnectionState.Open)
746 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
751 _storeConnection.EnlistTransaction(transaction);
753 // null means "Unenlist transaction". It is fine if no transaction is in progress (no op). Otherwise
754 // _storeConnection.EnlistTransaction should throw and we would not get here.
755 Debug.Assert(transaction != null || !EnlistedInUserTransaction,
756 "DbConnection should not allow unenlist from a transaction that has not completed.");
758 // It is OK to enlist in null transaction or multiple times in the same transaction.
759 // In the latter case we don't need to be called multiple times when the transaction completes
760 // so subscribe only when enlisting for the first time. Note that _storeConnection.EnlistTransaction
761 // will throw in invalid cases (like enlisting the connection in a transaction when another
762 // transaction has not completed) so when we get here we are sure that either no transactions are
763 // active or the transaction the caller tries enlisting to
764 // is the active transaction.
765 if (transaction != null && !EnlistedInUserTransaction)
767 transaction.TransactionCompleted += EnlistedTransactionCompleted;
770 _enlistedTransaction = transaction;
774 if (EntityUtil.IsCatchableExceptionType(e))
776 throw EntityUtil.Provider(@"EnlistTransaction", e);
783 /// Cleans up this connection object
785 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources</param>
786 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_currentTransaction")]
787 [ResourceExposure(ResourceScope.None)] //We are not exposing any resource
788 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For ChangeConnectionString method call. But since the connection string we pass in is an Empty String,
789 //we consume the resource and do not expose it any further.
790 protected override void Dispose(bool disposing)
792 // It is possible for the EntityConnection to be finalized even if the object was not actually
793 // created due to a "won't fix" bug in the x86 JITer--see Dev10 bug 892884.
794 // Even without this bug, a stack overflow trying to allocate space to run the constructor can
795 // result in effectively the same situation. This means we can end up finalizing objects that
796 // have not even been fully initialized. In order for this to work we have to be very careful
797 // what we do in Dispose and we need to stick rigidly to the "only dispose unmanaged resources
798 // if disposing is false" rule. We don't actually have any unmanaged resources--these are
799 // handled by the base class or other managed classes that we have references to. These classes
800 // will dispose of their unmanaged resources on finalize, so we shouldn't try to do it here.
804 bool raiseStateChangeEvent = EntityCloseHelper(false, this.State);
806 if (this._storeConnection != null)
808 StoreCloseHelper(); // closes store connection
809 if (this._storeConnection != null)
811 if (!this._userOwnsStoreConnection) // only dispose it if we didn't get it from the user...
813 this._storeConnection.Dispose();
815 this._storeConnection = null;
819 // Change the connection string to just an empty string, ChangeConnectionString should always succeed here,
820 // it's unnecessary to pass in the connection string parameter name in the second argument, which we don't
822 this.ChangeConnectionString(String.Empty);
824 if (raiseStateChangeEvent) // we need to raise the event explicitly
826 OnStateChange(StateChangeClosed);
829 base.Dispose(disposing);
833 /// Reinitialize this connection object to use the new connection string
835 /// <param name="newConnectionString">The new connection string</param>
836 [ResourceExposure(ResourceScope.Machine)] //Exposes the file names which are a Machine resource as part of the connection string
837 private void ChangeConnectionString(string newConnectionString)
839 DbConnectionOptions userConnectionOptions = s_emptyConnectionOptions;
840 if (!String.IsNullOrEmpty(newConnectionString))
842 userConnectionOptions = new DbConnectionOptions(newConnectionString, EntityConnectionStringBuilder.Synonyms);
845 DbProviderFactory factory = null;
846 DbConnection storeConnection = null;
847 DbConnectionOptions effectiveConnectionOptions = userConnectionOptions;
849 if (!userConnectionOptions.IsEmpty)
851 // Check if we have the named connection, if yes, then use the connection string from the configuration manager settings
852 string namedConnection = userConnectionOptions[EntityConnectionStringBuilder.NameParameterName];
853 if (!string.IsNullOrEmpty(namedConnection))
855 // There cannot be other parameters when the named connection is specified
856 if (1 < userConnectionOptions.Parsetable.Count)
858 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ExtraParametersWithNamedConnection);
861 // Find the named connection from the configuration, then extract the settings
862 ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings[namedConnection];
863 if (setting == null || setting.ProviderName != EntityConnection.s_entityClientProviderName)
865 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidNamedConnection);
868 effectiveConnectionOptions = new DbConnectionOptions(setting.ConnectionString, EntityConnectionStringBuilder.Synonyms);
870 // Check for a nested Name keyword
871 string nestedNamedConnection = effectiveConnectionOptions[EntityConnectionStringBuilder.NameParameterName];
872 if (!string.IsNullOrEmpty(nestedNamedConnection))
874 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_NestedNamedConnection(namedConnection));
878 //Validate the connection string has the required Keywords( Provider and Metadata)
879 //We trim the values for both the Keywords, so a string value with only spaces will throw an exception
880 //reporting back to the user that the Keyword was missing.
881 ValidateValueForTheKeyword(effectiveConnectionOptions, EntityConnectionStringBuilder.MetadataParameterName);
883 string providerName = ValidateValueForTheKeyword(effectiveConnectionOptions, EntityConnectionStringBuilder.ProviderParameterName);
884 // Get the correct provider factory
885 factory = GetFactory(providerName);
887 // Create the underlying provider specific connection and give it the connection string from the DbConnectionOptions object
888 storeConnection = GetStoreConnection(factory);
892 // When the value of 'Provider Connection String' is null, it means it has not been present in the entity connection string at all.
893 // Providers should still be able handle empty connection strings since those may be explicitly passed by clients.
894 string providerConnectionString = effectiveConnectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
895 if (providerConnectionString != null)
897 storeConnection.ConnectionString = providerConnectionString;
902 if (EntityUtil.IsCatchableExceptionType(e))
904 throw EntityUtil.Provider(@"ConnectionString", e);
910 // This lock is to ensure that the connection string matches with the provider connection and metadata workspace that's being
911 // managed by this EntityConnection, so states in this connection object are not messed up.
912 // It's not for security, but just to help reduce user error.
913 lock (_connectionStringLock)
915 // Now we have sufficient information and verified the configuration string is good, use them for this connection object
916 // Failure should not occur from this point to the end of this method
917 this._providerFactory = factory;
918 this._metadataWorkspace = null;
921 ResetStoreConnection(storeConnection, null, false);
923 // Remembers the connection options objects with the connection string set by the user
924 this._userConnectionOptions = userConnectionOptions;
925 this._effectiveConnectionOptions = effectiveConnectionOptions;
929 private static string ValidateValueForTheKeyword(DbConnectionOptions effectiveConnectionOptions,
932 string keywordValue = effectiveConnectionOptions[keywordName];
933 if (!string.IsNullOrEmpty(keywordValue))
934 keywordValue = keywordValue.Trim(); // be nice to user, always trim the value
936 // Check that we have a non-null and non-empty value for the keyword
937 if (string.IsNullOrEmpty(keywordValue))
939 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ConnectionStringMissingInfo(keywordName));
944 private static EdmItemCollection LoadEdmItemCollection(MetadataWorkspace workspace, MetadataArtifactLoader artifactLoader)
946 // Build a string as the key and look up the MetadataCache for a match
947 string edmCacheKey = CreateMetadataCacheKey(artifactLoader.GetOriginalPaths(DataSpace.CSpace), null, null);
949 // Check the MetadataCache for an entry with this key
951 EdmItemCollection edmItemCollection = MetadataCache.GetOrCreateEdmItemCollection(edmCacheKey,
954 workspace.RegisterItemCollection(edmItemCollection);
956 // Adding the edm metadata entry token to the workspace, to make sure that this token remains alive till workspace is alive
957 workspace.AddMetadataEntryToken(entryToken);
959 return edmItemCollection;
962 private static void LoadStoreItemCollections(MetadataWorkspace workspace,
963 DbConnection storeConnection,
964 DbProviderFactory factory,
965 DbConnectionOptions connectionOptions,
966 EdmItemCollection edmItemCollection,
967 MetadataArtifactLoader artifactLoader)
969 Debug.Assert(workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace), "C-Space must be loaded before loading S or C-S space");
971 // The provider connection string is optional; if it has not been specified,
972 // we pick up the store's connection string.
974 string providerConnectionString = connectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
975 if (string.IsNullOrEmpty(providerConnectionString) && (storeConnection != null))
977 providerConnectionString = storeConnection.ConnectionString;
980 // Build a string as the key and look up the MetadataCache for a match
981 string storeCacheKey = CreateMetadataCacheKey(artifactLoader.GetOriginalPaths(),
982 connectionOptions[EntityConnectionStringBuilder.ProviderParameterName],
983 providerConnectionString);
985 // Load store metadata.
987 StorageMappingItemCollection mappingCollection =
988 MetadataCache.GetOrCreateStoreAndMappingItemCollections(storeCacheKey,
993 workspace.RegisterItemCollection(mappingCollection.StoreItemCollection);
994 workspace.RegisterItemCollection(mappingCollection);
996 // Adding the store metadata entry token to the workspace
997 workspace.AddMetadataEntryToken(entryToken);
1000 private static string GetErrorMessageWorthyProviderName(DbProviderFactory factory)
1002 EntityUtil.CheckArgumentNull(factory, "factory");
1004 string providerName;
1005 if (!EntityUtil.TryGetProviderInvariantName(factory, out providerName))
1007 providerName = factory.GetType().FullName;
1009 return providerName;
1013 /// Create a key to be used with the MetadataCache from a connection options object
1015 /// <param name="paths">A list of metadata file paths</param>
1016 /// <param name="providerName">The provider name</param>
1017 /// <param name="providerConnectionString">The provider connection string</param>
1018 /// <returns>The key</returns>
1019 private static string CreateMetadataCacheKey(IList<string> paths, string providerName, string providerConnectionString)
1021 int resultCount = 0;
1024 // Do a first pass to calculate the output size of the metadata cache key,
1025 // then another pass to populate a StringBuilder with the exact size and
1027 CreateMetadataCacheKeyWithCount(paths, providerName, providerConnectionString,
1028 false, ref resultCount, out result);
1029 CreateMetadataCacheKeyWithCount(paths, providerName, providerConnectionString,
1030 true, ref resultCount, out result);
1036 /// Create a key to be used with the MetadataCache from a connection options
1039 /// <param name="paths">A list of metadata file paths</param>
1040 /// <param name="providerName">The provider name</param>
1041 /// <param name="providerConnectionString">The provider connection string</param>
1042 /// <param name="buildResult">Whether the result variable should be built.</param>
1043 /// <param name="resultCount">
1044 /// On entry, the expected size of the result (unused if buildResult is false).
1045 /// After execution, the effective result.</param>
1046 /// <param name="result">The key.</param>
1048 /// This method should be called once with buildResult=false, to get
1049 /// the size of the resulting key, and once with buildResult=true
1050 /// and the size specification.
1052 private static void CreateMetadataCacheKeyWithCount(IList<string> paths,
1053 string providerName, string providerConnectionString,
1054 bool buildResult, ref int resultCount, out string result)
1056 // Build a string as the key and look up the MetadataCache for a match
1057 StringBuilder keyString;
1060 keyString = new StringBuilder(resultCount);
1067 // At this point, we've already used resultCount. Reset it
1068 // to zero to make the final debug assertion that our computation
1072 if (!string.IsNullOrEmpty(providerName))
1074 resultCount += providerName.Length + 1;
1077 keyString.Append(providerName);
1078 keyString.Append(EntityConnection.s_semicolonSeparator);
1084 for (int i = 0; i < paths.Count; i++)
1086 if (paths[i].Length > 0)
1093 keyString.Append(EntityConnection.s_metadataPathSeparator);
1096 resultCount += paths[i].Length;
1099 keyString.Append(paths[i]);
1106 keyString.Append(EntityConnection.s_semicolonSeparator);
1110 if (!string.IsNullOrEmpty(providerConnectionString))
1112 resultCount += providerConnectionString.Length;
1115 keyString.Append(providerConnectionString);
1121 result = keyString.ToString();
1128 System.Diagnostics.Debug.Assert(
1129 !buildResult || (result.Length == resultCount));
1133 /// Clears the current DbTransaction and the transaction the user enlisted the connection in
1134 /// with EnlistTransaction() method.
1136 private void ClearTransactions()
1138 ClearCurrentTransaction();
1139 ClearEnlistedTransaction();
1143 /// Clears the current DbTransaction for this connection
1145 internal void ClearCurrentTransaction()
1147 _currentTransaction = null;
1151 /// Clears the transaction the user elinsted in using EnlistTransaction() method.
1153 private void ClearEnlistedTransaction()
1155 if (EnlistedInUserTransaction)
1157 _enlistedTransaction.TransactionCompleted -= EnlistedTransactionCompleted;
1160 _enlistedTransaction = null;
1164 /// Event handler invoked when the transaction has completed (either by committing or rolling back).
1166 /// <param name="sender">The source of the event.</param>
1167 /// <param name="e">The <see cref="TransactionEventArgs"/> that contains the event data.</param>
1168 /// <remarks>Note that to avoid threading issues we never reset the <see cref=" _enlistedTransaction"/> field here.</remarks>
1169 private void EnlistedTransactionCompleted(object sender, TransactionEventArgs e)
1171 e.Transaction.TransactionCompleted -= EnlistedTransactionCompleted;
1175 /// Helper method invoked as part of Close()/Dispose() that releases the underlying
1176 /// store connection and raises the appropriate event.
1178 private void CloseHelper()
1180 ConnectionState previousState = this.State; // the public connection state before cleanup
1183 true, // raise the state change event
1189 /// Store-specific helper method invoked as part of Close()/Dispose().
1191 private void StoreCloseHelper()
1195 if (this._storeConnection != null && (this._storeConnection.State != ConnectionState.Closed))
1197 this._storeConnection.Close();
1199 // Need to disassociate the transaction objects with this connection
1200 ClearTransactions();
1204 if (EntityUtil.IsCatchableExceptionType(e))
1206 throw EntityUtil.ProviderExceptionWithMessage(
1207 System.Data.Entity.Strings.EntityClient_ErrorInClosingConnection,
1216 /// Entity-specific helper method invoked as part of Close()/Dispose().
1218 /// <param name="fireEventOnStateChange">Indicates whether we need to raise the state change event here</param>
1219 /// <param name="previousState">The public state of the connection before cleanup began</param>
1220 /// <returns>true if the caller needs to raise the state change event</returns>
1221 private bool EntityCloseHelper(bool fireEventOnStateChange, ConnectionState previousState)
1223 bool result = false;
1225 this._entityClientConnectionState = ConnectionState.Closed;
1227 if (previousState == ConnectionState.Open)
1229 if (fireEventOnStateChange)
1231 OnStateChange(StateChangeClosed);
1235 result = true; // we didn't raise the event here; the caller should do that
1243 /// Call to determine if changes to the entity object are currently permitted.
1245 private void ValidateChangesPermitted()
1249 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_SettingsCannotBeChangedOnOpenConnection);
1254 /// Returns the DbProviderFactory associated with specified provider string
1256 private DbProviderFactory GetFactory(string providerString)
1260 return DbProviderFactories.GetFactory(providerString);
1262 catch (ArgumentException e)
1264 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidStoreProvider, e);
1269 /// Uses DbProviderFactory to create a DbConnection
1271 private DbConnection GetStoreConnection(DbProviderFactory factory)
1273 DbConnection storeConnection = factory.CreateConnection();
1274 if (storeConnection == null)
1276 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_ReturnedNullOnProviderMethod("CreateConnection", factory.GetType().Name));
1278 return storeConnection;