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