Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityClient / EntityConnection.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EntityConnection.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
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;
19 using System.Linq;
20 using System.Runtime.Versioning;
21 using System.Text;
22 using System.Transactions;
23
24 namespace System.Data.EntityClient
25 {
26     /// <summary>
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.
30     /// </summary>
31     public sealed class EntityConnection : DbConnection
32     {
33         #region Constants
34
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);
43         #endregion
44
45         private readonly object _connectionStringLock = new object();
46         private static readonly DbConnectionOptions s_emptyConnectionOptions = new DbConnectionOptions(String.Empty, null);
47
48         // The connection options object having the connection settings needed by this connection
49         private DbConnectionOptions _userConnectionOptions;
50         private DbConnectionOptions _effectiveConnectionOptions;
51
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;
55
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;
68
69         /// <summary>
70         /// Constructs the EntityConnection object with a connection not yet associated to a particular store
71         /// </summary>
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()
76             : this(String.Empty)
77         {
78         }
79
80         /// <summary>
81         /// Constructs the EntityConnection object with a connection string
82         /// </summary>
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)
88         {
89             GC.SuppressFinalize(this);
90             this.ChangeConnectionString(connectionString);
91         }
92
93         /// <summary>
94         /// Constructs the EntityConnection from Metadata loaded in memory
95         /// </summary>
96         /// <param name="workspace">Workspace containing metadata information.</param>
97         public EntityConnection(MetadataWorkspace workspace, DbConnection connection)
98         {
99             GC.SuppressFinalize(this);
100
101             EntityUtil.CheckArgumentNull(workspace, "workspace");
102             EntityUtil.CheckArgumentNull(connection, "connection");
103             
104
105             if (!workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace))
106             {
107                 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("EdmItemCollection"));
108             }
109             if(!workspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
110             {
111                 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("StoreItemCollection"));
112             }
113             if(!workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSSpace))
114             {
115                 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ItemCollectionsNotRegisteredInWorkspace("StorageMappingItemCollection"));
116             }
117
118             if (connection.State != ConnectionState.Closed)
119             {
120                 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ConnectionMustBeClosed);
121             }
122
123             // Verify that a factory can be retrieved
124             if (DbProviderFactories.GetFactory(connection) == null)
125             {
126                 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_DbConnectionHasNoProvider(connection));
127             }
128
129             StoreItemCollection collection = (StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace);
130
131             _providerFactory = collection.StoreProviderFactory;
132             _storeConnection = connection;
133             _userOwnsStoreConnection = true;
134             _metadataWorkspace = workspace;
135             _initialized = true;
136         }
137         
138         /// <summary>
139         /// Get or set the entity connection string associated with this connection object
140         /// </summary>
141         public override string ConnectionString
142         {
143             get
144             {
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)
149                 {
150                     Debug.Assert(_storeConnection != null);
151
152                     string invariantName;
153                     if (!EntityUtil.TryGetProviderInvariantName(DbProviderFactories.GetFactory(_storeConnection), out invariantName))
154                     {
155                         Debug.Fail("Provider Invariant Name not found");
156                         invariantName = "";
157                     }
158
159                     return string.Format(Globalization.CultureInfo.InvariantCulture,
160                         "{0}={3}{4};{1}={5};{2}=\"{6}\";",
161                         EntityConnectionStringBuilder.MetadataParameterName,
162                         s_providerInvariantName,
163                         s_providerConnectionString,
164                         s_readerPrefix,
165                         _metadataWorkspace.MetadataWorkspaceId,
166                         invariantName,
167                         FormatProviderString(_storeConnection.ConnectionString));
168                 }
169
170                 string userConnectionString = this._userConnectionOptions.UsersConnectionString;
171
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)
181                 {
182                     string storeConnectionString = null;
183                     try
184                     {
185                         storeConnectionString = this._storeConnection.ConnectionString;
186                     }
187                     catch (Exception e)
188                     {
189                         if (EntityUtil.IsCatchableExceptionType(e))
190                         {
191                             throw EntityUtil.Provider(@"ConnectionString", e);
192                         }
193                         throw;
194                     }
195
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).
199                     //
200                     string userStoreConnectionString = this._userConnectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
201                     if ((storeConnectionString != userStoreConnectionString)
202                         && !(string.IsNullOrEmpty(storeConnectionString) && string.IsNullOrEmpty(userStoreConnectionString)))
203                     {
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;
209                     }
210                 }
211
212                 return userConnectionString;
213             }
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.
216             set
217             {
218                 ValidateChangesPermitted();
219                 this.ChangeConnectionString(value);
220             }
221         }
222
223         /// <summary>
224         /// Formats provider string to replace " with \" so it can be appended within quotation marks "..."
225         /// </summary>
226         private static string FormatProviderString(string providerString)
227         {
228             return providerString.Trim().Replace("\"", "\\\"");
229         }
230
231         /// <summary>
232         /// Get the time to wait when attempting to establish a connection before ending the try and generating an error
233         /// </summary>
234         public override int ConnectionTimeout
235         {
236             get
237             {
238                 if (this._storeConnection == null)
239                     return 0;
240
241                 try
242                 {
243                     return this._storeConnection.ConnectionTimeout;
244                 }
245                 catch (Exception e)
246                 {
247                     if (EntityUtil.IsCatchableExceptionType(e))
248                     {
249                         throw EntityUtil.Provider(@"ConnectionTimeout", e);
250                     }
251                     throw;
252                 }
253             }
254         }
255
256         /// <summary>
257         /// Get the name of the current database or the database that will be used after a connection is opened
258         /// </summary>
259         public override string Database
260         {
261             get
262             {
263                 return String.Empty;
264             }
265         }
266
267         /// <summary>
268         /// Gets the ConnectionState property of the EntityConnection
269         /// </summary>
270         public override ConnectionState State
271         {
272             get
273             {
274                 try
275                 {
276                     if (this._entityClientConnectionState == ConnectionState.Open)
277                     {
278                         Debug.Assert(this.StoreConnection != null);
279                         if (this.StoreConnection.State != ConnectionState.Open)
280                         {
281                             return ConnectionState.Broken;
282                         }
283                     }
284                     return this._entityClientConnectionState;
285                 }
286                 catch (Exception e)
287                 {
288                     if (EntityUtil.IsCatchableExceptionType(e))
289                     {
290                         throw EntityUtil.Provider(@"State", e);
291                     }
292                     throw;
293                 }
294             }
295         }
296
297         /// <summary>
298         /// Gets the name or network address of the data source to connect to
299         /// </summary>
300         public override string DataSource
301         {
302             get
303             {
304                 if (this._storeConnection == null)
305                     return String.Empty;
306
307                 try
308                 {
309                     return this._storeConnection.DataSource;
310                 }
311                 catch (Exception e)
312                 {
313                     if (EntityUtil.IsCatchableExceptionType(e))
314                     {
315                         throw EntityUtil.Provider(@"DataSource", e);
316                     }
317                     throw;
318                 }
319             }
320         }
321
322         /// <summary>
323         /// Gets a string that contains the version of the data store to which the client is connected
324         /// </summary>
325         public override string ServerVersion
326         {
327             get
328             {
329                 if (this._storeConnection == null)
330                     throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
331
332                 if (this.State != ConnectionState.Open)
333                 {
334                     throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
335                 }
336
337                 try
338                 {
339                     return this._storeConnection.ServerVersion;
340                 }
341                 catch (Exception e)
342                 {
343                     if (EntityUtil.IsCatchableExceptionType(e))
344                     {
345                         throw EntityUtil.Provider(@"ServerVersion", e);
346                     }
347                     throw;
348                 }
349             }
350         }
351
352         /// <summary>
353         /// Gets the provider factory associated with EntityConnection
354         /// </summary>
355         override protected DbProviderFactory DbProviderFactory
356         {
357             get
358             {
359                 return EntityProviderFactory.Instance;
360             }
361         }
362
363         /// <summary>
364         /// Gets the DbProviderFactory for the underlying provider
365         /// </summary>
366         internal DbProviderFactory StoreProviderFactory
367         {
368             get
369             {
370                 return this._providerFactory;
371             }
372         }
373
374         /// <summary>
375         /// Gets the DbConnection for the underlying provider connection
376         /// </summary>
377         public DbConnection StoreConnection
378         {
379             get
380             {
381                 return this._storeConnection;
382             }
383         }
384
385         /// <summary>
386         /// Gets the metadata workspace used by this connection
387         /// </summary>
388         [CLSCompliant(false)]
389         public MetadataWorkspace GetMetadataWorkspace()
390         {
391             return GetMetadataWorkspace(true /* initializeAllCollections */);
392         }
393
394         private bool ShouldRecalculateMetadataArtifactLoader(List<MetadataArtifactLoader> loaders)
395         {
396             if (loaders.Any(loader => loader.GetType() == typeof(MetadataArtifactLoaderCompositeFile)))
397             {
398                 // the loaders had folders in it
399                 return true;
400             }
401             // in the case that loaders only contains resources or file name, we trust the cache
402             return false;
403         }
404
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)
408         {
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)))
412             {
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)
416                 {
417                     EdmItemCollection edmItemCollection = null;
418                     if (_metadataWorkspace == null)
419                     {
420                         MetadataWorkspace workspace = new MetadataWorkspace();
421                         List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
422                         string paths = _effectiveConnectionOptions[EntityConnectionStringBuilder.MetadataParameterName];
423
424                         if (!string.IsNullOrEmpty(paths))
425                         {
426                             loaders = MetadataCache.GetOrCreateMetdataArtifactLoader(paths);
427                             
428                             if(!ShouldRecalculateMetadataArtifactLoader(loaders))
429                             {
430                                 _artifactLoader = MetadataArtifactLoader.Create(loaders);
431                             }
432                             else
433                             {
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));
436                             }
437                         }
438                         else
439                         {
440                             _artifactLoader = MetadataArtifactLoader.Create(loaders);
441                         }
442
443                         edmItemCollection = LoadEdmItemCollection(workspace, _artifactLoader);
444                         _metadataWorkspace = workspace;
445                     }
446                     else
447                     {
448                         edmItemCollection = (EdmItemCollection)_metadataWorkspace.GetItemCollection(DataSpace.CSpace);
449                     }
450
451                     if (initializeAllCollections && !_metadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
452                     {
453                         LoadStoreItemCollections(_metadataWorkspace, _storeConnection, _providerFactory, _effectiveConnectionOptions, edmItemCollection, _artifactLoader);
454                         _artifactLoader = null;
455                         _initialized = true;
456                     }
457                 }
458             }
459
460             return _metadataWorkspace;
461         }
462
463         /// <summary>
464         /// Gets the current transaction that this connection is enlisted in
465         /// </summary>
466         internal EntityTransaction CurrentTransaction
467         {
468             get
469             {
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)))
472                 {
473                     ClearCurrentTransaction();
474                 }
475                 return _currentTransaction;
476             }
477         }
478
479         /// <summary>
480         /// Whether the user has enlisted in transaction using EnlistTransaction method
481         /// </summary>
482         /// <remarks>
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.
487         /// </remarks>
488         internal bool EnlistedInUserTransaction
489         {
490             get
491             {
492                 try
493                 {
494                     return _enlistedTransaction != null && _enlistedTransaction.TransactionInformation.Status == TransactionStatus.Active;
495                 }
496                 catch (ObjectDisposedException)
497                 {
498                     _enlistedTransaction = null;
499                     return false;
500                 }
501             }
502         }
503
504         /// <summary>
505         /// Establish a connection to the data store by calling the Open method on the underlying data provider
506         /// </summary>
507         public override void Open()
508         {
509             if (this._storeConnection == null)
510                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
511
512             if (this.State != ConnectionState.Closed)
513             {
514                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotReopenConnection);
515             }
516
517             bool closeStoreConnectionOnFailure = false;
518             OpenStoreConnectionIf(this._storeConnection.State != ConnectionState.Open,
519                                   this._storeConnection,
520                                   null,
521                                   EntityRes.EntityClient_ProviderSpecificError,
522                                   @"Open",
523                                   ref closeStoreConnectionOnFailure);
524
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)
528             {
529                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
530             }
531
532             InitializeMetadata(this._storeConnection, this._storeConnection, closeStoreConnectionOnFailure);
533             SetEntityClientConnectionStateToOpen();
534         }
535
536         /// <summary>
537         /// Helper method that conditionally opens a specified store connection
538         /// </summary>
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)
550         {
551             try
552             {
553                 if (openCondition)
554                 {
555                     storeConnectionToOpen.Open();
556                     closeStoreConnectionOnFailure = true;
557                 }
558
559                 ResetStoreConnection(storeConnectionToOpen, originalConnection, false);
560
561                 // With every successful open of the store connection, always null out the current db transaction and enlistedTransaction
562                 ClearTransactions();
563             }
564             catch (Exception e)
565             {
566                 if (EntityUtil.IsCatchableExceptionType(e))
567                 {
568                     string exceptionMessage = string.IsNullOrEmpty(attemptedOperation) ?
569                         EntityRes.GetString(exceptionCode) :
570                         EntityRes.GetString(exceptionCode, attemptedOperation);
571
572                     throw EntityUtil.ProviderExceptionWithMessage(exceptionMessage, e);
573                 }
574                 throw;
575             }
576         }
577
578         /// <summary>
579         /// Helper method to initialize the metadata workspace and reset the store connection
580         /// associated with the entity client
581         /// </summary>
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)
589         {
590             try
591             {
592                 // Ensure metadata is loaded and the workspace is appropriately initialized.
593                 GetMetadataWorkspace();
594             }
595             catch (Exception ex)
596             {
597                 // Undo the open if something failed
598                 if (EntityUtil.IsCatchableExceptionType(ex))
599                 {
600                     ResetStoreConnection(newConnection, originalConnection, closeOriginalConnectionOnFailure);
601                 }
602                 throw;
603             }
604         }
605                 
606         /// <summary>
607         /// Set the entity client connection state to Open, and raise an appropriate event
608         /// </summary>
609         private void SetEntityClientConnectionStateToOpen()
610         {
611             this._entityClientConnectionState = ConnectionState.Open;
612             OnStateChange(StateChangeOpen);
613         }
614
615         /// <summary>
616         /// This method sets the store connection and hooks up the event
617         /// </summary>
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)
622         {
623             this._storeConnection = newConnection;
624
625             if (closeOriginalConnection && (originalConnection != null))
626             {
627                 originalConnection.Close();
628             }
629         }
630
631         /// <summary>
632         /// Create a new command object that uses this connection object.
633         /// </summary>
634         public new EntityCommand CreateCommand()
635         {
636             return new EntityCommand(null, this);
637         }
638
639         /// <summary>
640         /// Create a new command object that uses this connection object
641         /// </summary>
642         protected override DbCommand CreateDbCommand()
643         {
644             return this.CreateCommand();
645         }
646
647         /// <summary>
648         /// Close the connection to the data store
649         /// </summary>
650         public override void Close()
651         {
652             // It's a no-op if there isn't an underlying connection
653             if (this._storeConnection == null)
654                 return;
655
656             this.CloseHelper();
657         }
658
659         /// <summary>
660         /// Changes the current database for this connection
661         /// </summary>
662         /// <param name="databaseName">The name of the database to change to</param>
663         public override void ChangeDatabase(string databaseName)
664         {
665             throw EntityUtil.NotSupported();
666         }
667
668         /// <summary>
669         /// Begins a database transaction
670         /// </summary>
671         /// <returns>An object representing the new transaction</returns>
672         public new EntityTransaction BeginTransaction()
673         {
674             return base.BeginTransaction() as EntityTransaction;
675         }
676
677         /// <summary>
678         /// Begins a database transaction
679         /// </summary>
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)
683         {
684             return base.BeginTransaction(isolationLevel) as EntityTransaction;
685         }
686
687         /// <summary>
688         /// Begins a database transaction
689         /// </summary>
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)
693         {
694             if (CurrentTransaction != null)
695             {
696                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_TransactionAlreadyStarted);
697             }
698
699             if (this._storeConnection == null)
700                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
701
702             if (this.State != ConnectionState.Open)
703             {
704                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
705             }
706
707             DbTransaction storeTransaction = null;
708             try
709             {
710                 storeTransaction = this._storeConnection.BeginTransaction(isolationLevel);
711             }
712             catch (Exception e)
713             {
714                 if (EntityUtil.IsCatchableExceptionType(e))
715                 {
716                     throw EntityUtil.ProviderExceptionWithMessage(
717                                             System.Data.Entity.Strings.EntityClient_ErrorInBeginningTransaction,
718                                             e
719                                         );
720                 }
721                 throw;
722             }
723
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)
727             {
728                 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_ReturnedNullOnProviderMethod("BeginTransaction", _storeConnection.GetType().Name));
729             }
730
731             _currentTransaction = new EntityTransaction(this, storeTransaction);
732             return _currentTransaction;
733         }
734
735         /// <summary>
736         /// Enlist in the given transaction
737         /// </summary>
738         /// <param name="transaction">The transaction object to enlist into</param>
739         public override void EnlistTransaction(Transaction transaction)
740         {
741             if (_storeConnection == null)
742                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
743
744             if (this.State != ConnectionState.Open)
745             {
746                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionNotOpen);
747             }
748
749             try
750             {
751                 _storeConnection.EnlistTransaction(transaction);
752
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.");
757
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)
766                 {
767                     transaction.TransactionCompleted += EnlistedTransactionCompleted;
768                 }
769
770                 _enlistedTransaction = transaction;
771             }
772             catch (Exception e)
773             {
774                 if (EntityUtil.IsCatchableExceptionType(e))
775                 {
776                     throw EntityUtil.Provider(@"EnlistTransaction", e);
777                 }
778                 throw;
779             }
780         }
781
782         /// <summary>
783         /// Cleans up this connection object
784         /// </summary>
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)
791         {
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.
801             if (disposing)
802             {
803                 ClearTransactions();
804                 bool raiseStateChangeEvent = EntityCloseHelper(false, this.State);
805
806                 if (this._storeConnection != null)
807                 {
808                     StoreCloseHelper(); // closes store connection
809                     if (this._storeConnection != null)
810                     {
811                         if (!this._userOwnsStoreConnection)  // only dispose it if we didn't get it from the user...
812                         {
813                             this._storeConnection.Dispose();
814                         }
815                         this._storeConnection = null;
816                     }
817                 }
818
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
821                 // have anyway
822                 this.ChangeConnectionString(String.Empty);
823
824                 if (raiseStateChangeEvent)  // we need to raise the event explicitly
825                 {
826                     OnStateChange(StateChangeClosed);
827                 }
828             }
829             base.Dispose(disposing);
830         }
831
832         /// <summary>
833         /// Reinitialize this connection object to use the new connection string
834         /// </summary>
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)
838         {
839             DbConnectionOptions userConnectionOptions = s_emptyConnectionOptions;
840             if (!String.IsNullOrEmpty(newConnectionString))
841             {
842                 userConnectionOptions = new DbConnectionOptions(newConnectionString, EntityConnectionStringBuilder.Synonyms);
843             }
844
845             DbProviderFactory factory = null;
846             DbConnection storeConnection = null;
847             DbConnectionOptions effectiveConnectionOptions = userConnectionOptions;
848
849             if (!userConnectionOptions.IsEmpty)
850             {
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))
854                 {
855                     // There cannot be other parameters when the named connection is specified
856                     if (1 < userConnectionOptions.Parsetable.Count)
857                     {
858                         throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ExtraParametersWithNamedConnection);
859                     }
860
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)
864                     {
865                         throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidNamedConnection);
866                     }
867
868                     effectiveConnectionOptions = new DbConnectionOptions(setting.ConnectionString, EntityConnectionStringBuilder.Synonyms);
869
870                     // Check for a nested Name keyword
871                     string nestedNamedConnection = effectiveConnectionOptions[EntityConnectionStringBuilder.NameParameterName];
872                     if (!string.IsNullOrEmpty(nestedNamedConnection))
873                     {
874                         throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_NestedNamedConnection(namedConnection));
875                     }
876                 }
877
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);
882
883                 string providerName = ValidateValueForTheKeyword(effectiveConnectionOptions, EntityConnectionStringBuilder.ProviderParameterName);
884                 // Get the correct provider factory
885                 factory = GetFactory(providerName);
886
887                 // Create the underlying provider specific connection and give it the connection string from the DbConnectionOptions object
888                 storeConnection = GetStoreConnection(factory);
889
890                 try
891                 {
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)
896                     {
897                         storeConnection.ConnectionString = providerConnectionString;
898                     }
899                 }
900                 catch (Exception e)
901                 {
902                     if (EntityUtil.IsCatchableExceptionType(e))
903                     {
904                         throw EntityUtil.Provider(@"ConnectionString", e);
905                     }
906                     throw;
907                 }
908             }
909             
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)
914             {
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;
919
920                 ClearTransactions();
921                 ResetStoreConnection(storeConnection, null, false);
922
923                 // Remembers the connection options objects with the connection string set by the user
924                 this._userConnectionOptions = userConnectionOptions;
925                 this._effectiveConnectionOptions = effectiveConnectionOptions;
926             }
927         }
928
929         private static string ValidateValueForTheKeyword(DbConnectionOptions effectiveConnectionOptions,
930             string keywordName)
931         {
932             string keywordValue = effectiveConnectionOptions[keywordName];
933             if (!string.IsNullOrEmpty(keywordValue))
934                 keywordValue = keywordValue.Trim(); // be nice to user, always trim the value
935
936             // Check that we have a non-null and non-empty value for the keyword
937             if (string.IsNullOrEmpty(keywordValue))
938             {
939                 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_ConnectionStringMissingInfo(keywordName));
940             }
941             return keywordValue;
942         }
943
944         private static EdmItemCollection LoadEdmItemCollection(MetadataWorkspace workspace, MetadataArtifactLoader artifactLoader)
945         {
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);
948
949             // Check the MetadataCache for an entry with this key
950             object entryToken;
951             EdmItemCollection edmItemCollection = MetadataCache.GetOrCreateEdmItemCollection(edmCacheKey,
952                                                                                         artifactLoader,
953                                                                                         out entryToken);
954             workspace.RegisterItemCollection(edmItemCollection);
955
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);
958
959             return edmItemCollection;
960         }
961
962         private static void LoadStoreItemCollections(MetadataWorkspace workspace,
963                                                      DbConnection storeConnection,
964                                                      DbProviderFactory factory,
965                                                      DbConnectionOptions connectionOptions,
966                                                      EdmItemCollection edmItemCollection,
967                                                      MetadataArtifactLoader artifactLoader)                                                     
968         {
969             Debug.Assert(workspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace), "C-Space must be loaded before loading S or C-S space");
970                         
971             // The provider connection string is optional; if it has not been specified,
972             // we pick up the store's connection string.
973             //
974             string providerConnectionString = connectionOptions[EntityConnectionStringBuilder.ProviderConnectionStringParameterName];
975             if (string.IsNullOrEmpty(providerConnectionString) && (storeConnection != null))
976             {
977                 providerConnectionString = storeConnection.ConnectionString;
978             }
979
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);
984
985             // Load store metadata.
986             object entryToken;
987             StorageMappingItemCollection mappingCollection =
988                 MetadataCache.GetOrCreateStoreAndMappingItemCollections(storeCacheKey,
989                                                                      artifactLoader,
990                                                                      edmItemCollection,
991                                                                      out entryToken);
992
993             workspace.RegisterItemCollection(mappingCollection.StoreItemCollection);
994             workspace.RegisterItemCollection(mappingCollection);
995
996             // Adding the store metadata entry token to the workspace
997             workspace.AddMetadataEntryToken(entryToken);
998         }
999
1000         private static string GetErrorMessageWorthyProviderName(DbProviderFactory factory)
1001         {
1002             EntityUtil.CheckArgumentNull(factory, "factory");
1003
1004             string providerName;
1005             if (!EntityUtil.TryGetProviderInvariantName(factory, out providerName))
1006             {
1007                 providerName = factory.GetType().FullName;
1008             }
1009             return providerName;
1010         }
1011                 
1012         /// <summary>
1013         /// Create a key to be used with the MetadataCache from a connection options object
1014         /// </summary>
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)
1020         {
1021             int resultCount = 0;
1022             string result;
1023
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
1026             // get the result.
1027             CreateMetadataCacheKeyWithCount(paths, providerName, providerConnectionString,
1028                 false, ref resultCount, out result);
1029             CreateMetadataCacheKeyWithCount(paths, providerName, providerConnectionString,
1030                 true, ref resultCount, out result);
1031
1032             return result;
1033         }
1034
1035         /// <summary>
1036         /// Create a key to be used with the MetadataCache from a connection options 
1037         /// object.
1038         /// </summary>
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>
1047         /// <remarks>
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.
1051         /// </remarks>
1052         private static void CreateMetadataCacheKeyWithCount(IList<string> paths, 
1053             string providerName, string providerConnectionString,
1054             bool buildResult, ref int resultCount, out string result)
1055         {
1056             // Build a string as the key and look up the MetadataCache for a match
1057             StringBuilder keyString;
1058             if (buildResult)
1059             {
1060                 keyString = new StringBuilder(resultCount);
1061             }
1062             else
1063             {
1064                 keyString = null;
1065             }
1066
1067             // At this point, we've already used resultCount. Reset it
1068             // to zero to make the final debug assertion that our computation
1069             // is correct.
1070             resultCount = 0;
1071
1072             if (!string.IsNullOrEmpty(providerName))
1073             {
1074                 resultCount += providerName.Length + 1;
1075                 if (buildResult)
1076                 {
1077                     keyString.Append(providerName);
1078                     keyString.Append(EntityConnection.s_semicolonSeparator);
1079                 }
1080             }
1081
1082             if (paths != null)
1083             {
1084                 for (int i = 0; i < paths.Count; i++)
1085                 {
1086                     if (paths[i].Length > 0)
1087                     {
1088                         if (i > 0)
1089                         {
1090                             resultCount++;
1091                             if (buildResult)
1092                             {
1093                                 keyString.Append(EntityConnection.s_metadataPathSeparator);
1094                             }
1095                         }
1096                         resultCount += paths[i].Length;
1097                         if (buildResult)
1098                         {
1099                             keyString.Append(paths[i]);
1100                         }
1101                     }
1102                 }
1103                 resultCount++;
1104                 if (buildResult)
1105                 {
1106                     keyString.Append(EntityConnection.s_semicolonSeparator);
1107                 }
1108             }
1109
1110             if (!string.IsNullOrEmpty(providerConnectionString))
1111             {
1112                 resultCount += providerConnectionString.Length;
1113                 if (buildResult)
1114                 {
1115                     keyString.Append(providerConnectionString);
1116                 }
1117             }
1118
1119             if (buildResult)
1120             {
1121                 result = keyString.ToString();
1122             }
1123             else
1124             {
1125                 result = null;
1126             }
1127
1128             System.Diagnostics.Debug.Assert(
1129                 !buildResult || (result.Length == resultCount));
1130         }
1131
1132         /// <summary>
1133         /// Clears the current DbTransaction and the transaction the user enlisted the connection in 
1134         /// with EnlistTransaction() method.
1135         /// </summary>
1136         private void ClearTransactions()
1137         {
1138             ClearCurrentTransaction();
1139             ClearEnlistedTransaction();
1140         }
1141
1142         /// <summary>
1143         /// Clears the current DbTransaction for this connection
1144         /// </summary>
1145         internal void ClearCurrentTransaction()
1146         {
1147             _currentTransaction = null;
1148         }
1149
1150         /// <summary>
1151         /// Clears the transaction the user elinsted in using EnlistTransaction() method.
1152         /// </summary>
1153         private void ClearEnlistedTransaction()
1154         {
1155             if (EnlistedInUserTransaction)
1156             {
1157                 _enlistedTransaction.TransactionCompleted -= EnlistedTransactionCompleted;
1158             }
1159
1160             _enlistedTransaction = null;
1161         }
1162
1163         /// <summary>
1164         /// Event handler invoked when the transaction has completed (either by committing or rolling back).
1165         /// </summary>
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)
1170         {
1171             e.Transaction.TransactionCompleted -= EnlistedTransactionCompleted;
1172         }
1173
1174         /// <summary>
1175         /// Helper method invoked as part of Close()/Dispose() that releases the underlying
1176         /// store connection and raises the appropriate event.
1177         /// </summary>
1178         private void CloseHelper()
1179         {
1180             ConnectionState previousState = this.State; // the public connection state before cleanup
1181             StoreCloseHelper();
1182             EntityCloseHelper(
1183                             true,   // raise the state change event
1184                             previousState
1185                         );
1186         }
1187
1188         /// <summary>
1189         /// Store-specific helper method invoked as part of Close()/Dispose().
1190         /// </summary>
1191         private void StoreCloseHelper()
1192         {
1193             try
1194             {
1195                 if (this._storeConnection != null && (this._storeConnection.State != ConnectionState.Closed))
1196                 {
1197                     this._storeConnection.Close();
1198                 }
1199                 // Need to disassociate the transaction objects with this connection
1200                 ClearTransactions();
1201             }
1202             catch (Exception e)
1203             {
1204                 if (EntityUtil.IsCatchableExceptionType(e))
1205                 {
1206                     throw EntityUtil.ProviderExceptionWithMessage(
1207                                         System.Data.Entity.Strings.EntityClient_ErrorInClosingConnection,
1208                                         e
1209                                     );
1210                 }
1211                 throw;
1212             }
1213         }
1214
1215         /// <summary>
1216         /// Entity-specific helper method invoked as part of Close()/Dispose().
1217         /// </summary>
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)
1222         {
1223             bool result = false;
1224
1225             this._entityClientConnectionState = ConnectionState.Closed;
1226
1227             if (previousState == ConnectionState.Open)
1228             {
1229                 if (fireEventOnStateChange)
1230                 {
1231                     OnStateChange(StateChangeClosed);
1232                 }
1233                 else
1234                 {
1235                     result = true;  // we didn't raise the event here; the caller should do that
1236                 }
1237             }
1238
1239             return result;
1240         }
1241
1242         /// <summary>
1243         /// Call to determine if changes to the entity object are currently permitted.
1244         /// </summary>
1245         private void ValidateChangesPermitted()
1246         {
1247             if (_initialized)
1248             {
1249                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_SettingsCannotBeChangedOnOpenConnection);
1250             }
1251         }
1252
1253         /// <summary>
1254         /// Returns the DbProviderFactory associated with specified provider string
1255         /// </summary>
1256         private DbProviderFactory GetFactory(string providerString)
1257         {
1258             try
1259             {
1260                 return DbProviderFactories.GetFactory(providerString);
1261             }
1262             catch (ArgumentException e)
1263             {
1264                 throw EntityUtil.Argument(System.Data.Entity.Strings.EntityClient_InvalidStoreProvider, e);
1265             }
1266         }
1267
1268         /// <summary>
1269         /// Uses DbProviderFactory to create a DbConnection
1270         /// </summary>
1271         private DbConnection GetStoreConnection(DbProviderFactory factory)
1272         {
1273             DbConnection storeConnection = factory.CreateConnection();
1274             if (storeConnection == null)
1275             {
1276                 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.EntityClient_ReturnedNullOnProviderMethod("CreateConnection", factory.GetType().Name));
1277             }
1278             return storeConnection;
1279         }
1280
1281     }
1282 }