1 //---------------------------------------------------------------------
2 // <copyright file="ObjectContext.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 
10 namespace System.Data.Objects
11 {
12     using System;
13     using System.Collections;
14     using System.Collections.Generic;
15     using System.ComponentModel;
16     using System.Configuration;
17     using System.Data.Common;
18     using System.Data.Common.CommandTrees;
19     using System.Data.Common.CommandTrees.ExpressionBuilder;
20     using System.Data.Common.Internal.Materialization;
21     using System.Data.Common.Utils;
22     using System.Data.Entity;
23     using System.Data.EntityClient;
24     using System.Data.Metadata.Edm;
25     using System.Data.Objects.DataClasses;
26     using System.Data.Objects.ELinq;
27     using System.Data.Objects.Internal;
28     using System.Data.Query.InternalTrees;
29     using System.Diagnostics;
30     using System.Diagnostics.CodeAnalysis;
31     using System.Globalization;
32     using System.Linq;
33     using System.Linq.Expressions;
34     using System.Runtime.Versioning;
35     using System.Text;
36     using System.Transactions;
37 
38     /// <summary>
39     /// Defines options that affect the behavior of the ObjectContext.
40     /// </summary>
41     public sealed class ObjectContextOptions
42     {
43         private bool _lazyLoadingEnabled;
44         private bool _proxyCreationEnabled = true;
45         private bool _useLegacyPreserveChangesBehavior = false;
46         private bool _useConsistentNullReferenceBehavior;
47         private bool _useCSharpNullComparisonBehavior = false;
48 
ObjectContextOptions()49         internal ObjectContextOptions()
50         {
51         }
52 
53         /// <summary>
54         /// Get or set boolean that determines if related ends can be loaded on demand
55         /// when they are accessed through a navigation property.
56         /// </summary>
57         /// <value>
58         /// True if related ends can be loaded on demand; otherwise false.
59         /// </value>
60         public bool LazyLoadingEnabled
61         {
62             get { return _lazyLoadingEnabled; }
63             set { _lazyLoadingEnabled = value; }
64         }
65 
66         /// <summary>
67         /// Get or set boolean that determines whether proxy instances will be create
68         /// for CLR types with a corresponding proxy type.
69         /// </summary>
70         /// <value>
71         /// True if proxy instances should be created; otherwise false to create "normal" instances of the type.
72         /// </value>
73         public bool ProxyCreationEnabled
74         {
75             get { return _proxyCreationEnabled; }
76             set { _proxyCreationEnabled = value; }
77         }
78 
79         /// <summary>
80         /// Get or set a boolean that determines whether to use the legacy MergeOption.PreserveChanges behavior
81         /// when querying for entities using MergeOption.PreserveChanges
82         /// </summary>
83         /// <value>
84         /// True if the legacy MergeOption.PreserveChanges behavior should be used; otherwise false.
85         /// </value>
86         public bool UseLegacyPreserveChangesBehavior
87         {
88             get { return _useLegacyPreserveChangesBehavior; }
89             set { _useLegacyPreserveChangesBehavior = value; }
90         }
91 
92         /// <summary>
93         /// If this flag is set to false then setting the Value property of the <see cref="EntityReference{T}"/> for an
94         /// FK relationship to null when it is already null will have no effect. When this flag is set to true, then
95         /// setting the value to null will always cause the FK to be nulled and the relationship to be deleted
96         /// even if the value is currently null. The default value is false when using ObjectContext and true
97         /// when using DbContext.
98         /// </summary>
99         public bool UseConsistentNullReferenceBehavior
100         {
101             get { return _useConsistentNullReferenceBehavior; }
102             set { _useConsistentNullReferenceBehavior = value; }
103         }
104 
105         /// <summary>
106         /// This flag determines whether C# behavior should be exhibited when comparing null values in LinqToEntities.
107         /// If this flag is set, then any equality comparison between two operands, both of which are potentially
108         /// nullable, will be rewritten to show C# null comparison semantics. As an example:
109         /// (operand1 = operand2) will be rewritten as
110         /// (((operand1 = operand2) AND NOT (operand1 IS NULL OR operand2 IS NULL)) || (operand1 IS NULL && operand2 IS NULL))
111         /// The default value is false when using <see cref="ObjectContext"/>.
112         /// </summary>
113         public bool UseCSharpNullComparisonBehavior
114         {
115             get { return _useCSharpNullComparisonBehavior; }
116             set { _useCSharpNullComparisonBehavior = value; }
117         }
118     }
119 
120     /// <summary>
121     /// ObjectContext is the top-level object that encapsulates a connection between the CLR and the database,
122     /// serving as a gateway for Create, Read, Update, and Delete operations.
123     /// </summary>
124     public class ObjectContext : IDisposable
125     {
126         #region Fields
127         private IEntityAdapter _adapter;
128 
129         // Connection may be null if used by ObjectMaterializer for detached ObjectContext,
130         // but those code paths should not touch the connection.
131         //
132         // If the connection is null, this indicates that this object has been disposed.
133         // Disposal for this class doesn't mean complete disposal,
134         // but rather the disposal of the underlying connection object if the ObjectContext owns the connection,
135         // or the separation of the underlying connection object from the ObjectContext if the ObjectContext does not own the connection.
136         //
137         // Operations that require a connection should throw an ObjectDiposedException if the connection is null.
138         // Other operations that do not need a connection should continue to work after disposal.
139         private EntityConnection _connection;
140 
141         private readonly MetadataWorkspace _workspace;
142         private ObjectStateManager _cache;
143         private ClrPerspective _perspective;
144         private readonly bool _createdConnection;
145         private bool _openedConnection;             // whether or not the context opened the connection to do an operation
146         private int _connectionRequestCount;        // the number of active requests for an open connection
147         private int? _queryTimeout;
148         private Transaction _lastTransaction;
149 
150         private bool _disallowSettingDefaultContainerName;
151 
152         private EventHandler _onSavingChanges;
153 
154         private ObjectMaterializedEventHandler _onObjectMaterialized;
155 
156         private ObjectQueryProvider _queryProvider;
157 
158         private readonly ObjectContextOptions _options = new ObjectContextOptions();
159 
160         private readonly string s_UseLegacyPreserveChangesBehavior = "EntityFramework_UseLegacyPreserveChangesBehavior";
161 
162         #endregion Fields
163 
164         #region Constructors
165         /// <summary>
166         /// Creates an ObjectContext with the given connection and metadata workspace.
167         /// </summary>
168         /// <param name="connection">connection to the store</param>
ObjectContext(EntityConnection connection)169         public ObjectContext(EntityConnection connection)
170             : this(EntityUtil.CheckArgumentNull(connection, "connection"), true)
171         {
172         }
173 
174         /// <summary>
175         /// Creates an ObjectContext with the given connection string and
176         /// default entity container name.  This constructor
177         /// creates and initializes an EntityConnection so that the context is
178         /// ready to use; no other initialization is necessary.  The given
179         /// connection string must be valid for an EntityConnection; connection
180         /// strings for other connection types are not supported.
181         /// </summary>
182         /// <param name="connectionString">the connection string to use in the underlying EntityConnection to the store</param>
183         /// <exception cref="ArgumentNullException">connectionString is null</exception>
184         /// <exception cref="ArgumentException">if connectionString is invalid</exception>
185         [ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
186         [ResourceConsumption(ResourceScope.Machine)] //For CreateEntityConnection method. But the paths are not created in this method.
ObjectContext(string connectionString)187         public ObjectContext(string connectionString)
188             : this(CreateEntityConnection(connectionString), false)
189         {
190             _createdConnection = true;
191         }
192 
193 
194         /// <summary>
195         /// Creates an ObjectContext with the given connection string and
196         /// default entity container name.  This protected constructor creates and initializes an EntityConnection so that the context
197         /// is ready to use; no other initialization is necessary.  The given connection string must be valid for an EntityConnection;
198         /// connection strings for other connection types are not supported.
199         /// </summary>
200         /// <param name="connectionString">the connection string to use in the underlying EntityConnection to the store</param>
201         /// <param name="defaultContainerName">the name of the default entity container</param>
202         /// <exception cref="ArgumentNullException">connectionString is null</exception>
203         /// <exception cref="ArgumentException">either connectionString or defaultContainerName is invalid</exception>
204         [ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
205         [ResourceConsumption(ResourceScope.Machine)] //For ObjectContext method. But the paths are not created in this method.
ObjectContext(string connectionString, string defaultContainerName)206         protected ObjectContext(string connectionString, string defaultContainerName)
207             : this(connectionString)
208         {
209             DefaultContainerName = defaultContainerName;
210             if (!string.IsNullOrEmpty(defaultContainerName))
211             {
212                 _disallowSettingDefaultContainerName = true;
213             }
214         }
215 
216         /// <summary>
217         /// Creates an ObjectContext with the given connection and metadata workspace.
218         /// </summary>
219         /// <param name="connection">connection to the store</param>
220         /// <param name="defaultContainerName">the name of the default entity container</param>
ObjectContext(EntityConnection connection, string defaultContainerName)221         protected ObjectContext(EntityConnection connection, string defaultContainerName)
222             : this(connection)
223         {
224             DefaultContainerName = defaultContainerName;
225             if (!string.IsNullOrEmpty(defaultContainerName))
226             {
227                 _disallowSettingDefaultContainerName = true;
228             }
229         }
230 
ObjectContext(EntityConnection connection, bool isConnectionConstructor)231         private ObjectContext(EntityConnection connection, bool isConnectionConstructor)
232         {
233             Debug.Assert(null != connection, "null connection");
234             _connection = connection;
235 
236             _connection.StateChange += ConnectionStateChange;
237 
238             // Ensure a valid connection
239             string connectionString = connection.ConnectionString;
240             if (connectionString == null || connectionString.Trim().Length == 0)
241             {
242                 throw EntityUtil.InvalidConnection(isConnectionConstructor, null);
243             }
244 
245             try
246             {
247                 _workspace = RetrieveMetadataWorkspaceFromConnection();
248             }
249             catch (InvalidOperationException e)
250             {
251                 // Intercept exceptions retrieving workspace, and wrap exception in appropriate
252                 // message based on which constructor pattern is being used.
253                 throw EntityUtil.InvalidConnection(isConnectionConstructor, e);
254             }
255 
256             // Register the O and OC metadata
257             if (null != _workspace)
258             {
259                 // register the O-Loader
260                 if (!_workspace.IsItemCollectionAlreadyRegistered(DataSpace.OSpace))
261                 {
262                     ObjectItemCollection itemCollection = new ObjectItemCollection();
263                     _workspace.RegisterItemCollection(itemCollection);
264                 }
265 
266                 // have the OC-Loader registered by asking for it
267                 _workspace.GetItemCollection(DataSpace.OCSpace);
268             }
269 
270             // load config file properties
271             string value = ConfigurationManager.AppSettings[s_UseLegacyPreserveChangesBehavior];
272             bool useV35Behavior = false;
273             if (Boolean.TryParse(value, out useV35Behavior))
274             {
275                 ContextOptions.UseLegacyPreserveChangesBehavior = useV35Behavior;
276             }
277         }
278 
279         #endregion //Constructors
280 
281         #region Properties
282         /// <summary>
283         /// Gets the connection to the store.
284         /// </summary>
285         /// <exception cref="ObjectDisposedException">If the <see cref="ObjectContext"/> instance has been disposed.</exception>
286         public DbConnection Connection
287         {
288             get
289             {
290                 if (_connection == null)
291                 {
292                     throw EntityUtil.ObjectContextDisposed();
293                 }
294 
295                 return _connection;
296             }
297         }
298 
299         /// <summary>
300         /// Gets or sets the default container name.
301         /// </summary>
302         public string DefaultContainerName
303         {
304             get
305             {
306                 EntityContainer container = Perspective.GetDefaultContainer();
307                 return ((null != container) ? container.Name : String.Empty);
308             }
309             set
310             {
311                 if (!_disallowSettingDefaultContainerName)
312                 {
313                     Perspective.SetDefaultContainer(value);
314                 }
315                 else
316                 {
317                     throw EntityUtil.CannotSetDefaultContainerName();
318                 }
319             }
320         }
321 
322         /// <summary>
323         /// Gets the metadata workspace associated with this ObjectContext.
324         /// </summary>
325         [CLSCompliant(false)]
326         public MetadataWorkspace MetadataWorkspace
327         {
328             get
329             {
330                 return _workspace;
331             }
332         }
333 
334         /// <summary>
335         /// Gets the ObjectStateManager used by this ObjectContext.
336         /// </summary>
337         public ObjectStateManager ObjectStateManager
338         {
339             get
340             {
341                 if (_cache == null)
342                 {
343                     _cache = new ObjectStateManager(_workspace);
344                 }
345                 return _cache;
346             }
347         }
348 
349         /// <summary>
350         /// ClrPerspective based on the MetadataWorkspace.
351         /// </summary>
352         internal ClrPerspective Perspective
353         {
354             get
355             {
356                 if (_perspective == null)
357                 {
358                     _perspective = new ClrPerspective(_workspace);
359                 }
360                 return _perspective;
361             }
362         }
363 
364         /// <summary>
365         /// Gets and sets the timeout value used for queries with this ObjectContext.
366         /// A null value indicates that the default value of the underlying provider
367         /// will be used.
368         /// </summary>
369         public int? CommandTimeout
370         {
371             get
372             {
373                 return _queryTimeout;
374             }
375             set
376             {
377                 if (value.HasValue && value < 0)
378                 {
379                     throw EntityUtil.InvalidCommandTimeout("value");
380                 }
381                 _queryTimeout = value;
382             }
383         }
384 
385         /// <summary>
386         /// Gets the LINQ query provider associated with this object context.
387         /// </summary>
388         internal protected IQueryProvider QueryProvider
389         {
390             get
391             {
392                 if (null == _queryProvider)
393                 {
394                     _queryProvider = new ObjectQueryProvider(this);
395                 }
396                 return _queryProvider;
397             }
398         }
399 
400         /// <summary>
401         /// Whether or not we are in the middle of materialization
402         /// Used to suppress operations such as lazy loading that are not allowed during materialization
403         /// </summary>
404         internal bool InMaterialization { get; set; }
405 
406         /// <summary>
407         /// Get <see cref="ObjectContextOptions"/> instance that contains options
408         /// that affect the behavior of the ObjectContext.
409         /// </summary>
410         /// <value>
411         /// Instance of <see cref="ObjectContextOptions"/> for the current ObjectContext.
412         /// This value will never be null.
413         /// </value>
414         public ObjectContextOptions ContextOptions
415         {
416             get { return _options; }
417         }
418 
419         #endregion //Properties
420 
421         #region Events
422         /// <summary>
423         /// Property for adding a delegate to the SavingChanges Event.
424         /// </summary>
425         public event EventHandler SavingChanges
426         {
427             add { _onSavingChanges += value; }
428             remove { _onSavingChanges -= value; }
429         }
430         /// <summary>
431         /// A private helper function for the _savingChanges/SavingChanges event.
432         /// </summary>
OnSavingChanges()433         private void OnSavingChanges()
434         {
435             if (null != _onSavingChanges)
436             {
437                 _onSavingChanges(this, new EventArgs());
438             }
439         }
440 
441         /// <summary>
442         /// Event raised when a new entity object is materialized.  That is, the event is raised when
443         /// a new entity object is created from data in the store as part of a query or load operation.
444         /// </summary>
445         /// <remarks>
446         /// Note that the event is raised after included (spanned) referenced objects are loaded, but
447         /// before included (spanned) collections are loaded.  Also, for independent associations,
448         /// any stub entities for related objects that have not been loaded will also be created before
449         /// the event is raised.
450         ///
451         /// It is possible for an entity object to be created and then thrown away if it is determined
452         /// that an entity with the same ID already exists in the Context.  This event is not raised
453         /// in those cases.
454         /// </remarks>
455         public event ObjectMaterializedEventHandler ObjectMaterialized
456         {
457             add { _onObjectMaterialized += value; }
458             remove { _onObjectMaterialized -= value; }
459         }
460 
OnObjectMaterialized(object entity)461         internal void OnObjectMaterialized(object entity)
462         {
463             if (null != _onObjectMaterialized)
464             {
465                 _onObjectMaterialized(this, new ObjectMaterializedEventArgs(entity));
466             }
467         }
468 
469         /// <summary>
470         /// Returns true if any handlers for the ObjectMaterialized event exist.  This is
471         /// used for perf reasons to avoid collecting the information needed for the event
472         /// if there is no point in firing it.
473         /// </summary>
474         internal bool OnMaterializedHasHandlers
475         {
476             get { return _onObjectMaterialized != null && _onObjectMaterialized.GetInvocationList().Length != 0; }
477         }
478 
479         #endregion //Events
480 
481         #region Methods
482         /// <summary>
483         /// AcceptChanges on all associated entries in the ObjectStateManager so their resultant state is either unchanged or detached.
484         /// </summary>
485         /// <returns></returns>
AcceptAllChanges()486         public void AcceptAllChanges()
487         {
488             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
489 
490             if (ObjectStateManager.SomeEntryWithConceptualNullExists())
491             {
492                 throw new InvalidOperationException(Strings.ObjectContext_CommitWithConceptualNull);
493             }
494 
495             // There are scenarios in which order of calling AcceptChanges does matter:
496             // in case there is an entity in Deleted state and another entity in Added state with the same ID -
497             // it is necessary to call AcceptChanges on Deleted entity before calling AcceptChanges on Added entity
498             // (doing this in the other order there is conflict of keys).
499             foreach (ObjectStateEntry entry in ObjectStateManager.GetObjectStateEntries(EntityState.Deleted))
500             {
501                 entry.AcceptChanges();
502             }
503 
504             foreach (ObjectStateEntry entry in ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
505             {
506                 entry.AcceptChanges();
507             }
508 
509             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
510         }
511 
VerifyRootForAdd(bool doAttach, string entitySetName, IEntityWrapper wrappedEntity, EntityEntry existingEntry, out EntitySet entitySet, out bool isNoOperation)512         private void VerifyRootForAdd(bool doAttach, string entitySetName, IEntityWrapper wrappedEntity, EntityEntry existingEntry, out EntitySet entitySet, out bool isNoOperation)
513         {
514             isNoOperation = false;
515 
516             EntitySet entitySetFromName = null;
517 
518             if (doAttach)
519             {
520                 // For AttachTo the entity set name is optional
521                 if (!String.IsNullOrEmpty(entitySetName))
522                 {
523                     entitySetFromName = this.GetEntitySetFromName(entitySetName);
524                 }
525             }
526             else
527             {
528                 // For AddObject the entity set name is obligatory
529                 entitySetFromName = this.GetEntitySetFromName(entitySetName);
530             }
531 
532             // Find entity set using entity key
533             EntitySet entitySetFromKey = null;
534 
535             EntityKey key = existingEntry != null ? existingEntry.EntityKey : wrappedEntity.GetEntityKeyFromEntity();
536             if (null != (object)key)
537             {
538                 entitySetFromKey = key.GetEntitySet(this.MetadataWorkspace);
539 
540                 if (entitySetFromName != null)
541                 {
542                     // both entity sets are not null, compare them
543                     EntityUtil.ValidateEntitySetInKey(key, entitySetFromName, "entitySetName");
544                 }
545                 key.ValidateEntityKey(_workspace, entitySetFromKey);
546             }
547 
548             entitySet = entitySetFromKey ?? entitySetFromName;
549 
550             // Check if entity set was found
551             if (entitySet == null)
552             {
553                 throw EntityUtil.EntitySetNameOrEntityKeyRequired();
554             }
555 
556             this.ValidateEntitySet(entitySet, wrappedEntity.IdentityType);
557 
558             // If in the middle of Attach, try to find the entry by key
559             if (doAttach && existingEntry == null)
560             {
561                 // If we don't already have a key, create one now
562                 if (null == (object)key)
563                 {
564                     key = this.ObjectStateManager.CreateEntityKey(entitySet, wrappedEntity.Entity);
565                 }
566                 existingEntry = this.ObjectStateManager.FindEntityEntry(key);
567             }
568 
569             if (null != existingEntry && !(doAttach && existingEntry.IsKeyEntry))
570             {
571                 if (!Object.ReferenceEquals(existingEntry.Entity, wrappedEntity.Entity))
572                 {
573                     throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
574                 }
575                 else
576                 {
577                     EntityState exptectedState = doAttach ? EntityState.Unchanged : EntityState.Added;
578 
579                     if (existingEntry.State != exptectedState)
580                     {
581                         throw doAttach ?
582                             EntityUtil.EntityAlreadyExistsInObjectStateManager() :
583                             EntityUtil.ObjectStateManagerDoesnotAllowToReAddUnchangedOrModifiedOrDeletedEntity(existingEntry.State);
584                     }
585                     else
586                     {
587                         // AttachTo:
588                         // Attach is no-op when the existing entry is not a KeyEntry
589                         // and it's entity is the same entity instance and it's state is Unchanged
590 
591                         // AddObject:
592                         // AddObject is no-op when the existing entry's entity is the same entity
593                         // instance and it's state is Added
594                         isNoOperation = true;
595                         return;
596                     }
597                 }
598             }
599         }
600 
601         /// <summary>
602         /// Adds an object to the cache.  If it doesn't already have an entity key, the
603         /// entity set is determined based on the type and the O-C map.
604         /// If the object supports relationships (i.e. it implements IEntityWithRelationships),
605         /// this also sets the context onto its RelationshipManager object.
606         /// </summary>
607         /// <param name="entitySetName">entitySetName the Object to be added. It might be qualifed with container name </param>
608         /// <param name="entity">Object to be added.</param>
AddObject(string entitySetName, object entity)609         public void AddObject(string entitySetName, object entity)
610         {
611             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
612             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
613             EntityUtil.CheckArgumentNull(entity, "entity");
614 
615             EntityEntry existingEntry;
616             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingContextGettingEntry(entity, this, out existingEntry);
617 
618             if (existingEntry == null)
619             {
620                 // If the exact object being added is already in the context, there there is no way we need to
621                 // load the type for it, and since this is expensive, we only do the load if we have to.
622 
623                 // SQLBUDT 480919: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
624                 // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
625                 // We will auto-load the entity type's assembly into the ObjectItemCollection.
626                 // We don't need the user's calling assembly for LoadAssemblyForType since entityType is sufficient.
627                 MetadataWorkspace.ImplicitLoadAssemblyForType(wrappedEntity.IdentityType, null);
628             }
629             else
630             {
631                 Debug.Assert((object)existingEntry.Entity == (object)entity, "FindEntityEntry should return null if existing entry contains a different object.");
632             }
633 
634             EntitySet entitySet;
635             bool isNoOperation;
636 
637             this.VerifyRootForAdd(false, entitySetName, wrappedEntity, existingEntry, out entitySet, out isNoOperation);
638             if (isNoOperation)
639             {
640                 return;
641             }
642 
643             System.Data.Objects.Internal.TransactionManager transManager = ObjectStateManager.TransactionManager;
644             transManager.BeginAddTracking();
645 
646             try
647             {
648                 RelationshipManager relationshipManager = wrappedEntity.RelationshipManager;
649                 Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
650 
651                 bool doCleanup = true;
652                 try
653                 {
654                     // Add the root of the graph to the cache.
655                     AddSingleObject(entitySet, wrappedEntity, "entity");
656                     doCleanup = false;
657                 }
658                 finally
659                 {
660                     // If we failed after adding the entry but before completely attaching the related ends to the context, we need to do some cleanup.
661                     // If the context is null, we didn't even get as far as trying to attach the RelationshipManager, so something failed before the entry
662                     // was even added, therefore there is nothing to clean up.
663                     if (doCleanup && wrappedEntity.Context == this)
664                     {
665                         // If the context is not null, it be because the failure happened after it was attached, or it
666                         // could mean that this entity was already attached, in which case we don't want to clean it up
667                         // If we find the entity in the context and its key is temporary, we must have just added it, so remove it now.
668                         EntityEntry entry = this.ObjectStateManager.FindEntityEntry(wrappedEntity.Entity);
669                         if (entry != null && entry.EntityKey.IsTemporary)
670                         {
671                             // devnote: relationshipManager is valid, so entity must be IEntityWithRelationships and casting is safe
672                             relationshipManager.NodeVisited = true;
673                             // devnote: even though we haven't added the rest of the graph yet, we need to go through the related ends and
674                             //          clean them up, because some of them could have been attached to the context before the failure occurred
675                             RelationshipManager.RemoveRelatedEntitiesFromObjectStateManager(wrappedEntity);
676                             RelatedEnd.RemoveEntityFromObjectStateManager(wrappedEntity);
677                         }
678                         // else entry was not added or the key is not temporary, so it must have already been in the cache before we tried to add this product, so don't remove anything
679                     }
680                 }
681                 relationshipManager.AddRelatedEntitiesToObjectStateManager(/*doAttach*/false);
682             }
683             finally
684             {
685                 transManager.EndAddTracking();
686                 ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
687             }
688         }
689         /// <summary>
690         /// Adds an object to the cache without adding its related
691         /// entities.
692         /// </summary>
693         /// <param name="entity">Object to be added.</param>
694         /// <param name="setName">EntitySet name for the Object to be added. It may be qualified with container name</param>
695         /// <param name="containerName">Container name for the Object to be added.</param>
696         /// <param name="argumentName">Name of the argument passed to a public method, for use in exceptions.</param>
AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, string argumentName)697         internal void AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, string argumentName)
698         {
699             Debug.Assert(entitySet != null, "The extent for an entity must be a non-null entity set.");
700             Debug.Assert(wrappedEntity != null, "The entity wrapper must not be null.");
701             Debug.Assert(wrappedEntity.Entity != null, "The entity must not be null.");
702 
703             EntityKey key = wrappedEntity.GetEntityKeyFromEntity();
704             if (null != (object)key)
705             {
706                 EntityUtil.ValidateEntitySetInKey(key, entitySet);
707                 key.ValidateEntityKey(_workspace, entitySet);
708             }
709 
710             VerifyContextForAddOrAttach(wrappedEntity);
711             wrappedEntity.Context = this;
712             EntityEntry entry = this.ObjectStateManager.AddEntry(wrappedEntity, (EntityKey)null, entitySet, argumentName, true);
713 
714             // If the entity supports relationships, AttachContext on the
715             // RelationshipManager object - with load option of
716             // AppendOnly (if adding a new object to a context, set
717             // the relationships up to cache by default -- load option
718             // is only set to other values when AttachContext is
719             // called by the materializer). Also add all related entitites to
720             // cache.
721             //
722             // NOTE: AttachContext must be called after adding the object to
723             // the cache--otherwise the object might not have a key
724             // when the EntityCollections expect it to.
725             Debug.Assert(this.ObjectStateManager.TransactionManager.TrackProcessedEntities, "Expected tracking processed entities to be true when adding.");
726             Debug.Assert(this.ObjectStateManager.TransactionManager.ProcessedEntities != null, "Expected non-null collection when flag set.");
727 
728             this.ObjectStateManager.TransactionManager.ProcessedEntities.Add(wrappedEntity);
729 
730             wrappedEntity.AttachContext(this, entitySet, MergeOption.AppendOnly);
731 
732             // Find PK values in referenced principals and use these to set FK values
733             entry.FixupFKValuesFromNonAddedReferences();
734 
735             _cache.FixupReferencesByForeignKeys(entry);
736             wrappedEntity.TakeSnapshotOfRelationships(entry);
737         }
738 
739         /// <summary>
740         /// Explicitly loads a referenced entity or collection of entities into the given entity.
741         /// </summary>
742         /// <remarks>
743         /// After loading, the referenced entity or collection can be accessed through the properties
744         /// of the source entity.
745         /// </remarks>
746         /// <param name="entity">The source entity on which the relationship is defined</param>
747         /// <param name="navigationProperty">The name of the property to load</param>
LoadProperty(object entity, string navigationProperty)748         public void LoadProperty(object entity, string navigationProperty)
749         {
750             IEntityWrapper wrappedEntity = WrapEntityAndCheckContext(entity, "property");
751             wrappedEntity.RelationshipManager.GetRelatedEnd(navigationProperty).Load();
752         }
753 
754         /// <summary>
755         /// Explicitly loads a referenced entity or collection of entities into the given entity.
756         /// </summary>
757         /// <remarks>
758         /// After loading, the referenced entity or collection can be accessed through the properties
759         /// of the source entity.
760         /// </remarks>
761         /// <param name="entity">The source entity on which the relationship is defined</param>
762         /// <param name="navigationProperty">The name of the property to load</param>
763         /// <param name="mergeOption">The merge option to use for the load</param>
LoadProperty(object entity, string navigationProperty, MergeOption mergeOption)764         public void LoadProperty(object entity, string navigationProperty, MergeOption mergeOption)
765         {
766             IEntityWrapper wrappedEntity = WrapEntityAndCheckContext(entity, "property");
767             wrappedEntity.RelationshipManager.GetRelatedEnd(navigationProperty).Load(mergeOption);
768         }
769 
770         /// <summary>
771         /// Explicitly loads a referenced entity or collection of entities into the given entity.
772         /// </summary>
773         /// <remarks>
774         /// After loading, the referenced entity or collection can be accessed through the properties
775         /// of the source entity.
776         /// The property to load is specified by a LINQ expression which must be in the form of
777         /// a simple property member access.  For example, <code>(entity) => entity.PropertyName</code>
778         /// where PropertyName is the navigation property to be loaded.  Other expression forms will
779         /// be rejected at runtime.
780         /// </remarks>
781         /// <param name="entity">The source entity on which the relationship is defined</param>
782         /// <param name="selector">A LINQ expression specifying the property to load</param>
LoadProperty(TEntity entity, Expression<Func<TEntity, object>> selector)783         public void LoadProperty<TEntity>(TEntity entity, Expression<Func<TEntity, object>> selector)
784         {
785             // We used to throw an ArgumentException if the expression contained a Convert.  Now we remove the convert,
786             // but if we still need to throw, then we should still throw an ArgumentException to avoid a breaking change.
787             // Therefore, we keep track of whether or not we removed the convert.
788             bool removedConvert;
789             var navProp = ParsePropertySelectorExpression<TEntity>(selector, out removedConvert);
790             IEntityWrapper wrappedEntity = WrapEntityAndCheckContext(entity, "property");
791             wrappedEntity.RelationshipManager.GetRelatedEnd(navProp, throwArgumentException: removedConvert).Load();
792         }
793 
794         /// <summary>
795         /// Explicitly loads a referenced entity or collection of entities into the given entity.
796         /// </summary>
797         /// <remarks>
798         /// After loading, the referenced entity or collection can be accessed through the properties
799         /// of the source entity.
800         /// The property to load is specified by a LINQ expression which must be in the form of
801         /// a simple property member access.  For example, <code>(entity) => entity.PropertyName</code>
802         /// where PropertyName is the navigation property to be loaded.  Other expression forms will
803         /// be rejected at runtime.
804         /// </remarks>
805         /// <param name="entity">The source entity on which the relationship is defined</param>
806         /// <param name="selector">A LINQ expression specifying the property to load</param>
807         /// <param name="mergeOption">The merge option to use for the load</param>
LoadProperty(TEntity entity, Expression<Func<TEntity, object>> selector, MergeOption mergeOption)808         public void LoadProperty<TEntity>(TEntity entity, Expression<Func<TEntity, object>> selector, MergeOption mergeOption)
809         {
810             // We used to throw an ArgumentException if the expression contained a Convert.  Now we remove the convert,
811             // but if we still need to throw, then we should still throw an ArgumentException to avoid a breaking change.
812             // Therefore, we keep track of whether or not we removed the convert.
813             bool removedConvert;
814             var navProp = ParsePropertySelectorExpression<TEntity>(selector, out removedConvert);
815             IEntityWrapper wrappedEntity = WrapEntityAndCheckContext(entity, "property");
816             wrappedEntity.RelationshipManager.GetRelatedEnd(navProp, throwArgumentException: removedConvert).Load(mergeOption);
817         }
818 
819         // Wraps the given entity and checks that it has a non-null context (i.e. that is is not detached).
WrapEntityAndCheckContext(object entity, string refType)820         private IEntityWrapper WrapEntityAndCheckContext(object entity, string refType)
821         {
822             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingContext(entity, this);
823             if (wrappedEntity.Context == null)
824             {
825                 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectContext_CannotExplicitlyLoadDetachedRelationships(refType));
826             }
827             if (wrappedEntity.Context != this)
828             {
829                 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectContext_CannotLoadReferencesUsingDifferentContext(refType));
830             }
831             return wrappedEntity;
832         }
833 
834         // Validates that the given property selector may represent a navigation property and returns the nav prop string.
835         // The actual check that the navigation property is valid is performed by the
836         // RelationshipManager while loading the RelatedEnd.
ParsePropertySelectorExpression(Expression<Func<TEntity, object>> selector, out bool removedConvert)837         internal static string ParsePropertySelectorExpression<TEntity>(Expression<Func<TEntity, object>> selector, out bool removedConvert)
838         {
839             EntityUtil.CheckArgumentNull(selector, "selector");
840 
841             // We used to throw an ArgumentException if the expression contained a Convert.  Now we remove the convert,
842             // but if we still need to throw, then we should still throw an ArgumentException to avoid a breaking change.
843             // Therefore, we keep track of whether or not we removed the convert.
844             removedConvert = false;
845             var body = selector.Body;
846             while (body.NodeType == ExpressionType.Convert || body.NodeType == ExpressionType.ConvertChecked)
847             {
848                 removedConvert = true;
849                 body = ((UnaryExpression)body).Operand;
850             }
851 
852             var bodyAsMember = body as MemberExpression;
853             if (bodyAsMember == null ||
854                 !bodyAsMember.Member.DeclaringType.IsAssignableFrom(typeof(TEntity)) ||
855                 bodyAsMember.Expression.NodeType != ExpressionType.Parameter)
856             {
857                 throw new ArgumentException(System.Data.Entity.Strings.ObjectContext_SelectorExpressionMustBeMemberAccess);
858             }
859             return bodyAsMember.Member.Name;
860         }
861 
862         /// <summary>
863         /// Apply modified properties to the original object.
864         /// This API is obsolete.  Please use ApplyCurrentValues instead.
865         /// </summary>
866         /// <param name="entitySetName">name of EntitySet of entity to be updated</param>
867         /// <param name="changed">object with modified properties</param>
868         [EditorBrowsable(EditorBrowsableState.Never)]
869         [Browsable(false)]
870         [Obsolete("Use ApplyCurrentValues instead")]
ApplyPropertyChanges(string entitySetName, object changed)871         public void ApplyPropertyChanges(string entitySetName, object changed)
872         {
873             EntityUtil.CheckStringArgument(entitySetName, "entitySetName");
874             EntityUtil.CheckArgumentNull(changed, "changed");
875 
876             this.ApplyCurrentValues(entitySetName, changed);
877         }
878 
879         /// <summary>
880         /// Apply modified properties to the original object.
881         /// </summary>
882         /// <param name="entitySetName">name of EntitySet of entity to be updated</param>
883         /// <param name="currentEntity">object with modified properties</param>
884         public TEntity ApplyCurrentValues<TEntity>(string entitySetName, TEntity currentEntity) where TEntity : class
885         {
EntityUtil.CheckStringArgument(entitySetName, R)886             EntityUtil.CheckStringArgument(entitySetName, "entitySetName");
EntityUtil.CheckArgumentNull(currentEntity, R)887             EntityUtil.CheckArgumentNull(currentEntity, "currentEntity");
888             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingContext(currentEntity, this);
889 
890             // SQLBUDT 480919: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
891             // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
892             // We will auto-load the entity type's assembly into the ObjectItemCollection.
893             // We don't need the user's calling assembly for LoadAssemblyForType since entityType is sufficient.
MetadataWorkspace.ImplicitLoadAssemblyForType(wrappedEntity.IdentityType, null)894             MetadataWorkspace.ImplicitLoadAssemblyForType(wrappedEntity.IdentityType, null);
895 
896             EntitySet entitySet = this.GetEntitySetFromName(entitySetName);
897 
898             EntityKey key = wrappedEntity.EntityKey;
899             if (null != (object)key)
900             {
901                 EntityUtil.ValidateEntitySetInKey(key, entitySet, "entitySetName");
902                 key.ValidateEntityKey(_workspace, entitySet);
903             }
904             else
905             {
906                 key = this.ObjectStateManager.CreateEntityKey(entitySet, currentEntity);
907             }
908 
909             // Check if entity is already in the cache
910             EntityEntry ose = this.ObjectStateManager.FindEntityEntry(key);
911             if (ose == null || ose.IsKeyEntry)
912             {
913                 throw EntityUtil.EntityNotTracked();
914             }
915 
ose.ApplyCurrentValuesInternal(wrappedEntity)916             ose.ApplyCurrentValuesInternal(wrappedEntity);
917 
918             return (TEntity)ose.Entity;
919         }
920 
921         /// <summary>
922         /// Apply original values to the entity.
923         /// The entity to update is found based on key values of the <paramref name="originalEntity"/> entity and the given <paramref name="entitySetName"/>.
924         /// </summary>
925         /// <param name="entitySetName">name of EntitySet of entity to be updated</param>
926         /// <param name="originalEntity">object with original values</param>
927         /// <returns>updated entity</returns>
928         public TEntity ApplyOriginalValues<TEntity>(string entitySetName, TEntity originalEntity) where TEntity : class
929         {
EntityUtil.CheckStringArgument(entitySetName, R)930             EntityUtil.CheckStringArgument(entitySetName, "entitySetName");
EntityUtil.CheckArgumentNull(originalEntity, R)931             EntityUtil.CheckArgumentNull(originalEntity, "originalEntity");
932 
933             IEntityWrapper wrappedOriginalEntity = EntityWrapperFactory.WrapEntityUsingContext(originalEntity, this);
934 
935             // SQLBUDT 480919: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
936             // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
937             // We will auto-load the entity type's assembly into the ObjectItemCollection.
938             // We don't need the user's calling assembly for LoadAssemblyForType since entityType is sufficient.
MetadataWorkspace.ImplicitLoadAssemblyForType(wrappedOriginalEntity.IdentityType, null)939             MetadataWorkspace.ImplicitLoadAssemblyForType(wrappedOriginalEntity.IdentityType, null);
940 
941             EntitySet entitySet = this.GetEntitySetFromName(entitySetName);
942 
943             EntityKey key = wrappedOriginalEntity.EntityKey;
944             if (null != (object)key)
945             {
946                 EntityUtil.ValidateEntitySetInKey(key, entitySet, "entitySetName");
947                 key.ValidateEntityKey(_workspace, entitySet);
948             }
949             else
950             {
951                 key = this.ObjectStateManager.CreateEntityKey(entitySet, originalEntity);
952             }
953 
954             // Check if the entity is already in the cache
955             EntityEntry ose = this.ObjectStateManager.FindEntityEntry(key);
956             if (ose == null || ose.IsKeyEntry)
957             {
958                 throw EntityUtil.EntityNotTrackedOrHasTempKey();
959             }
960 
961             if (ose.State != EntityState.Modified &&
962                 ose.State != EntityState.Unchanged &&
963                 ose.State != EntityState.Deleted)
964             {
965                 throw EntityUtil.EntityMustBeUnchangedOrModifiedOrDeleted(ose.State);
966             }
967 
968             if (ose.WrappedEntity.IdentityType != wrappedOriginalEntity.IdentityType)
969             {
970                 throw EntityUtil.EntitiesHaveDifferentType(ose.Entity.GetType().FullName, originalEntity.GetType().FullName);
971             }
972 
ose.CompareKeyProperties(originalEntity)973             ose.CompareKeyProperties(originalEntity);
974 
975             // The ObjectStateEntry.UpdateModifiedFields uses a variation of Shaper.UpdateRecord method
976             // which additionaly marks properties as modified as necessary.
ose.UpdateOriginalValues(wrappedOriginalEntity.Entity)977             ose.UpdateOriginalValues(wrappedOriginalEntity.Entity);
978 
979             // return the current entity
980             return (TEntity)ose.Entity;
981         }
982 
983 
984         /// <summary>
985         /// Attach entity graph into the context in the Unchanged state.
986         /// This version takes entity which doesn't have to have a Key.
987         /// </summary>
988         /// <param name="entitySetName">EntitySet name for the Object to be attached. It may be qualified with container name</param>
989         /// <param name="entity"></param>
AttachTo(string entitySetName, object entity)990         public void AttachTo(string entitySetName, object entity)
991         {
992             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
993             EntityUtil.CheckArgumentNull(entity, "entity");
994             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
995 
996             EntityEntry existingEntry;
997             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingContextGettingEntry(entity, this, out existingEntry);
998 
999             if (existingEntry == null)
1000             {
1001                 // If the exact object being added is already in the context, there there is no way we need to
1002                 // load the type for it, and since this is expensive, we only do the load if we have to.
1003 
1004                 // SQLBUDT 480919: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
1005                 // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
1006                 // We will auto-load the entity type's assembly into the ObjectItemCollection.
1007                 // We don't need the user's calling assembly for LoadAssemblyForType since entityType is sufficient.
1008                 MetadataWorkspace.ImplicitLoadAssemblyForType(wrappedEntity.IdentityType, null);
1009             }
1010             else
1011             {
1012                 Debug.Assert((object)existingEntry.Entity == (object)entity, "FindEntityEntry should return null if existing entry contains a different object.");
1013             }
1014 
1015             EntitySet entitySet;
1016             bool isNoOperation;
1017 
1018             this.VerifyRootForAdd(true, entitySetName, wrappedEntity, existingEntry, out entitySet, out isNoOperation);
1019             if (isNoOperation)
1020             {
1021                 return;
1022             }
1023 
1024             System.Data.Objects.Internal.TransactionManager transManager = ObjectStateManager.TransactionManager;
1025             transManager.BeginAttachTracking();
1026 
1027             try
1028             {
1029                 this.ObjectStateManager.TransactionManager.OriginalMergeOption = wrappedEntity.MergeOption;
1030                 RelationshipManager relationshipManager = wrappedEntity.RelationshipManager;
1031                 Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
1032 
1033                 bool doCleanup = true;
1034                 try
1035                 {
1036                     // Attach the root of entity graph to the cache.
1037                     AttachSingleObject(wrappedEntity, entitySet, "entity");
1038                     doCleanup = false;
1039                 }
1040                 finally
1041                 {
1042                     // SQLBU 555615 Be sure that wrappedEntity.Context == this to not try to detach
1043                     // entity from context if it was already attached to some other context.
1044                     // It's enough to check this only for the root of the graph since we can assume that all entities
1045                     // in the graph are attached to the same context (or none of them is attached).
1046                     if (doCleanup && wrappedEntity.Context == this)
1047                     {
1048                         // SQLBU 509900 RIConstraints: Entity still exists in cache after Attach fails
1049                         //
1050                         // Cleaning up is needed only when root of the graph violates some referential constraint.
1051                         // Normal cleaning is done in RelationshipManager.AddRelatedEntitiesToObjectStateManager()
1052                         // (referential constraints properties are checked in AttachSingleObject(), before
1053                         // AddRelatedEntitiesToObjectStateManager is called, that's why normal cleaning
1054                         // doesn't work in this case)
1055 
1056                         relationshipManager.NodeVisited = true;
1057                         // devnote: even though we haven't attached the rest of the graph yet, we need to go through the related ends and
1058                         //          clean them up, because some of them could have been attached to the context.
1059                         RelationshipManager.RemoveRelatedEntitiesFromObjectStateManager(wrappedEntity);
1060                         RelatedEnd.RemoveEntityFromObjectStateManager(wrappedEntity);
1061                     }
1062                 }
1063                 relationshipManager.AddRelatedEntitiesToObjectStateManager(/*doAttach*/true);
1064             }
1065             finally
1066             {
1067                 transManager.EndAttachTracking();
1068                 ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1069             }
1070         }
1071         /// <summary>
1072         /// Attach entity graph into the context in the Unchanged state.
1073         /// This version takes entity which does have to have a non-temporary Key.
1074         /// </summary>
1075         /// <param name="entity"></param>
Attach(IEntityWithKey entity)1076         public void Attach(IEntityWithKey entity)
1077         {
1078             EntityUtil.CheckArgumentNull(entity, "entity");
1079 
1080             if (null == (object)entity.EntityKey)
1081             {
1082                 throw EntityUtil.CannotAttachEntityWithoutKey();
1083             }
1084 
1085             this.AttachTo(null, entity);
1086         }
1087         /// <summary>
1088         /// Attaches single object to the cache without adding its related entities.
1089         /// </summary>
1090         /// <param name="entity">Entity to be attached.</param>
1091         /// <param name="entitySet">"Computed" entity set.</param>
1092         /// <param name="argumentName">Name of the argument passed to a public method, for use in exceptions.</param>
AttachSingleObject(IEntityWrapper wrappedEntity, EntitySet entitySet, string argumentName)1093         internal void AttachSingleObject(IEntityWrapper wrappedEntity, EntitySet entitySet, string argumentName)
1094         {
1095             Debug.Assert(wrappedEntity != null, "entity wrapper shouldn't be null");
1096             Debug.Assert(wrappedEntity.Entity != null, "entity shouldn't be null");
1097             Debug.Assert(entitySet != null, "entitySet shouldn't be null");
1098 
1099             // Try to detect if the entity is invalid as soon as possible
1100             // (before adding the entity to the ObjectStateManager)
1101             RelationshipManager relationshipManager = wrappedEntity.RelationshipManager;
1102             Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
1103 
1104             EntityKey key = wrappedEntity.GetEntityKeyFromEntity();
1105             if (null != (object)key)
1106             {
1107                 EntityUtil.ValidateEntitySetInKey(key, entitySet);
1108                 key.ValidateEntityKey(_workspace, entitySet);
1109             }
1110             else
1111             {
1112                 key = this.ObjectStateManager.CreateEntityKey(entitySet, wrappedEntity.Entity);
1113             }
1114 
1115             Debug.Assert(key != null, "GetEntityKey should have returned a non-null key");
1116 
1117             // Temporary keys are not allowed
1118             if (key.IsTemporary)
1119             {
1120                 throw EntityUtil.CannotAttachEntityWithTemporaryKey();
1121             }
1122 
1123             if (wrappedEntity.EntityKey != key)
1124             {
1125                 wrappedEntity.EntityKey = key;
1126             }
1127 
1128             // Check if entity already exists in the cache.
1129             // NOTE: This check could be done earlier, but this way I avoid creating key twice.
1130             EntityEntry entry = ObjectStateManager.FindEntityEntry(key);
1131 
1132             if (null != entry)
1133             {
1134                 if (entry.IsKeyEntry)
1135                 {
1136                     // devnote: SQLBU 555615. This method was extracted from PromoteKeyEntry to have consistent
1137                     // behavior of AttachTo in case of attaching entity which is already attached to some other context.
1138                     // We can not detect if entity is attached to another context until we call SetChangeTrackerOntoEntity
1139                     // which throws exception if the change tracker is already set.
1140                     // SetChangeTrackerOntoEntity is now called from PromoteKeyEntryInitialization().
1141                     // Calling PromoteKeyEntryInitialization() before calling relationshipManager.AttachContext prevents
1142                     // overriding Context property on relationshipManager (and attaching relatedEnds to current context).
1143                     this.ObjectStateManager.PromoteKeyEntryInitialization(this, entry, wrappedEntity, /*shadowValues*/ null, /*replacingEntry*/ false);
1144 
1145                     Debug.Assert(this.ObjectStateManager.TransactionManager.TrackProcessedEntities, "Expected tracking processed entities to be true when adding.");
1146                     Debug.Assert(this.ObjectStateManager.TransactionManager.ProcessedEntities != null, "Expected non-null collection when flag set.");
1147 
1148                     this.ObjectStateManager.TransactionManager.ProcessedEntities.Add(wrappedEntity);
1149 
1150                     wrappedEntity.TakeSnapshotOfRelationships(entry);
1151 
1152                     this.ObjectStateManager.PromoteKeyEntry(entry,
1153                         wrappedEntity,
1154                         /*shadowValues*/ null,
1155                         /*replacingEntry*/ false,
1156                         /*setIsLoaded*/ false,
1157                         /*keyEntryInitialized*/ true,
1158                         "Attach");
1159 
1160                     ObjectStateManager.FixupReferencesByForeignKeys(entry);
1161 
1162                     relationshipManager.CheckReferentialConstraintProperties(entry);
1163                 }
1164                 else
1165                 {
1166                     Debug.Assert(!Object.ReferenceEquals(entry.Entity, wrappedEntity.Entity));
1167                     throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
1168                 }
1169             }
1170             else
1171             {
1172                 VerifyContextForAddOrAttach(wrappedEntity);
1173                 wrappedEntity.Context = this;
1174                 entry = this.ObjectStateManager.AttachEntry(key, wrappedEntity, entitySet, argumentName);
1175 
1176                 Debug.Assert(this.ObjectStateManager.TransactionManager.TrackProcessedEntities, "Expected tracking processed entities to be true when adding.");
1177                 Debug.Assert(this.ObjectStateManager.TransactionManager.ProcessedEntities != null, "Expected non-null collection when flag set.");
1178 
1179                 this.ObjectStateManager.TransactionManager.ProcessedEntities.Add(wrappedEntity);
1180 
1181                 wrappedEntity.AttachContext(this, entitySet, MergeOption.AppendOnly);
1182 
1183                 ObjectStateManager.FixupReferencesByForeignKeys(entry);
1184                 wrappedEntity.TakeSnapshotOfRelationships(entry);
1185 
1186                 relationshipManager.CheckReferentialConstraintProperties(entry);
1187             }
1188         }
1189 
1190         /// <summary>
1191         /// When attaching we need to check that the entity is not already attached to a different context
1192         /// before we wipe away that context.
1193         /// </summary>
VerifyContextForAddOrAttach(IEntityWrapper wrappedEntity)1194         private void VerifyContextForAddOrAttach(IEntityWrapper wrappedEntity)
1195         {
1196             if (wrappedEntity.Context != null &&
1197                 wrappedEntity.Context != this &&
1198                 !wrappedEntity.Context.ObjectStateManager.IsDisposed &&
1199                 wrappedEntity.MergeOption != MergeOption.NoTracking)
1200             {
1201                 throw EntityUtil.EntityCantHaveMultipleChangeTrackers();
1202             }
1203         }
1204 
1205         /// <summary>
1206         /// Create entity key based on given entity set and values of given entity.
1207         /// </summary>
1208         /// <param name="entitySetName">entity set of the entity</param>
1209         /// <param name="entity">entity</param>
1210         /// <returns>new instance of entity key</returns>
CreateEntityKey(string entitySetName, object entity)1211         public EntityKey CreateEntityKey(string entitySetName, object entity)
1212         {
1213             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
1214             EntityUtil.CheckStringArgument(entitySetName, "entitySetName");
1215             EntityUtil.CheckArgumentNull(entity, "entity");
1216 
1217             // SQLBUDT 480919: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
1218             // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
1219             // We will auto-load the entity type's assembly into the ObjectItemCollection.
1220             // We don't need the user's calling assembly for LoadAssemblyForType since entityType is sufficient.
1221             MetadataWorkspace.ImplicitLoadAssemblyForType(EntityUtil.GetEntityIdentityType(entity.GetType()), null);
1222 
1223             EntitySet entitySet = this.GetEntitySetFromName(entitySetName);
1224 
1225             return this.ObjectStateManager.CreateEntityKey(entitySet, entity);
1226         }
1227 
GetEntitySetFromName(string entitySetName)1228         internal EntitySet GetEntitySetFromName(string entitySetName)
1229         {
1230             string setName;
1231             string containerName;
1232 
1233             ObjectContext.GetEntitySetName(entitySetName, "entitySetName", this, out setName, out containerName);
1234 
1235             // Find entity set using entitySetName and entityContainerName
1236             return this.GetEntitySet(setName, containerName);
1237         }
1238 
AddRefreshKey(object entityLike, Dictionary<EntityKey, EntityEntry> entities, Dictionary<EntitySet, List<EntityKey>> currentKeys)1239         private void AddRefreshKey(object entityLike, Dictionary<EntityKey, EntityEntry> entities, Dictionary<EntitySet, List<EntityKey>> currentKeys)
1240         {
1241             Debug.Assert(!(entityLike is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
1242             if (null == entityLike)
1243             {
1244                 throw EntityUtil.NthElementIsNull(entities.Count);
1245             }
1246 
1247             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingContext(entityLike, this);
1248             EntityKey key = wrappedEntity.EntityKey;
1249             RefreshCheck(entities, entityLike, key);
1250 
1251             // Retrieve the EntitySet for the EntityKey and add an entry in the dictionary
1252             // that maps a set to the keys of entities that should be refreshed from that set.
1253             EntitySet entitySet = key.GetEntitySet(this.MetadataWorkspace);
1254 
1255             List<EntityKey> setKeys = null;
1256             if (!currentKeys.TryGetValue(entitySet, out setKeys))
1257             {
1258                 setKeys = new List<EntityKey>();
1259                 currentKeys.Add(entitySet, setKeys);
1260             }
1261 
1262             setKeys.Add(key);
1263         }
1264 
1265         /// <summary>
1266         /// Creates an ObjectSet based on the EntitySet that is defined for TEntity.
1267         /// Requires that the DefaultContainerName is set for the context and that there is a
1268         /// single EntitySet for the specified type. Throws exception if more than one type is found.
1269         /// </summary>
1270         /// <typeparam name="TEntity">Entity type for the requested ObjectSet</typeparam>
1271         public ObjectSet<TEntity> CreateObjectSet<TEntity>()
1272             where TEntity : class
1273         {
1274             EntitySet entitySet = GetEntitySetForType(typeof(TEntity), "TEntity");
1275             return new ObjectSet<TEntity>(entitySet, this);
1276         }
1277 
1278         /// <summary>
1279         /// Find the EntitySet in the default EntityContainer for the specified CLR type.
1280         /// Must be a valid mapped entity type and must be mapped to exactly one EntitySet across all of the EntityContainers in the metadata for this context.
1281         /// </summary>
1282         /// <param name="entityCLRType">CLR type to use for EntitySet lookup.</param>
1283         /// <returns></returns>
GetEntitySetForType(Type entityCLRType, string exceptionParameterName)1284         private EntitySet GetEntitySetForType(Type entityCLRType, string exceptionParameterName)
1285         {
1286             EntitySet entitySetForType = null;
1287 
1288             EntityContainer defaultContainer = this.Perspective.GetDefaultContainer();
1289             if (defaultContainer == null)
1290             {
1291                 // We don't have a default container, so look through all EntityContainers in metadata to see if
1292                 // we can find exactly one EntitySet that matches the specified CLR type.
1293                 System.Collections.ObjectModel.ReadOnlyCollection<EntityContainer> entityContainers = this.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.CSpace);
1294                 foreach (EntityContainer entityContainer in entityContainers)
1295                 {
1296                     // See if this container has exactly one EntitySet for this type
1297                     EntitySet entitySetFromContainer = GetEntitySetFromContainer(entityContainer, entityCLRType, exceptionParameterName);
1298 
1299                     if (entitySetFromContainer != null)
1300                     {
1301                         // Verify we haven't already found a matching EntitySet in some other container
1302                         if (entitySetForType != null)
1303                         {
1304                             // There is more than one EntitySet for this type across all containers in metadata, so we can't determine which one the user intended
1305                             throw EntityUtil.MultipleEntitySetsFoundInAllContainers(entityCLRType.FullName, exceptionParameterName);
1306                         }
1307 
1308                         entitySetForType = entitySetFromContainer;
1309                     }
1310                 }
1311             }
1312             else
1313             {
1314                 // There is a default container, so restrict the search to EntitySets within it
1315                 entitySetForType = GetEntitySetFromContainer(defaultContainer, entityCLRType, exceptionParameterName);
1316             }
1317 
1318             // We still may not have found a matching EntitySet for this type
1319             if (entitySetForType == null)
1320             {
1321                 throw EntityUtil.NoEntitySetFoundForType(entityCLRType.FullName, exceptionParameterName);
1322             }
1323 
1324             return entitySetForType;
1325         }
1326 
GetEntitySetFromContainer(EntityContainer container, Type entityCLRType, string exceptionParameterName)1327         private EntitySet GetEntitySetFromContainer(EntityContainer container, Type entityCLRType, string exceptionParameterName)
1328         {
1329             // Verify that we have an EdmType mapping for the specified CLR type
1330             EdmType entityEdmType = GetTypeUsage(entityCLRType).EdmType;
1331 
1332             // Try to find a single EntitySet for the specified type
1333             EntitySet entitySet = null;
1334             foreach (EntitySetBase es in container.BaseEntitySets)
1335             {
1336                 // This is a match if the set is an EntitySet (not an AssociationSet) and the EntitySet
1337                 // is defined for the specified entity type. Must be an exact match, not a base type.
1338                 if (es.BuiltInTypeKind == BuiltInTypeKind.EntitySet && es.ElementType == entityEdmType)
1339                 {
1340                     if (entitySet != null)
1341                     {
1342                         // There is more than one EntitySet for this type, so we can't determine which one the user intended
1343                         throw EntityUtil.MultipleEntitySetsFoundInSingleContainer(entityCLRType.FullName, container.Name, exceptionParameterName);
1344                     }
1345 
1346                     entitySet = (EntitySet)es;
1347                 }
1348             }
1349 
1350             return entitySet;
1351         }
1352 
1353         /// <summary>
1354         /// Creates an ObjectSet based on the specified EntitySet name.
1355         /// </summary>
1356         /// <typeparam name="TEntity">Expected type of the EntitySet</typeparam>
1357         /// <param name="entitySetName">
1358         /// EntitySet to use for the ObjectSet. Can be fully-qualified or unqualified if the DefaultContainerName is set.
1359         /// </param>
1360         public ObjectSet<TEntity> CreateObjectSet<TEntity>(string entitySetName)
1361             where TEntity : class
1362         {
1363             EntitySet entitySet = GetEntitySetForNameAndType(entitySetName, typeof(TEntity), "TEntity");
1364             return new ObjectSet<TEntity>(entitySet, this);
1365         }
1366 
1367         /// <summary>
1368         /// Finds an EntitySet with the specified name and verifies that its type matches the specified type.
1369         /// </summary>
1370         /// <param name="entitySetName">
1371         /// Name of the EntitySet to find. Can be fully-qualified or unqualified if the DefaultContainerName is set
1372         /// </param>
1373         /// <param name="entityCLRType">
1374         /// Expected CLR type of the EntitySet. Must exactly match the type for the EntitySet, base types are not valid.
1375         /// </param>
1376         /// <param name="exceptionParameterName">Argument name to use if an exception occurs.</param>
1377         /// <returns>EntitySet that was found in metadata with the specified parameters</returns>
GetEntitySetForNameAndType(string entitySetName, Type entityCLRType, string exceptionParameterName)1378         private EntitySet GetEntitySetForNameAndType(string entitySetName, Type entityCLRType, string exceptionParameterName)
1379         {
1380             // Verify that the specified entitySetName exists in metadata
1381             EntitySet entitySet = GetEntitySetFromName(entitySetName);
1382 
1383             // Verify that the EntitySet type matches the specified type exactly (a base type is not valid)
1384             EdmType entityEdmType = GetTypeUsage(entityCLRType).EdmType;
1385             if (entitySet.ElementType != entityEdmType)
1386             {
1387                 throw EntityUtil.InvalidEntityTypeForObjectSet(entityCLRType.FullName, entitySet.ElementType.FullName, entitySetName, exceptionParameterName);
1388             }
1389 
1390             return entitySet;
1391         }
1392 
1393         #region Connection Management
1394 
1395         /// <summary>
1396         /// Ensures that the connection is opened for an operation that requires an open connection to the store.
1397         /// Calls to EnsureConnection MUST be matched with a single call to ReleaseConnection.
1398         /// </summary>
1399         /// <exception cref="ObjectDisposedException">If the <see cref="ObjectContext"/> instance has been disposed.</exception>
EnsureConnection()1400         internal void EnsureConnection()
1401         {
1402             if (_connection == null)
1403             {
1404                 throw EntityUtil.ObjectContextDisposed();
1405             }
1406 
1407             if (ConnectionState.Closed == Connection.State)
1408             {
1409                 Connection.Open();
1410                 _openedConnection = true;
1411             }
1412 
1413             if (_openedConnection)
1414             {
1415                 _connectionRequestCount++;
1416             }
1417 
1418             // Check the connection was opened correctly
1419             if ((_connection.State == ConnectionState.Closed) || (_connection.State == ConnectionState.Broken))
1420             {
1421                 string message = System.Data.Entity.Strings.EntityClient_ExecutingOnClosedConnection(
1422                        _connection.State == ConnectionState.Closed ? System.Data.Entity.Strings.EntityClient_ConnectionStateClosed : System.Data.Entity.Strings.EntityClient_ConnectionStateBroken);
1423                 throw EntityUtil.InvalidOperation(message);
1424             }
1425 
1426             try
1427             {
1428                 // Make sure the necessary metadata is registered
1429                 EnsureMetadata();
1430 
1431                 #region EnsureContextIsEnlistedInCurrentTransaction
1432 
1433                 // The following conditions are no longer valid since Metadata Independence.
1434                 Debug.Assert(ConnectionState.Open == _connection.State, "Connection must be open.");
1435 
1436                 // IF YOU MODIFIED THIS TABLE YOU MUST UPDATE TESTS IN SaveChangesTransactionTests SUITE ACCORDINGLY AS SOME CASES REFER TO NUMBERS IN THIS TABLE
1437                 //
1438                 // TABLE OF ACTIONS WE PERFORM HERE:
1439                 //
1440                 //  #  lastTransaction     currentTransaction         ConnectionState   WillClose      Action                                  Behavior when no explicit transaction (started with .ElistTransaction())     Behavior with explicit transaction (started with .ElistTransaction())
1441                 //  1   null                null                       Open              No             no-op;                                  implicit transaction will be created and used                                explicit transaction should be used
1442                 //  2   non-null tx1        non-null tx1               Open              No             no-op;                                  the last transaction will be used                                            N/A - it is not possible to EnlistTransaction if another transaction has already enlisted
1443                 //  3   null                non-null                   Closed            Yes            connection.Open();                      Opening connection will automatically enlist into Transaction.Current        N/A - cannot enlist in transaction on a closed connection
1444                 //  4   null                non-null                   Open              No             connection.Enlist(currentTransaction);  currentTransaction enlisted and used                                         N/A - it is not possible to EnlistTransaction if another transaction has already enlisted
1445                 //  5   non-null            null                       Open              No             no-op;                                  implicit transaction will be created and used                                explicit transaction should be used
1446                 //  6   non-null            null                       Closed            Yes            no-op;                                  implicit transaction will be created and used                                N/A - cannot enlist in transaction on a closed connection
1447                 //  7   non-null tx1        non-null tx2               Open              No             connection.Enlist(currentTransaction);  currentTransaction enlisted and used                                         N/A - it is not possible to EnlistTransaction if another transaction has already enlisted
1448                 //  8   non-null tx1        non-null tx2               Open              Yes            connection.Close(); connection.Open();  Re-opening connection will automatically enlist into Transaction.Current     N/A - only applies to TransactionScope - requires two transactions and CommitableTransaction and TransactionScope cannot be mixed
1449                 //  9   non-null tx1        non-null tx2               Closed            Yes            connection.Open();                      Opening connection will automatcially enlist into Transaction.Current        N/A - cannot enlist in transaction on a closed connection
1450 
1451                 Transaction currentTransaction = Transaction.Current;
1452 
1453                 bool transactionHasChanged = (null != currentTransaction && !currentTransaction.Equals(_lastTransaction))
1454                                           || (null != _lastTransaction && !_lastTransaction.Equals(currentTransaction));
1455 
1456                 if (transactionHasChanged)
1457                 {
1458                     if (!_openedConnection)
1459                     {
1460                         // We didn't open the connection so, just try to enlist the connection in the current transaction.
1461                         // Note that the connection can already be enlisted in a transaction (since the user opened
1462                         // it s/he could enlist it manually using EntityConnection.EnlistTransaction() method). If the
1463                         // transaction the connection is enlisted in has not completed (e.g. nested transaction) this call
1464                         // will fail (throw). Also currentTransaction can be null here which means that the transaction
1465                         // used in the previous operation has completed. In this case we should not enlist the connection
1466                         // in "null" transaction as the user might have enlisted in a transaction manually between calls by
1467                         // calling EntityConnection.EnlistTransaction() method. Enlisting with null would in this case mean "unenlist"
1468                         // and would cause an exception (see above). Had the user not enlisted in a transaction between the calls
1469                         // enlisting with null would be a no-op - so again no reason to do it.
1470                         if (currentTransaction != null)
1471                         {
1472                             _connection.EnlistTransaction(currentTransaction);
1473                         }
1474                     }
1475                     else if (_connectionRequestCount > 1)
1476                     {
1477                         // We opened the connection. In addition we are here because there are multiple
1478                         // active requests going on (read: enumerators that has not been disposed yet)
1479                         // using the same connection. (If there is only one active request e.g. like SaveChanges
1480                         // or single enumerator there is no need for any specific transaction handling - either
1481                         // we use the implicit ambient transaction (Transaction.Current) if one exists or we
1482                         // will create our own local transaction. Also if there is only one active request
1483                         // the user could not enlist it in a transaction using EntityConnection.EnlistTransaction()
1484                         // because we opened the connection).
1485                         // If there are multiple active requests the user might have "played" with transactions
1486                         // after the first transaction. This code tries to deal with this kind of changes.
1487 
1488                         if (null == _lastTransaction)
1489                         {
1490                             Debug.Assert(currentTransaction != null, "transaction has changed and the lastTransaction was null");
1491 
1492                             // Two cases here:
1493                             // - the previous operation was not run inside a transaction created by the user while this one is - just
1494                             //   enlist the connection in the transaction
1495                             // - the previous operation ran withing explicit transaction started with EntityConnection.EnlistTransaction()
1496                             //   method - try enlisting the connection in the transaction. This may fail however if the transactions
1497                             //   are nested as you cannot enlist the connection in the transaction until the previous transaction has
1498                             //   completed.
1499                             _connection.EnlistTransaction(currentTransaction);
1500                         }
1501                         else
1502                         {
1503                             // We'll close and reopen the connection to get the benefit of automatic transaction enlistment.
1504                             // Remarks: We get here only if there is more than one active query (e.g. nested foreach or two subsequent queries or SaveChanges
1505                             // inside a for each) and each of these queries are using a different transaction (note that using TransactionScopeOption.Required
1506                             // will not create a new transaction if an ambient transaction already exists - the ambient transaction will be used and we will
1507                             // not end up in this code path). If we get here we are already in a loss-loss situation - we cannot enlist to the second transaction
1508                             // as this would cause an exception saying that there is already an active transaction that needs to be committed or rolled back
1509                             // before we can enlist the connection to a new transaction. The other option (and this is what we do here) is to close and reopen
1510                             // the connection. This will enlist the newly opened connection to the second transaction but will also close the reader being used
1511                             // by the first active query. As a result when trying to continue reading results from the first query the user will get an exception
1512                             // saying that calling "Read" on a closed data reader is not a valid operation.
1513                             _connection.Close();
1514                             _connection.Open();
1515                             _openedConnection = true;
1516                             _connectionRequestCount++;
1517                         }
1518                     }
1519                 }
1520                 else
1521                 {
1522                     // we don't need to do anything, nothing has changed.
1523                 }
1524 
1525                 // If we get here, we have an open connection, either enlisted in the current
1526                 // transaction (if it's non-null) or unenlisted from all transactions (if the
1527                 // current transaction is null)
1528                 _lastTransaction = currentTransaction;
1529 
1530                 #endregion
1531             }
1532             catch (Exception)
1533             {
1534                 // when the connection is unable to enlist properly or another error occured, be sure to release this connection
1535                 ReleaseConnection();
1536                 throw;
1537             }
1538 
1539         }
1540 
1541         /// <summary>
1542         /// Resets the state of connection management when the connection becomes closed.
1543         /// </summary>
1544         /// <param name="sender"></param>
1545         /// <param name="e"></param>
ConnectionStateChange(object sender, StateChangeEventArgs e)1546         private void ConnectionStateChange(object sender, StateChangeEventArgs e)
1547         {
1548             if (e.CurrentState == ConnectionState.Closed)
1549             {
1550                 _connectionRequestCount = 0;
1551                 _openedConnection = false;
1552             }
1553         }
1554 
1555         /// <summary>
1556         /// Releases the connection, potentially closing the connection if no active operations
1557         /// require the connection to be open. There should be a single ReleaseConnection call
1558         /// for each EnsureConnection call.
1559         /// </summary>
1560         /// <exception cref="ObjectDisposedException">If the <see cref="ObjectContext"/> instance has been disposed.</exception>
ReleaseConnection()1561         internal void ReleaseConnection()
1562         {
1563             if (_connection == null)
1564             {
1565                 throw EntityUtil.ObjectContextDisposed();
1566             }
1567 
1568             if (_openedConnection)
1569             {
1570                 Debug.Assert(_connectionRequestCount > 0, "_connectionRequestCount is zero or negative");
1571                 if (_connectionRequestCount > 0)
1572                 {
1573                     _connectionRequestCount--;
1574                 }
1575 
1576                 // When no operation is using the connection and the context had opened the connection
1577                 // the connection can be closed
1578                 if (_connectionRequestCount == 0)
1579                 {
1580                     Connection.Close();
1581                     _openedConnection = false;
1582                 }
1583             }
1584         }
1585 
EnsureMetadata()1586         internal void EnsureMetadata()
1587         {
1588             if (!MetadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace))
1589             {
1590                 Debug.Assert(!MetadataWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.CSSpace), "ObjectContext has C-S metadata but not S?");
1591 
1592                 // Only throw an ObjectDisposedException if an attempt is made to access the underlying connection object.
1593                 if (_connection == null)
1594                 {
1595                     throw EntityUtil.ObjectContextDisposed();
1596                 }
1597 
1598                 MetadataWorkspace connectionWorkspace = _connection.GetMetadataWorkspace();
1599 
1600                 Debug.Assert(connectionWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.CSpace) &&
1601                              connectionWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.SSpace) &&
1602                              connectionWorkspace.IsItemCollectionAlreadyRegistered(DataSpace.CSSpace),
1603                             "EntityConnection.GetMetadataWorkspace() did not return an initialized workspace?");
1604 
1605                 // Validate that the context's MetadataWorkspace and the underlying connection's MetadataWorkspace
1606                 // have the same CSpace collection. Otherwise, an error will occur when trying to set the SSpace
1607                 // and CSSpace metadata
1608                 ItemCollection connectionCSpaceCollection = connectionWorkspace.GetItemCollection(DataSpace.CSpace);
1609                 ItemCollection contextCSpaceCollection = MetadataWorkspace.GetItemCollection(DataSpace.CSpace);
1610                 if (!object.ReferenceEquals(connectionCSpaceCollection, contextCSpaceCollection))
1611                 {
1612                     throw EntityUtil.ContextMetadataHasChanged();
1613                 }
1614                 MetadataWorkspace.RegisterItemCollection(connectionWorkspace.GetItemCollection(DataSpace.SSpace));
1615                 MetadataWorkspace.RegisterItemCollection(connectionWorkspace.GetItemCollection(DataSpace.CSSpace));
1616             }
1617         }
1618 
1619         #endregion
1620 
1621         /// <summary>
1622         /// Creates an ObjectQuery<typeparamref name="T"/> over the store, ready to be executed.
1623         /// </summary>
1624         /// <typeparam name="T">type of the query result</typeparam>
1625         /// <param name="queryString">the query string to be executed</param>
1626         /// <param name="parameters">parameters to pass to the query</param>
1627         /// <returns>an ObjectQuery instance, ready to be executed</returns>
CreateQuery(string queryString, params ObjectParameter[] parameters)1628         public ObjectQuery<T> CreateQuery<T>(string queryString, params ObjectParameter[] parameters)
1629         {
1630             EntityUtil.CheckArgumentNull(queryString, "queryString");
1631             EntityUtil.CheckArgumentNull(parameters, "parameters");
1632 
1633             // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
1634             // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
1635             // We either auto-load <T>'s assembly into the ObjectItemCollection or we auto-load the user's calling assembly and its referenced assemblies.
1636             // If the entities in the user's result spans multiple assemblies, the user must manually call LoadFromAssembly.
1637             // *GetCallingAssembly returns the assembly of the method that invoked the currently executing method.
1638             MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(T), System.Reflection.Assembly.GetCallingAssembly());
1639 
1640             // create a ObjectQuery<T> with default settings
1641             ObjectQuery<T> query = new ObjectQuery<T>(queryString, this, MergeOption.AppendOnly);
1642 
1643             foreach (ObjectParameter parameter in parameters)
1644             {
1645                 query.Parameters.Add(parameter);
1646             }
1647 
1648             return query;
1649         }
1650         /// <summary>
1651         /// Creates an EntityConnection from the given connection string.
1652         /// </summary>
1653         /// <param name="connectionString">the connection string</param>
1654         /// <returns>the newly created connection</returns>
1655         [ResourceExposure(ResourceScope.Machine)] //Exposes the file names as part of ConnectionString which are a Machine resource
1656         [ResourceConsumption(ResourceScope.Machine)] //For EntityConnection constructor. But the paths are not created in this method.
CreateEntityConnection(string connectionString)1657         private static EntityConnection CreateEntityConnection(string connectionString)
1658         {
1659             EntityUtil.CheckStringArgument(connectionString, "connectionString");
1660 
1661             // create the connection
1662             EntityConnection connection = new EntityConnection(connectionString);
1663 
1664             return connection;
1665         }
1666         /// <summary>
1667         /// Given an entity connection, returns a copy of its MetadataWorkspace. Ensure we get
1668         /// all of the metadata item collections by priming the entity connection.
1669         /// </summary>
1670         /// <returns></returns>
1671         /// <exception cref="ObjectDisposedException">If the <see cref="ObjectContext"/> instance has been disposed.</exception>
RetrieveMetadataWorkspaceFromConnection()1672         private MetadataWorkspace RetrieveMetadataWorkspaceFromConnection()
1673         {
1674             if (_connection == null)
1675             {
1676                 throw EntityUtil.ObjectContextDisposed();
1677             }
1678 
1679             MetadataWorkspace connectionWorkspace = _connection.GetMetadataWorkspace(false /* initializeAllConnections */);
1680             Debug.Assert(connectionWorkspace != null, "EntityConnection.MetadataWorkspace is null.");
1681 
1682             // Create our own workspace
1683             MetadataWorkspace workspace = connectionWorkspace.ShallowCopy();
1684 
1685             return workspace;
1686         }
1687         /// <summary>
1688         /// Marks an object for deletion from the cache.
1689         /// </summary>
1690         /// <param name="entity">Object to be deleted.</param>
DeleteObject(object entity)1691         public void DeleteObject(object entity)
1692         {
1693             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1694             // This method and ObjectSet.DeleteObject are expected to have identical behavior except for the extra validation ObjectSet
1695             // requests by passing a non-null expectedEntitySetName. Any changes to this method are expected to be made in the common
1696             // internal overload below that ObjectSet also uses, unless there is a specific reason why a behavior is desired when the
1697             // call comes from ObjectContext only.
1698             DeleteObject(entity, null /*expectedEntitySetName*/);
1699             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1700         }
1701 
1702         /// <summary>
1703         /// Common DeleteObject method that is used by both ObjectContext.DeleteObject and ObjectSet.DeleteObject.
1704         /// </summary>
1705         /// <param name="entity">Object to be deleted.</param>
1706         /// <param name="expectedEntitySet">
1707         /// EntitySet that the specified object is expected to be in. Null if the caller doesn't want to validate against a particular EntitySet.
1708         /// </param>
DeleteObject(object entity, EntitySet expectedEntitySet)1709         internal void DeleteObject(object entity, EntitySet expectedEntitySet)
1710         {
1711             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
1712             EntityUtil.CheckArgumentNull(entity, "entity");
1713 
1714             EntityEntry cacheEntry = this.ObjectStateManager.FindEntityEntry(entity);
1715             if (cacheEntry == null || !object.ReferenceEquals(cacheEntry.Entity, entity))
1716             {
1717                 throw EntityUtil.CannotDeleteEntityNotInObjectStateManager();
1718             }
1719 
1720             if (expectedEntitySet != null)
1721             {
1722                 EntitySetBase actualEntitySet = cacheEntry.EntitySet;
1723                 if (actualEntitySet != expectedEntitySet)
1724                 {
1725                     throw EntityUtil.EntityNotInObjectSet_Delete(actualEntitySet.EntityContainer.Name, actualEntitySet.Name, expectedEntitySet.EntityContainer.Name, expectedEntitySet.Name);
1726                 }
1727             }
1728 
1729             cacheEntry.Delete();
1730             // Detaching from the context happens when the object
1731             // actually detaches from the cache (not just when it is
1732             // marked for deletion).
1733         }
1734 
1735         /// <summary>
1736         /// Detach entity from the cache.
1737         /// </summary>
1738         /// <param name="entity">Object to be detached.</param>
Detach(object entity)1739         public void Detach(object entity)
1740         {
1741             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1742             // This method and ObjectSet.DetachObject are expected to have identical behavior except for the extra validation ObjectSet
1743             // requests by passing a non-null expectedEntitySetName. Any changes to this method are expected to be made in the common
1744             // internal overload below that ObjectSet also uses, unless there is a specific reason why a behavior is desired when the
1745             // call comes from ObjectContext only.
1746             Detach(entity, null /*expectedEntitySet*/);
1747             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1748         }
1749 
1750         /// <summary>
1751         /// Common Detach method that is used by both ObjectContext.Detach and ObjectSet.Detach.
1752         /// </summary>
1753         /// <param name="entity">Object to be detached.</param>
1754         /// <param name="expectedEntitySet">
1755         /// EntitySet that the specified object is expected to be in. Null if the caller doesn't want to validate against a particular EntitySet.
1756         /// </param>
Detach(object entity, EntitySet expectedEntitySet)1757         internal void Detach(object entity, EntitySet expectedEntitySet)
1758         {
1759             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
1760             EntityUtil.CheckArgumentNull(entity, "entity");
1761 
1762             EntityEntry cacheEntry = this.ObjectStateManager.FindEntityEntry(entity);
1763             if (cacheEntry == null ||
1764                 !object.ReferenceEquals(cacheEntry.Entity, entity) ||
1765                 cacheEntry.Entity == null) // this condition includes key entries and relationship entries
1766             {
1767                 throw EntityUtil.CannotDetachEntityNotInObjectStateManager();
1768             }
1769 
1770             if (expectedEntitySet != null)
1771             {
1772                 EntitySetBase actualEntitySet = cacheEntry.EntitySet;
1773                 if (actualEntitySet != expectedEntitySet)
1774                 {
1775                     throw EntityUtil.EntityNotInObjectSet_Detach(actualEntitySet.EntityContainer.Name, actualEntitySet.Name, expectedEntitySet.EntityContainer.Name, expectedEntitySet.Name);
1776                 }
1777             }
1778 
1779             cacheEntry.Detach();
1780         }
1781 
1782         /// <summary>
1783         /// Disposes this ObjectContext.
1784         /// </summary>
Dispose()1785         public void Dispose()
1786         {
1787             // Technically, calling GC.SuppressFinalize is not required because the class does not
1788             // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1789             // in the future, and prevents an FxCop warning.
1790             GC.SuppressFinalize(this);
1791             Dispose(true);
1792         }
1793 
1794         /// <summary>
1795         /// Disposes this ObjectContext.
1796         /// </summary>
1797         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
Dispose(bool disposing)1798         protected virtual void Dispose(bool disposing)
1799         {
1800             if (disposing)
1801             {
1802                 // Release managed resources here.
1803 
1804                 if (_connection != null)
1805                 {
1806                     _connection.StateChange -= ConnectionStateChange;
1807 
1808                     // Dispose the connection the ObjectContext created
1809                     if (_createdConnection)
1810                     {
1811                         _connection.Dispose();
1812                     }
1813                 }
1814                 _connection = null; // Marks this object as disposed.
1815                 _adapter = null;
1816                 if (_cache != null)
1817                 {
1818                     _cache.Dispose();
1819                 }
1820             }
1821             // Release unmanaged resources here (none for this class).
1822         }
1823 
1824         #region GetEntitySet
1825 
1826         /// <summary>
1827         /// Returns the EntitySet with the given name from given container.
1828         /// </summary>
1829         /// <param name="entitySetName">name of entity set</param>
1830         /// <param name="entityContainerName">name of container</param>
1831         /// <returns>the appropriate EntitySet</returns>
1832         /// <exception cref="InvalidOperationException">the entity set could not be found for the given name</exception>
1833         /// <exception cref="InvalidOperationException">the entity container could not be found for the given name</exception>
GetEntitySet(string entitySetName, string entityContainerName)1834         internal EntitySet GetEntitySet(string entitySetName, string entityContainerName)
1835         {
1836             Debug.Assert(entitySetName != null, "entitySetName should be not null");
1837 
1838             EntityContainer container = null;
1839 
1840             if (String.IsNullOrEmpty(entityContainerName))
1841             {
1842                 container = this.Perspective.GetDefaultContainer();
1843                 Debug.Assert(container != null, "Problem with metadata - default container not found");
1844             }
1845             else
1846             {
1847                 if (!this.MetadataWorkspace.TryGetEntityContainer(entityContainerName, DataSpace.CSpace, out container))
1848                 {
1849                     throw EntityUtil.EntityContainterNotFoundForName(entityContainerName);
1850                 }
1851             }
1852 
1853             EntitySet entitySet = null;
1854 
1855             if (!container.TryGetEntitySetByName(entitySetName, false, out entitySet))
1856             {
1857                 throw EntityUtil.EntitySetNotFoundForName(TypeHelpers.GetFullName(container.Name, entitySetName));
1858             }
1859 
1860             return entitySet;
1861         }
1862 
GetEntitySetName(string qualifiedName, string parameterName, ObjectContext context, out string entityset, out string container)1863         private static void GetEntitySetName(string qualifiedName, string parameterName, ObjectContext context, out  string entityset, out string container)
1864         {
1865             entityset = null;
1866             container = null;
1867             EntityUtil.CheckStringArgument(qualifiedName, parameterName);
1868 
1869             string[] result = qualifiedName.Split('.');
1870             if (result.Length > 2)
1871             {
1872                 throw EntityUtil.QualfiedEntitySetName(parameterName);
1873             }
1874             if (result.Length == 1) // if not '.' at all
1875             {
1876                 entityset = result[0];
1877             }
1878             else
1879             {
1880                 container = result[0];
1881                 entityset = result[1];
1882                 if (container == null || container.Length == 0) // if it starts with '.'
1883                 {
1884                     throw EntityUtil.QualfiedEntitySetName(parameterName);
1885                 }
1886             }
1887             if (entityset == null || entityset.Length == 0) // if it's not in the form "ES name . containername"
1888             {
1889                 throw EntityUtil.QualfiedEntitySetName(parameterName);
1890             }
1891 
1892             if (context != null && String.IsNullOrEmpty(container) && (context.Perspective.GetDefaultContainer() == null))
1893             {
1894                 throw EntityUtil.ContainerQualifiedEntitySetNameRequired(parameterName);
1895             }
1896         }
1897 
1898         /// <summary>
1899         /// Validate that an EntitySet is compatible with a given entity instance's CLR type.
1900         /// </summary>
1901         /// <param name="entitySet">an EntitySet</param>
1902         /// <param name="entityType">The CLR type of an entity instance</param>
ValidateEntitySet(EntitySet entitySet, Type entityType)1903         private void ValidateEntitySet(EntitySet entitySet, Type entityType)
1904         {
1905             TypeUsage entityTypeUsage = GetTypeUsage(entityType);
1906             if (!entitySet.ElementType.IsAssignableFrom(entityTypeUsage.EdmType))
1907             {
1908                 throw EntityUtil.InvalidEntitySetOnEntity(entitySet.Name, entityType, "entity");
1909             }
1910         }
1911 
GetTypeUsage(Type entityCLRType)1912         internal TypeUsage GetTypeUsage(Type entityCLRType)
1913         {
1914             // Register the assembly so the type information will be sure to be loaded in metadata
1915             this.MetadataWorkspace.ImplicitLoadAssemblyForType(entityCLRType, System.Reflection.Assembly.GetCallingAssembly());
1916 
1917             TypeUsage entityTypeUsage = null;
1918             if (!this.Perspective.TryGetType(entityCLRType, out entityTypeUsage) ||
1919                 !TypeSemantics.IsEntityType(entityTypeUsage))
1920             {
1921                 throw EntityUtil.InvalidEntityType(entityCLRType);
1922             }
1923             Debug.Assert(entityTypeUsage != null, "entityTypeUsage is null");
1924             return entityTypeUsage;
1925         }
1926         #endregion
1927 
1928         /// <summary>
1929         /// Retrieves an object from the cache if present or from the
1930         /// store if not.
1931         /// </summary>
1932         /// <param name="key">Key of the object to be found.</param>
1933         /// <returns>Entity object.</returns>
GetObjectByKey(EntityKey key)1934         public object GetObjectByKey(EntityKey key)
1935         {
1936             EntityUtil.CheckArgumentNull(key, "key");
1937 
1938             EntitySet entitySet = key.GetEntitySet(this.MetadataWorkspace);
1939             Debug.Assert(entitySet != null, "Key's EntitySet should not be null in the MetadataWorkspace");
1940 
1941             // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
1942             // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
1943             // Either the entity type's assembly is already in the ObjectItemCollection or we auto-load the user's calling assembly and its referenced assemblies.
1944             // *GetCallingAssembly returns the assembly of the method that invoked the currently executing method.
1945             MetadataWorkspace.ImplicitLoadFromEntityType(entitySet.ElementType, System.Reflection.Assembly.GetCallingAssembly());
1946 
1947             object entity;
1948             if (!TryGetObjectByKey(key, out entity))
1949             {
1950                 throw EntityUtil.ObjectNotFound();
1951             }
1952             return entity;
1953         }
1954 
1955         #region Refresh
1956         /// <summary>
1957         /// Refreshing cache data with store data for specific entities.
1958         /// The order in which entites are refreshed is non-deterministic.
1959         /// </summary>
1960         /// <param name="refreshMode">Determines how the entity retrieved from the store is merged with the entity in the cache</param>
1961         /// <param name="collection">must not be null and all entities must be attached to this context. May be empty.</param>
1962         /// <exception cref="ArgumentOutOfRangeException">if refreshMode is not valid</exception>
1963         /// <exception cref="ArgumentNullException">collection is null</exception>
1964         /// <exception cref="ArgumentException">collection contains null or non entities or entities not attached to this context</exception>
Refresh(RefreshMode refreshMode, IEnumerable collection)1965         public void Refresh(RefreshMode refreshMode, IEnumerable collection)
1966         {
1967             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1968             try
1969             {
1970                 EntityUtil.CheckArgumentRefreshMode(refreshMode);
1971                 EntityUtil.CheckArgumentNull(collection, "collection");
1972 
1973                 // collection may not contain any entities -- this is valid for this overload
1974                 RefreshEntities(refreshMode, collection);
1975             }
1976             finally
1977             {
1978                 ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1979             }
1980         }
1981         /// <summary>
1982         /// Refreshing cache data with store data for a specific entity.
1983         /// </summary>
1984         /// <param name="refreshMode">Determines how the entity retrieved from the store is merged with the entity in the cache</param>
1985         /// <param name="entity">The entity to refresh. This must be a non-null entity that is attached to this context</param>
1986         /// <exception cref="ArgumentOutOfRangeException">if refreshMode is not valid</exception>
1987         /// <exception cref="ArgumentNullException">entity is null</exception>
1988         /// <exception cref="ArgumentException">entity is not attached to this context</exception>
Refresh(RefreshMode refreshMode, object entity)1989         public void Refresh(RefreshMode refreshMode, object entity)
1990         {
1991             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
1992             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
1993             try
1994             {
1995                 EntityUtil.CheckArgumentRefreshMode(refreshMode);
1996                 EntityUtil.CheckArgumentNull(entity, "entity");
1997 
1998                 RefreshEntities(refreshMode, new object[] { entity });
1999             }
2000             finally
2001             {
2002                 ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
2003             }
2004         }
2005 
2006         /// <summary>
2007         /// Validates that the given entity/key pair has an ObjectStateEntry
2008         /// and that entry is not in the added state.
2009         ///
2010         /// The entity is added to the entities dictionary, and checked for duplicates.
2011         /// </summary>
2012         /// <param name="entities">on exit, entity is added to this dictionary.</param>
2013         /// <param name="entity">An object reference that is not "Added," has an ObjectStateEntry and is not in the entities list.</param>
2014         /// <param name="key"></param>
RefreshCheck( Dictionary<EntityKey, EntityEntry> entities, object entity, EntityKey key)2015         private void RefreshCheck(
2016             Dictionary<EntityKey, EntityEntry> entities,
2017             object entity, EntityKey key)
2018         {
2019             Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
2020             Debug.Assert(entity != null, "The entity is null.");
2021 
2022             EntityEntry entry = ObjectStateManager.FindEntityEntry(key);
2023             if (null == entry)
2024             {
2025                 throw EntityUtil.NthElementNotInObjectStateManager(entities.Count);
2026             }
2027             if (EntityState.Added == entry.State)
2028             {
2029                 throw EntityUtil.NthElementInAddedState(entities.Count);
2030             }
2031             Debug.Assert(EntityState.Added != entry.State, "not expecting added");
2032             Debug.Assert(EntityState.Detached != entry.State, "not expecting detached");
2033 
2034             try
2035             {
2036                 entities.Add(key, entry); // don't ignore duplicates
2037             }
2038             catch (ArgumentException)
2039             {
2040                 throw EntityUtil.NthElementIsDuplicate(entities.Count);
2041             }
2042 
2043             Debug.Assert(null != entity, "null entity");
2044             Debug.Assert(null != (object)key, "null entity.Key");
2045             Debug.Assert(null != key.EntitySetName, "null entity.Key.EntitySetName");
2046         }
2047 
RefreshEntities(RefreshMode refreshMode, IEnumerable collection)2048         private void RefreshEntities(RefreshMode refreshMode, IEnumerable collection)
2049         {
2050             // refreshMode and collection should already be validated prior to this call -- collection can be empty in one Refresh overload
2051             // but not in the other, so we need to do that check before we get to this common method
2052             Debug.Assert(collection != null, "collection may not contain any entities but should never be null");
2053 
2054             bool openedConnection = false;
2055 
2056             try
2057             {
2058                 Dictionary<EntityKey, EntityEntry> entities = new Dictionary<EntityKey, EntityEntry>(RefreshEntitiesSize(collection));
2059 
2060                 #region 1) Validate and bucket the entities by entity set
2061                 Dictionary<EntitySet, List<EntityKey>> refreshKeys = new Dictionary<EntitySet, List<EntityKey>>();
2062                 foreach (object entity in collection) // anything other than object risks InvalidCastException
2063                 {
2064                     AddRefreshKey(entity, entities, refreshKeys);
2065                 }
2066 
2067                 // The collection is no longer required at this point.
2068                 collection = null;
2069                 #endregion
2070 
2071                 #region 2) build and execute the query for each set of entities
2072                 if (refreshKeys.Count > 0)
2073                 {
2074                     EnsureConnection();
2075                     openedConnection = true;
2076 
2077                     // All entities from a single set can potentially be refreshed in the same query.
2078                     // However, the refresh operations are batched in an attempt to avoid the generation
2079                     // of query trees or provider SQL that exhaust available client or server resources.
2080                     foreach (EntitySet targetSet in refreshKeys.Keys)
2081                     {
2082                         List<EntityKey> setKeys = refreshKeys[targetSet];
2083                         int refreshedCount = 0;
2084                         while (refreshedCount < setKeys.Count)
2085                         {
2086                             refreshedCount = BatchRefreshEntitiesByKey(refreshMode, entities, targetSet, setKeys, refreshedCount);
2087                         }
2088                     }
2089                 }
2090 
2091                 // The refreshKeys list is no longer required at this point.
2092                 refreshKeys = null;
2093                 #endregion
2094 
2095                 #region 3) process the unrefreshed entities
2096                 if (RefreshMode.StoreWins == refreshMode)
2097                 {
2098                     // remove all entites that have been removed from the store, not added by client
2099                     foreach (KeyValuePair<EntityKey, EntityEntry> item in entities)
2100                     {
2101                         Debug.Assert(EntityState.Added != item.Value.State, "should not be possible");
2102                         if (EntityState.Detached != item.Value.State)
2103                         {
2104                             // We set the detaching flag here even though we are deleting because we are doing a
2105                             // Delete/AcceptChanges cycle to simulate a Detach, but we can't use Detach directly
2106                             // because legacy behavior around cascade deletes should be preserved.  However, we
2107                             // do want to prevent FK values in dependents from being nulled, which is why we
2108                             // need to set the detaching flag.
2109                             ObjectStateManager.TransactionManager.BeginDetaching();
2110                             try
2111                             {
2112                                 item.Value.Delete();
2113                             }
2114                             finally
2115                             {
2116                                 ObjectStateManager.TransactionManager.EndDetaching();
2117                             }
2118                             Debug.Assert(EntityState.Detached != item.Value.State, "not expecting detached");
2119 
2120                             item.Value.AcceptChanges();
2121                         }
2122                     }
2123                 }
2124                 else if ((RefreshMode.ClientWins == refreshMode) && (0 < entities.Count))
2125                 {
2126                     // throw an exception with all appropriate entity keys in text
2127                     string prefix = String.Empty;
2128                     StringBuilder builder = new StringBuilder();
2129                     foreach (KeyValuePair<EntityKey, EntityEntry> item in entities)
2130                     {
2131                         Debug.Assert(EntityState.Added != item.Value.State, "should not be possible");
2132                         if (item.Value.State == EntityState.Deleted)
2133                         {
2134                             // Detach the deleted items because this is the client changes and the server
2135                             // does not have these items any more
2136                             item.Value.AcceptChanges();
2137                         }
2138                         else
2139                         {
2140                             builder.Append(prefix).Append(Environment.NewLine);
2141                             builder.Append('\'').Append(item.Key.ConcatKeyValue()).Append('\'');
2142                             prefix = ",";
2143                         }
2144                     }
2145                     // If there were items that could not be found, throw an exception
2146                     if (builder.Length > 0)
2147                     {
2148                         throw EntityUtil.ClientEntityRemovedFromStore(builder.ToString());
2149                     }
2150                 }
2151                 #endregion
2152             }
2153             finally
2154             {
2155                 if (openedConnection)
2156                 {
2157                     ReleaseConnection();
2158                 }
2159             }
2160         }
2161 
BatchRefreshEntitiesByKey(RefreshMode refreshMode, Dictionary<EntityKey, EntityEntry> trackedEntities, EntitySet targetSet, List<EntityKey> targetKeys, int startFrom)2162         private int BatchRefreshEntitiesByKey(RefreshMode refreshMode, Dictionary<EntityKey, EntityEntry> trackedEntities, EntitySet targetSet, List<EntityKey> targetKeys, int startFrom)
2163         {
2164             //
2165             // A single refresh query can be built for all entities from the same set.
2166             // For each entity set, a DbFilterExpression is constructed that
2167             // expresses the equivalent of:
2168             //
2169             // SELECT VALUE e
2170             // FROM <entityset> AS e
2171             // WHERE
2172             // GetRefKey(GetEntityRef(e)) == <ref1>.KeyValues
2173             // [OR GetRefKey(GetEntityRef(e)) == <ref2>.KeyValues
2174             // [..OR GetRefKey(GetEntityRef(e)) == <refN>.KeyValues]]
2175             //
2176             // Note that a LambdaFunctionExpression is used so that instead
2177             // of repeating GetRefKey(GetEntityRef(e)) a VariableReferenceExpression
2178             // to a Lambda argument with the value GetRefKey(GetEntityRef(e)) is used instead.
2179             // The query is therefore logically equivalent to:
2180             //
2181             // SELECT VALUE e
2182             // FROM <entityset> AS e
2183             // WHERE
2184             //   LET(x = GetRefKey(GetEntityRef(e)) IN (
2185             //      x == <ref1>.KeyValues
2186             //     [OR x == <ref2>.KeyValues
2187             //     [..OR x == <refN>.KeyValues]]
2188             //   )
2189             //
2190 
2191             // The batch size determines the maximum depth of the predicate OR tree and
2192             // also limits the size of the generated provider SQL that is sent to the server.
2193             const int maxBatch = 250;
2194 
2195             // Bind the target EntitySet under the name "EntitySet".
2196             DbExpressionBinding entitySetBinding = targetSet.Scan().BindAs("EntitySet");
2197 
2198             // Use the variable from the set binding as the 'e' in a new GetRefKey(GetEntityRef(e)) expression.
2199             DbExpression sourceEntityKey = entitySetBinding.Variable.GetEntityRef().GetRefKey();
2200 
2201             // Build the where predicate as described above. A maximum of <batchsize> entity keys will be included
2202             // in the predicate, starting from position <startFrom> in the list of entity keys. As each key is
2203             // included, both <batchsize> and <startFrom> are incremented to ensure that the batch size is
2204             // correctly constrained and that the new starting position for the next call to this method is calculated.
2205             int batchSize = Math.Min(maxBatch, (targetKeys.Count - startFrom));
2206             DbExpression[] keyFilters = new DbExpression[batchSize];
2207             for (int idx = 0; idx < batchSize; idx++)
2208             {
2209                 // Create a row constructor expression based on the key values of the EntityKey.
2210                 KeyValuePair<string, DbExpression>[] keyValueColumns = targetKeys[startFrom++].GetKeyValueExpressions(targetSet);
2211                 DbExpression keyFilter = DbExpressionBuilder.NewRow(keyValueColumns);
2212 
2213                 // Create an equality comparison between the row constructor and the lambda variable
2214                 // that refers to GetRefKey(GetEntityRef(e)), which also produces a row
2215                 // containing key values, but for the current entity from the entity set.
2216                 keyFilters[idx] = sourceEntityKey.Equal(keyFilter);
2217             }
2218 
2219             // Sanity check that the batch includes at least one element.
2220             Debug.Assert(batchSize > 0, "Didn't create a refresh expression?");
2221 
2222             // Build a balanced binary tree that OR's the key filters together.
2223             DbExpression entitySetFilter = Helpers.BuildBalancedTreeInPlace(keyFilters, DbExpressionBuilder.Or);
2224 
2225             // Create a FilterExpression based on the EntitySet binding and the Lambda predicate.
2226             // This FilterExpression encapsulated the logic required for the refresh query as described above.
2227             DbExpression refreshQuery = entitySetBinding.Filter(entitySetFilter);
2228 
2229             // Initialize the command tree used to issue the refresh query.
2230             DbQueryCommandTree tree = DbQueryCommandTree.FromValidExpression(this.MetadataWorkspace, DataSpace.CSpace, refreshQuery);
2231 
2232             // Evaluate the refresh query using ObjectQuery<T> and process the results to update the ObjectStateManager.
2233             MergeOption mergeOption = (RefreshMode.StoreWins == refreshMode ?
2234                                        MergeOption.OverwriteChanges :
2235                                        MergeOption.PreserveChanges);
2236 
2237             // The connection will be released by ObjectResult when enumeration is complete.
2238             this.EnsureConnection();
2239 
2240             try
2241             {
2242                 ObjectResult<object> results = ObjectQueryExecutionPlan.ExecuteCommandTree<object>(this, tree, mergeOption);
2243 
2244                 foreach (object entity in results)
2245                 {
2246                     // There is a risk that, during an event, the Entity removed itself from the cache.
2247                     EntityEntry entry = ObjectStateManager.FindEntityEntry(entity);
2248                     if ((null != entry) && (EntityState.Modified == entry.State))
2249                     {   // this is 'ForceChanges' - which is the same as PreserveChanges, except all properties are marked modified.
2250                         Debug.Assert(RefreshMode.ClientWins == refreshMode, "StoreWins always becomes unchanged");
2251                         entry.SetModifiedAll();
2252                     }
2253 
2254                     IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingContext(entity, this);
2255                     EntityKey key = wrappedEntity.EntityKey;
2256                     EntityUtil.CheckEntityKeyNull(key);
2257 
2258                     // Dev10#673631 - An incorrectly returned entity should result in an exception to avoid further corruption to the OSM.
2259                     if (!trackedEntities.Remove(key))
2260                     {
2261                         throw EntityUtil.StoreEntityNotPresentInClient();
2262                     }
2263                 }
2264             }
2265             catch
2266             {
2267                 // Enumeration did not complete, so the connection must be explicitly released.
2268                 this.ReleaseConnection();
2269                 throw;
2270             }
2271 
2272             // Return the position in the list from which the next refresh operation should start.
2273             // This will be equal to the list count if all remaining entities in the list were
2274             // refreshed during this call.
2275             return startFrom;
2276         }
2277 
RefreshEntitiesSize(IEnumerable collection)2278         private static int RefreshEntitiesSize(IEnumerable collection)
2279         {
2280             ICollection list = collection as ICollection;
2281             return ((null != list) ? list.Count : 0);
2282         }
2283         #endregion
2284 
2285         #region SaveChanges
2286         /// <summary>
2287         /// Persists all updates to the store.
2288         /// </summary>
2289         /// <returns>
2290         ///   the number of dirty (i.e., Added, Modified, or Deleted) ObjectStateEntries
2291         ///   in the ObjectStateManager when SaveChanges was called.
2292         /// </returns>
SaveChanges()2293         public Int32 SaveChanges()
2294         {
2295             return SaveChanges(SaveOptions.DetectChangesBeforeSave | SaveOptions.AcceptAllChangesAfterSave);
2296         }
2297 
2298 
2299         /// <summary>
2300         ///   Persists all updates to the store.
2301         ///   This API is obsolete.  Please use SaveChanges(SaveOptions options) instead.
2302         ///   SaveChanges(true) is equivalent to SaveChanges() -- That is it detects changes and
2303         ///      accepts all changes after save.
2304         ///   SaveChanges(false) detects changes but does not accept changes after save.
2305         /// </summary>
2306         /// <param name="acceptChangesDuringSave">if false, user must call AcceptAllChanges</param>/>
2307         /// <returns>
2308         ///   the number of dirty (i.e., Added, Modified, or Deleted) ObjectStateEntries
2309         ///   in the ObjectStateManager when SaveChanges was called.
2310         /// </returns>
2311         [EditorBrowsable(EditorBrowsableState.Never)]
2312         [Browsable(false)]
2313         [Obsolete("Use SaveChanges(SaveOptions options) instead.")]
SaveChanges(bool acceptChangesDuringSave)2314         public Int32 SaveChanges(bool acceptChangesDuringSave)
2315         {
2316             return this.SaveChanges(acceptChangesDuringSave ? SaveOptions.DetectChangesBeforeSave | SaveOptions.AcceptAllChangesAfterSave
2317                                                             : SaveOptions.DetectChangesBeforeSave);
2318         }
2319 
2320         /// <summary>
2321         /// Persists all updates to the store.
2322         /// </summary>
2323         /// <param name="options">describes behavior options of SaveChanges</param>
2324         /// <returns>
2325         ///   the number of dirty (i.e., Added, Modified, or Deleted) ObjectStateEntries
2326         ///   in the ObjectStateManager processed by SaveChanges.
2327         /// </returns>
SaveChanges(SaveOptions options)2328         public virtual Int32 SaveChanges(SaveOptions options)
2329         {
2330             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
2331 
2332             OnSavingChanges();
2333 
2334             if ((SaveOptions.DetectChangesBeforeSave & options) != 0)
2335             {
2336                 this.ObjectStateManager.DetectChanges();
2337             }
2338 
2339             if (ObjectStateManager.SomeEntryWithConceptualNullExists())
2340             {
2341                 throw new InvalidOperationException(Strings.ObjectContext_CommitWithConceptualNull);
2342             }
2343 
2344             bool mustReleaseConnection = false;
2345             Int32 entriesAffected = ObjectStateManager.GetObjectStateEntriesCount(EntityState.Added | EntityState.Deleted | EntityState.Modified);
2346             EntityConnection connection = (EntityConnection)Connection;
2347 
2348             if (0 < entriesAffected)
2349             {   // else fast exit if no changes to save to avoids interacting with or starting of new transactions
2350 
2351                 // get data adapter
2352                 if (_adapter == null)
2353                 {
2354                     IServiceProvider sp = DbProviderFactories.GetFactory(connection) as IServiceProvider;
2355                     if (sp != null)
2356                     {
2357                         _adapter = sp.GetService(typeof(IEntityAdapter)) as IEntityAdapter;
2358                     }
2359                     if (_adapter == null)
2360                     {
2361                         throw EntityUtil.InvalidDataAdapter();
2362                     }
2363                 }
2364                 // only accept changes after the local transaction commits
2365                 _adapter.AcceptChangesDuringUpdate = false;
2366                 _adapter.Connection = connection;
2367                 _adapter.CommandTimeout = this.CommandTimeout;
2368 
2369                 try
2370                 {
2371                     EnsureConnection();
2372                     mustReleaseConnection = true;
2373 
2374                     // determine what transaction to enlist in
2375                     bool needLocalTransaction = false;
2376 
2377                     if (null == connection.CurrentTransaction && !connection.EnlistedInUserTransaction)
2378                     {
2379                         // If there isn't a local transaction started by the user, we'll attempt to enlist
2380                         // on the current SysTx transaction so we don't need to construct a local
2381                         // transaction.
2382 
2383                         needLocalTransaction = (null == _lastTransaction);
2384                     }
2385                     // else the user already has his own local transaction going; user will do the abort or commit.
2386 
2387                     DbTransaction localTransaction = null;
2388                     try
2389                     {
2390                         // EntityConnection tracks the CurrentTransaction we don't need to pass it around
2391                         if (needLocalTransaction)
2392                         {
2393                             localTransaction = connection.BeginTransaction();
2394                         }
2395                         entriesAffected = _adapter.Update(ObjectStateManager);
2396 
2397                         if (null != localTransaction)
2398                         {   // we started the local transaction; so we also commit it
2399                             localTransaction.Commit();
2400                         }
2401                         // else on success with no exception is thrown, user generally commits the transaction
2402                     }
2403                     finally
2404                     {
2405                         if (null != localTransaction)
2406                         {   // we started the local transaction; so it requires disposal (rollback if not previously committed
2407                             localTransaction.Dispose();
2408                         }
2409                         // else on failure with an exception being thrown, user generally aborts (default action with transaction without an explict commit)
2410                     }
2411                 }
2412                 finally
2413                 {
2414                     if (mustReleaseConnection)
2415                     {
2416                         // Release the connection when we are done with the save
2417                         ReleaseConnection();
2418                     }
2419                 }
2420 
2421                 if ((SaveOptions.AcceptAllChangesAfterSave & options) != 0)
2422                 {   // only accept changes after the local transaction commits
2423 
2424                     try
2425                     {
2426                         AcceptAllChanges();
2427                     }
2428                     catch (Exception e)
2429                     {
2430                         // If AcceptAllChanges throw - let's inform user that changes in database were committed
2431                         // and that Context and Database can be in inconsistent state.
2432 
2433                         // We should not be wrapping all exceptions
2434                         if (EntityUtil.IsCatchableExceptionType(e))
2435                         {
2436                             throw EntityUtil.AcceptAllChangesFailure(e);
2437                         }
2438                         throw;
2439                     }
2440                 }
2441             }
2442             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
2443             return entriesAffected;
2444         }
2445         #endregion
2446 
2447         /// <summary>
2448         /// For every tracked entity which doesn't implement IEntityWithChangeTracker detect changes in the entity's property values
2449         /// and marks appropriate ObjectStateEntry as Modified.
2450         /// For every tracked entity which doesn't implement IEntityWithRelationships detect changes in its relationships.
2451         ///
2452         /// The method is used inter----ly by ObjectContext.SaveChanges() but can be also used if user wants to detect changes
2453         /// and have ObjectStateEntries in appropriate state before the SaveChanges() method is called.
2454         /// </summary>
DetectChanges()2455         public void DetectChanges()
2456         {
2457             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
2458             this.ObjectStateManager.DetectChanges();
2459             ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
2460         }
2461 
2462         /// <summary>
2463         /// Attempts to retrieve an object from the cache or the store.
2464         /// </summary>
2465         /// <param name="key">Key of the object to be found.</param>
2466         /// <param name="value">Out param for the object.</param>
2467         /// <returns>True if the object was found, false otherwise.</returns>
TryGetObjectByKey(EntityKey key, out object value)2468         public bool TryGetObjectByKey(EntityKey key, out object value)
2469         {
2470             // try the cache first
2471             EntityEntry entry;
2472             ObjectStateManager.TryGetEntityEntry(key, out entry); // this will check key argument
2473             if (entry != null)
2474             {
2475                 // can't find keys
2476                 if (!entry.IsKeyEntry)
2477                 {
2478                     // SQLBUDT 511296 returning deleted object.
2479                     value = entry.Entity;
2480                     return value != null;
2481                 }
2482             }
2483 
2484             if (key.IsTemporary)
2485             {
2486                 // If the key is temporary, we cannot find a corresponding object in the store.
2487                 value = null;
2488                 return false;
2489             }
2490 
2491             EntitySet entitySet = key.GetEntitySet(this.MetadataWorkspace);
2492             Debug.Assert(entitySet != null, "Key's EntitySet should not be null in the MetadataWorkspace");
2493 
2494             // Validate the EntityKey values against the EntitySet
2495             key.ValidateEntityKey(_workspace, entitySet, true /*isArgumentException*/, "key");
2496 
2497             // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type is loaded into the workspace.
2498             // If the schema types are not loaded: metadata, cache & query would be unable to reason about the type.
2499             // Either the entity type's assembly is already in the ObjectItemCollection or we auto-load the user's calling assembly and its referenced assemblies.
2500             // *GetCallingAssembly returns the assembly of the method that invoked the currently executing method.
2501             MetadataWorkspace.ImplicitLoadFromEntityType(entitySet.ElementType, System.Reflection.Assembly.GetCallingAssembly());
2502 
2503             // Execute the query:
2504             // SELECT VALUE X FROM [EC].[ES] AS X
2505             // WHERE X.KeyProp0 = @p0 AND X.KeyProp1 = @p1 AND ...
2506             // parameters are the key values
2507 
2508             // Build the Entity SQL query
2509             StringBuilder esql = new StringBuilder();
2510             esql.AppendFormat("SELECT VALUE X FROM {0}.{1} AS X WHERE ", EntityUtil.QuoteIdentifier(entitySet.EntityContainer.Name), EntityUtil.QuoteIdentifier(entitySet.Name));
2511             EntityKeyMember[] members = key.EntityKeyValues;
2512             ReadOnlyMetadataCollection<EdmMember> keyMembers = entitySet.ElementType.KeyMembers;
2513             ObjectParameter[] parameters = new ObjectParameter[members.Length];
2514 
2515             for (int i = 0; i < members.Length; i++)
2516             {
2517                 if (i > 0)
2518                 {
2519                     esql.Append(" AND ");
2520                 }
2521                 string parameterName = string.Format(CultureInfo.InvariantCulture, "p{0}", i.ToString(CultureInfo.InvariantCulture));
2522                 esql.AppendFormat("X.{0} = @{1}", EntityUtil.QuoteIdentifier(members[i].Key), parameterName);
2523                 parameters[i] = new ObjectParameter(parameterName, members[i].Value);
2524 
2525                 // Try to set the TypeUsage on the ObjectParameter
2526                 EdmMember keyMember = null;
2527                 if (keyMembers.TryGetValue(members[i].Key, true, out keyMember))
2528                 {
2529                     parameters[i].TypeUsage = keyMember.TypeUsage;
2530                 }
2531             }
2532 
2533             // Execute the query
2534             object entity = null;
2535             ObjectResult<object> results = CreateQuery<object>(esql.ToString(), parameters).Execute(MergeOption.AppendOnly);
2536             foreach (object queriedEntity in results)
2537             {
2538                 Debug.Assert(entity == null, "Query for a key returned more than one entity!");
2539                 entity = queriedEntity;
2540             }
2541 
2542             value = entity;
2543             return value != null;
2544         }
2545 
2546 
2547         /// <summary>
2548         /// Executes the given function on the default container.
2549         /// </summary>
2550         /// <typeparam name="TElement">Element type for function results.</typeparam>
2551         /// <param name="functionName">Name of function. May include container (e.g. ContainerName.FunctionName)
2552         /// or just function name when DefaultContainerName is known.</param>
2553         /// <param name="parameters"></param>
2554         /// <exception cref="ArgumentException">If function is null or empty</exception>
2555         /// <exception cref="InvalidOperationException">If function is invalid (syntax,
2556         /// does not exist, refers to a function with return type incompatible with T)</exception>
ExecuteFunction(string functionName, params ObjectParameter[] parameters)2557         public ObjectResult<TElement> ExecuteFunction<TElement>(string functionName, params ObjectParameter[] parameters)
2558         {
2559             return ExecuteFunction<TElement>(functionName, MergeOption.AppendOnly, parameters);
2560         }
2561 
2562         /// <summary>
2563         /// Executes the given function on the default container.
2564         /// </summary>
2565         /// <typeparam name="TElement">Element type for function results.</typeparam>
2566         /// <param name="functionName">Name of function. May include container (e.g. ContainerName.FunctionName)
2567         /// or just function name when DefaultContainerName is known.</param>
2568         /// <param name="mergeOption"></param>
2569         /// <param name="parameters"></param>
2570         /// <exception cref="ArgumentException">If function is null or empty</exception>
2571         /// <exception cref="InvalidOperationException">If function is invalid (syntax,
2572         /// does not exist, refers to a function with return type incompatible with T)</exception>
ExecuteFunction(string functionName, MergeOption mergeOption, params ObjectParameter[] parameters)2573         public ObjectResult<TElement> ExecuteFunction<TElement>(string functionName, MergeOption mergeOption, params ObjectParameter[] parameters)
2574         {
2575             EntityUtil.CheckStringArgument(functionName, "function");
2576             EntityUtil.CheckArgumentNull(parameters, "parameters");
2577 
2578             EdmFunction functionImport;
2579             EntityCommand entityCommand = CreateEntityCommandForFunctionImport(functionName, out functionImport, parameters);
2580             int returnTypeCount = Math.Max(1, functionImport.ReturnParameters.Count);
2581             EdmType[] expectedEdmTypes = new EdmType[returnTypeCount];
2582             expectedEdmTypes[0] = MetadataHelper.GetAndCheckFunctionImportReturnType<TElement>(functionImport, 0, this.MetadataWorkspace);
2583             for (int i = 1; i < returnTypeCount; i++)
2584             {
2585                 if (!MetadataHelper.TryGetFunctionImportReturnType<EdmType>(functionImport, i, out expectedEdmTypes[i]))
2586                 {
2587                     throw EntityUtil.ExecuteFunctionCalledWithNonReaderFunction(functionImport);
2588                 }
2589             }
2590 
2591             return CreateFunctionObjectResult<TElement>(entityCommand, functionImport.EntitySets, expectedEdmTypes, mergeOption);
2592         }
2593 
2594         /// <summary>
2595         /// Executes the given function on the default container and discard any results returned from the function.
2596         /// </summary>
2597         /// <param name="functionName">Name of function. May include container (e.g. ContainerName.FunctionName)
2598         /// or just function name when DefaultContainerName is known.</param>
2599         /// <param name="parameters"></param>
2600         /// <returns>Number of rows affected</returns>
2601         /// <exception cref="ArgumentException">If function is null or empty</exception>
2602         /// <exception cref="InvalidOperationException">If function is invalid (syntax,
2603         /// does not exist, refers to a function with return type incompatible with T)</exception>
ExecuteFunction(string functionName, params ObjectParameter[] parameters)2604         public int ExecuteFunction(string functionName, params ObjectParameter[] parameters)
2605         {
2606             EntityUtil.CheckStringArgument(functionName, "function");
2607             EntityUtil.CheckArgumentNull(parameters, "parameters");
2608 
2609             EdmFunction functionImport;
2610             EntityCommand entityCommand = CreateEntityCommandForFunctionImport(functionName, out functionImport, parameters);
2611 
2612             EnsureConnection();
2613 
2614             // Prepare the command before calling ExecuteNonQuery, so that exceptions thrown during preparation are not wrapped in CommandCompilationException
2615             entityCommand.Prepare();
2616 
2617             try
2618             {
2619                 return entityCommand.ExecuteNonQuery();
2620             }
2621             catch (Exception e)
2622             {
2623                 if (EntityUtil.IsCatchableEntityExceptionType(e))
2624                 {
2625                     throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_CommandExecutionFailed, e);
2626                 }
2627                 throw;
2628             }
2629             finally
2630             {
2631                 this.ReleaseConnection();
2632             }
2633         }
2634 
CreateEntityCommandForFunctionImport(string functionName, out EdmFunction functionImport, params ObjectParameter[] parameters)2635         private EntityCommand CreateEntityCommandForFunctionImport(string functionName, out EdmFunction functionImport, params ObjectParameter[] parameters)
2636         {
2637             for (int i = 0; i < parameters.Length; i++)
2638             {
2639                 ObjectParameter parameter = parameters[i];
2640                 if (null == parameter)
2641                 {
2642                     throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectContext_ExecuteFunctionCalledWithNullParameter(i));
2643                 }
2644             }
2645 
2646             string containerName;
2647             string functionImportName;
2648 
2649             functionImport =
2650                 MetadataHelper.GetFunctionImport(
2651                 functionName, this.DefaultContainerName, this.MetadataWorkspace,
2652                 out containerName, out functionImportName);
2653 
2654 
2655             EntityConnection connection = (EntityConnection)this.Connection;
2656 
2657             // create query
2658             EntityCommand entityCommand = new EntityCommand();
2659             entityCommand.CommandType = CommandType.StoredProcedure;
2660             entityCommand.CommandText = containerName + "." + functionImportName;
2661             entityCommand.Connection = connection;
2662             if (this.CommandTimeout.HasValue)
2663             {
2664                 entityCommand.CommandTimeout = this.CommandTimeout.Value;
2665             }
2666 
2667             PopulateFunctionImportEntityCommandParameters(parameters, functionImport, entityCommand);
2668 
2669             return entityCommand;
2670         }
2671 
CreateFunctionObjectResult(EntityCommand entityCommand, ReadOnlyMetadataCollection<EntitySet> entitySets, EdmType[] edmTypes, MergeOption mergeOption)2672         private ObjectResult<TElement> CreateFunctionObjectResult<TElement>(EntityCommand entityCommand, ReadOnlyMetadataCollection<EntitySet> entitySets, EdmType[] edmTypes, MergeOption mergeOption)
2673         {
2674             Debug.Assert(edmTypes != null && edmTypes.Length > 0);
2675             EnsureConnection();
2676 
2677             EntityCommandDefinition commandDefinition = entityCommand.GetCommandDefinition();
2678 
2679             // get store data reader
2680             DbDataReader storeReader;
2681             try
2682             {
2683                 storeReader = commandDefinition.ExecuteStoreCommands(entityCommand, CommandBehavior.Default);
2684             }
2685             catch (Exception e)
2686             {
2687                 this.ReleaseConnection();
2688                 if (EntityUtil.IsCatchableEntityExceptionType(e))
2689                 {
2690                     throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_CommandExecutionFailed, e);
2691                 }
2692                 throw;
2693             }
2694 
2695             return MaterializedDataRecord<TElement>(entityCommand, storeReader, 0, entitySets, edmTypes, mergeOption);
2696         }
2697 
2698         /// <summary>
2699         ///  Get the materializer for the resultSetIndexth result set of storeReader.
2700         /// </summary>
MaterializedDataRecord(EntityCommand entityCommand, DbDataReader storeReader, int resultSetIndex, ReadOnlyMetadataCollection<EntitySet> entitySets, EdmType[] edmTypes, MergeOption mergeOption)2701         internal ObjectResult<TElement> MaterializedDataRecord<TElement>(EntityCommand entityCommand, DbDataReader storeReader, int resultSetIndex, ReadOnlyMetadataCollection<EntitySet> entitySets, EdmType[] edmTypes, MergeOption mergeOption)
2702         {
2703             EntityCommandDefinition commandDefinition = entityCommand.GetCommandDefinition();
2704             try
2705             {
2706                 // We want the shaper to close the reader if it is the last result set.
2707                 bool shaperOwnsReader = edmTypes.Length <= resultSetIndex + 1;
2708                 EdmType edmType = edmTypes[resultSetIndex];
2709 
2710                 //Note: Defensive check for historic reasons, we expect entitySets.Count > resultSetIndex
2711                 EntitySet entitySet = entitySets.Count > resultSetIndex ? entitySets[resultSetIndex] : null;
2712 
2713                 // create the shaper
2714                 System.Data.Common.QueryCache.QueryCacheManager cacheManager = this.Perspective.MetadataWorkspace.GetQueryCacheManager();
2715                 ShaperFactory<TElement> shaperFactory = Translator.TranslateColumnMap<TElement>(cacheManager, commandDefinition.CreateColumnMap(storeReader, resultSetIndex), this.MetadataWorkspace, null, mergeOption, false);
2716                 Shaper<TElement> shaper = shaperFactory.Create(storeReader, this, this.MetadataWorkspace, mergeOption, shaperOwnsReader);
2717 
2718                 NextResultGenerator nextResultGenerator;
2719 
2720                 // We need to run notifications when the data reader is closed in order to propagate any out parameters.
2721                 // We do this whenever the last (declared) result set's enumerator is disposed (this calls Finally on the shaper)
2722                 // or when the underlying reader is closed as a result of the ObjectResult itself getting disposed.
2723                 // We use onReaderDisposeHasRun to ensure that this notification is only called once.
2724                 // the alternative approach of not making the final ObjectResult's disposal result do cleanup doesn't work in the case where
2725                 // its GetEnumerator is called explicitly, and the resulting enumerator is never disposed.
2726                 bool onReaderDisposeHasRun = false;
2727                 Action<object, EventArgs> onReaderDispose = (object sender, EventArgs e) =>
2728                     {
2729                         if (!onReaderDisposeHasRun)
2730                         {
2731                             onReaderDisposeHasRun = true;
2732                             // consume the store reader
2733                             CommandHelper.ConsumeReader(storeReader);
2734                             // trigger event callback
2735                             entityCommand.NotifyDataReaderClosing();
2736                         }
2737                     };
2738 
2739                 if (shaperOwnsReader)
2740                 {
2741                     shaper.OnDone += new EventHandler(onReaderDispose);
2742                     nextResultGenerator = null;
2743                 }
2744                 else
2745                 {
2746                     nextResultGenerator = new NextResultGenerator(this, entityCommand, edmTypes, entitySets, mergeOption, resultSetIndex + 1);
2747                 }
2748 
2749                 // We want the ObjectResult to close the reader in its Dispose method, even if it is not the last result set.
2750                 // This is to allow users to cancel reading results without the unnecessary iteration thru all the result sets.
2751                 return new ObjectResult<TElement>(shaper, entitySet, TypeUsage.Create(edmTypes[resultSetIndex]), true, nextResultGenerator, onReaderDispose);
2752             }
2753             catch
2754             {
2755                 this.ReleaseConnection();
2756                 storeReader.Dispose();
2757                 throw;
2758             }
2759         }
2760 
2761 
PopulateFunctionImportEntityCommandParameters(ObjectParameter[] parameters, EdmFunction functionImport, EntityCommand command)2762         private void PopulateFunctionImportEntityCommandParameters(ObjectParameter[] parameters, EdmFunction functionImport, EntityCommand command)
2763         {
2764             // attach entity parameters
2765             for (int i = 0; i < parameters.Length; i++)
2766             {
2767                 ObjectParameter objectParameter = parameters[i];
2768                 EntityParameter entityParameter = new EntityParameter();
2769 
2770                 FunctionParameter functionParameter = FindParameterMetadata(functionImport, parameters, i);
2771 
2772                 if (null != functionParameter)
2773                 {
2774                     entityParameter.Direction = MetadataHelper.ParameterModeToParameterDirection(
2775                        functionParameter.Mode);
2776                     entityParameter.ParameterName = functionParameter.Name;
2777                 }
2778                 else
2779                 {
2780                     entityParameter.ParameterName = objectParameter.Name;
2781                 }
2782 
2783                 entityParameter.Value = objectParameter.Value ?? DBNull.Value;
2784 
2785                 if (DBNull.Value == entityParameter.Value ||
2786                     entityParameter.Direction != ParameterDirection.Input)
2787                 {
2788                     TypeUsage typeUsage;
2789                     if (functionParameter != null)
2790                     {
2791                         // give precedence to the statically declared type usage
2792                         typeUsage = functionParameter.TypeUsage;
2793                     }
2794                     else if (null == objectParameter.TypeUsage)
2795                     {
2796                         Debug.Assert(objectParameter.MappableType != null, "MappableType must not be null");
2797                         Debug.Assert(Nullable.GetUnderlyingType(objectParameter.MappableType) == null, "Nullable types not expected here.");
2798 
2799                         // since ObjectParameters do not allow users to especify 'facets', make
2800                         // sure that the parameter typeusage is not populated with the provider
2801                         // dafault facet values.
2802                         // Try getting the type from the workspace. This may fail however for one of the following reasons:
2803                         // - the type is not a model type
2804                         // - the types were not loaded into the workspace yet
2805                         // If the types were not loaded into the workspace we try loading types from the assembly the type lives in and re-try
2806                         // loading the type. We don't care if the type still cannot be loaded - in this case the result TypeUsage will be null
2807                         // which we handle later.
2808                         if (!this.Perspective.TryGetTypeByName(objectParameter.MappableType.FullName, /*ignoreCase */ false, out typeUsage))
2809                         {
2810                             this.MetadataWorkspace.ImplicitLoadAssemblyForType(objectParameter.MappableType, null);
2811                             this.Perspective.TryGetTypeByName(objectParameter.MappableType.FullName, /*ignoreCase */ false, out typeUsage);
2812                         }
2813                     }
2814                     else
2815                     {
2816                         typeUsage = objectParameter.TypeUsage;
2817                     }
2818 
2819                     // set type information (if the provider cannot determine it from the actual value)
2820                     EntityCommandDefinition.PopulateParameterFromTypeUsage(entityParameter, typeUsage, entityParameter.Direction != ParameterDirection.Input);
2821                 }
2822 
2823                 if (entityParameter.Direction != ParameterDirection.Input)
2824                 {
2825                     ParameterBinder binder = new ParameterBinder(entityParameter, objectParameter);
2826                     command.OnDataReaderClosing += new EventHandler(binder.OnDataReaderClosingHandler);
2827                 }
2828                 command.Parameters.Add(entityParameter);
2829             }
2830         }
2831 
FindParameterMetadata(EdmFunction functionImport, ObjectParameter[] parameters, int ordinal)2832         private static FunctionParameter FindParameterMetadata(EdmFunction functionImport, ObjectParameter[] parameters, int ordinal)
2833         {
2834             // Retrieve parameter information from functionImport.
2835             // We first attempt to resolve by case-sensitive name. If there is no exact match,
2836             // check if there is a case-insensitive match. Case insensitive matches are only permitted
2837             // when a single parameter would match.
2838             FunctionParameter functionParameter;
2839             string parameterName = parameters[ordinal].Name;
2840             if (!functionImport.Parameters.TryGetValue(parameterName, false, out functionParameter))
2841             {
2842                 // if only one parameter has this name, try a case-insensitive lookup
2843                 int matchCount = 0;
2844                 for (int i = 0; i < parameters.Length && matchCount < 2; i++)
2845                 {
2846                     if (StringComparer.OrdinalIgnoreCase.Equals(parameters[i].Name, parameterName))
2847                     {
2848                         matchCount++;
2849                     }
2850                 }
2851                 if (matchCount == 1)
2852                 {
2853                     functionImport.Parameters.TryGetValue(parameterName, true, out functionParameter);
2854                 }
2855             }
2856             return functionParameter;
2857         }
2858 
2859         /// <summary>
2860         /// Attempt to generate a proxy type for each type in the supplied enumeration.
2861         /// </summary>
2862         /// <param name="types">
2863         /// Enumeration of Type objects that should correspond to O-Space types.
2864         /// </param>
2865         /// <remarks>
2866         /// Types in the enumeration that do not map to an O-Space type are ignored.
2867         /// Also, there is no guarantee that a proxy type will be created for a given type,
2868         /// only that if a proxy can be generated, then it will be generated.
2869         ///
2870         /// See <see cref="EntityProxyFactory"/> class for more information about proxy type generation.
2871         /// </remarks>
2872 
2873         // Use one of the following methods to retrieve an enumeration of all CLR types mapped to O-Space EntityType objects:
2874         //
2875 
2876 
2877 
2878 
2879 
2880 
2881 
2882 
2883 
2884 
2885         //
2886 
CreateProxyTypes(IEnumerable<Type> types)2887         public void CreateProxyTypes(IEnumerable<Type> types)
2888         {
2889             ObjectItemCollection ospaceItems = (ObjectItemCollection)MetadataWorkspace.GetItemCollection(DataSpace.OSpace);
2890 
2891             // Ensure metadata is loaded for each type,
2892             // and attempt to create proxy type only for types that have a mapping to an O-Space EntityType.
2893             EntityProxyFactory.TryCreateProxyTypes(
2894                 types.Select(type =>
2895                 {
2896                     // Ensure the assembly containing the entity's CLR type is loaded into the workspace.
2897                     MetadataWorkspace.ImplicitLoadAssemblyForType(type, null);
2898 
2899                     EntityType entityType;
2900                     ospaceItems.TryGetItem<EntityType>(type.FullName, out entityType);
2901                     return entityType;
2902                 }).Where(entityType => entityType != null)
2903             );
2904         }
2905 
2906         /// <summary>
2907         /// Return an enumerable of the current set of CLR proxy types.
2908         /// </summary>
2909         /// <returns>
2910         /// Enumerable of the current set of CLR proxy types.
2911         /// This will never be null.
2912         /// </returns>
GetKnownProxyTypes()2913         public static IEnumerable<Type> GetKnownProxyTypes()
2914         {
2915             return EntityProxyFactory.GetKnownProxyTypes();
2916         }
2917 
2918         /// <summary>
2919         /// Given a type that may represent a known proxy type,
2920         /// return the corresponding type being proxied.
2921         /// </summary>
2922         /// <param name="type">Type that may represent a proxy type.</param>
2923         /// <returns>
2924         /// Non-proxy type that corresponds to the supplied proxy type,
2925         /// or the supplied type if it is not a known proxy type.
2926         /// </returns>
2927         /// <exception cref="ArgumentNullException">
2928         /// If the value of the type parameter is null.
2929         /// </exception
GetObjectType(Type type)2930         public static Type GetObjectType(Type type)
2931         {
2932             EntityUtil.CheckArgumentNull(type, "type");
2933 
2934             return EntityProxyFactory.IsProxyType(type) ? type.BaseType : type;
2935         }
2936 
2937         /// <summary>
2938         /// Create an appropriate instance of the type <typeparamref name="T"/>.
2939         /// </summary>
2940         /// <typeparam name="T">
2941         /// Type of object to be returned.
2942         /// </typeparam>
2943         /// <returns>
2944         /// An instance of an object of type <typeparamref name="T"/>.
2945         /// The object will either be an instance of the exact type <typeparamref name="T"/>,
2946         /// or possibly an instance of the proxy type that corresponds to <typeparamref name="T"/>.
2947         /// </returns>
2948         /// <remarks>
2949         /// The type <typeparamref name="T"/> must have an OSpace EntityType representation.
2950         /// </remarks>
2951         public T CreateObject<T>()
2952             where T : class
2953         {
2954             T instance = null;
2955             Type clrType = typeof(T);
2956 
2957             // Ensure the assembly containing the entity's CLR type is loaded into the workspace.
MetadataWorkspace.ImplicitLoadAssemblyForType(clrType, null)2958             MetadataWorkspace.ImplicitLoadAssemblyForType(clrType, null);
2959 
2960             // Retrieve the OSpace EntityType that corresponds to the supplied CLR type.
2961             // This call ensure that this mapping exists.
2962             ClrEntityType entityType = MetadataWorkspace.GetItem<ClrEntityType>(clrType.FullName, DataSpace.OSpace);
2963             EntityProxyTypeInfo proxyTypeInfo = null;
2964 
2965             if (ContextOptions.ProxyCreationEnabled && ((proxyTypeInfo = EntityProxyFactory.GetProxyType(entityType)) != null))
2966             {
2967                 instance = (T)proxyTypeInfo.CreateProxyObject();
2968 
2969                 // After creating the proxy we need to add additional state to the proxy such
2970                 // that it is able to function correctly when returned.  In particular, it needs
2971                 // an initialized set of RelatedEnd objects because it will not be possible to
2972                 // create these for convention based mapping once the metadata in the context has
2973                 // been lost.
2974                 IEntityWrapper wrappedEntity = EntityWrapperFactory.CreateNewWrapper(instance, null);
2975                 wrappedEntity.InitializingProxyRelatedEnds = true;
2976                 try
2977                 {
2978                     // We're setting the context temporarily here so that we can go through the process
2979                     // of creating RelatedEnds even with convention-based mapping.
2980                     // However, we also need to tell the wrapper that we're doing this so that we don't
2981                     // try to do things that we normally do when we have a context, such as adding the
2982                     // context to the RelatedEnds.  We can't do these things since they require an
2983                     // EntitySet, and, because of MEST, we don't have one.
2984                     wrappedEntity.AttachContext(this, null, MergeOption.NoTracking);
2985                     proxyTypeInfo.SetEntityWrapper(wrappedEntity);
2986                     if (proxyTypeInfo.InitializeEntityCollections != null)
2987                     {
2988                         proxyTypeInfo.InitializeEntityCollections.Invoke(null, new object[] { wrappedEntity });
2989                     }
2990                 }
2991                 finally
2992                 {
2993                     wrappedEntity.InitializingProxyRelatedEnds = false;
2994                     wrappedEntity.DetachContext();
2995                 }
2996             }
2997             else
2998             {
2999                 Func<object> ctor = LightweightCodeGenerator.GetConstructorDelegateForType(entityType) as Func<object>;
3000                 Debug.Assert(ctor != null, "Could not find entity constructor");
3001                 instance = ctor() as T;
3002             }
3003 
3004             return instance;
3005         }
3006 
3007         /// <summary>
3008         /// Execute a command against the database server that does not return a sequence of objects.
3009         /// The command is specified using the server's native query language, such as SQL.
3010         /// </summary>
3011         /// <param name="command">The command specified in the server's native query language.</param>
3012         /// <param name="parameters">The parameter values to use for the query.</param>
3013         /// <returns>A single integer return value</returns>
ExecuteStoreCommand(string commandText, params object[] parameters)3014         public int ExecuteStoreCommand(string commandText, params object[] parameters)
3015         {
3016             this.EnsureConnection();
3017 
3018             try
3019             {
3020                 DbCommand command = CreateStoreCommand(commandText, parameters);
3021                 return command.ExecuteNonQuery();
3022             }
3023             finally
3024             {
3025                 this.ReleaseConnection();
3026             }
3027         }
3028 
3029         /// <summary>
3030         /// Execute the sequence returning query against the database server.
3031         /// The query is specified using the server's native query language, such as SQL.
3032         /// </summary>
3033         /// <typeparam name="TElement">The element type of the result sequence.</typeparam>
3034         /// <param name="query">The query specified in the server's native query language.</param>
3035         /// <param name="parameters">The parameter values to use for the query.</param>
3036         /// <returns>An IEnumerable sequence of objects.</returns>
3037         [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
ExecuteStoreQuery(string commandText, params object[] parameters)3038         public ObjectResult<TElement> ExecuteStoreQuery<TElement>(string commandText, params object[] parameters)
3039         {
3040             return ExecuteStoreQueryInternal<TElement>(commandText, null /*entitySetName*/, MergeOption.AppendOnly, parameters);
3041         }
3042 
3043         /// <summary>
3044         /// Execute the sequence returning query against the database server.
3045         /// The query is specified using the server's native query language, such as SQL.
3046         /// </summary>
3047         /// <typeparam name="TEntity">The element type of the resulting sequence</typeparam>
3048         /// <param name="reader">The DbDataReader to translate</param>
3049         /// <param name="entitySetName">The entity set in which results should be tracked. Null indicates there is no entity set.</param>
3050         /// <param name="mergeOption">Merge option to use for entity results.</param>
3051         /// <returns>The translated sequence of objects</returns>
3052         [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
ExecuteStoreQuery(string commandText, string entitySetName, MergeOption mergeOption, params object[] parameters)3053         public ObjectResult<TEntity> ExecuteStoreQuery<TEntity>(string commandText, string entitySetName, MergeOption mergeOption, params object[] parameters)
3054         {
3055             EntityUtil.CheckStringArgument(entitySetName, "entitySetName");
3056             return ExecuteStoreQueryInternal<TEntity>(commandText, entitySetName, mergeOption, parameters);
3057         }
3058 
3059         /// <summary>
3060         /// See ExecuteStoreQuery method.
3061         /// </summary>
ExecuteStoreQueryInternal(string commandText, string entitySetName, MergeOption mergeOption, params object[] parameters)3062         private ObjectResult<TElement> ExecuteStoreQueryInternal<TElement>(string commandText, string entitySetName, MergeOption mergeOption, params object[] parameters)
3063         {
3064             // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type
3065             // is loaded into the workspace. If the schema types are not loaded
3066             // metadata, cache & query would be unable to reason about the type. We
3067             // either auto-load <TElement>'s assembly into the ObjectItemCollection or we
3068             // auto-load the user's calling assembly and its referenced assemblies.
3069             // If the entities in the user's result spans multiple assemblies, the
3070             // user must manually call LoadFromAssembly. *GetCallingAssembly returns
3071             // the assembly of the method that invoked the currently executing method.
3072             this.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(TElement), System.Reflection.Assembly.GetCallingAssembly());
3073 
3074             this.EnsureConnection();
3075             DbDataReader reader = null;
3076 
3077             try
3078             {
3079                 DbCommand command = CreateStoreCommand(commandText, parameters);
3080                 reader = command.ExecuteReader();
3081             }
3082             catch
3083             {
3084                 // We only release the connection when there is an exception. Otherwise, the ObjectResult is
3085                 // in charge of releasing it.
3086                 this.ReleaseConnection();
3087                 throw;
3088             }
3089 
3090             try
3091             {
3092                 return InternalTranslate<TElement>(reader, entitySetName, mergeOption, true);
3093             }
3094             catch
3095             {
3096                 reader.Dispose();
3097                 this.ReleaseConnection();
3098                 throw;
3099             }
3100         }
3101 
3102         /// <summary>
3103         /// Translates the data from a DbDataReader into sequence of objects.
3104         /// </summary>
3105         /// <typeparam name="TElement">The element type of the resulting sequence</typeparam>
3106         /// <param name="reader">The DbDataReader to translate</param>
3107         /// <param name="mergeOption">Merge option to use for entity results.</param>
3108         /// <returns>The translated sequence of objects</returns>
3109         [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
Translate(DbDataReader reader)3110         public ObjectResult<TElement> Translate<TElement>(DbDataReader reader)
3111         {
3112             // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type
3113             // is loaded into the workspace. If the schema types are not loaded
3114             // metadata, cache & query would be unable to reason about the type. We
3115             // either auto-load <TElement>'s assembly into the ObjectItemCollection or we
3116             // auto-load the user's calling assembly and its referenced assemblies.
3117             // If the entities in the user's result spans multiple assemblies, the
3118             // user must manually call LoadFromAssembly. *GetCallingAssembly returns
3119             // the assembly of the method that invoked the currently executing method.
3120             this.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(TElement), System.Reflection.Assembly.GetCallingAssembly());
3121 
3122             return InternalTranslate<TElement>(reader, null /*entitySetName*/, MergeOption.AppendOnly, false);
3123         }
3124 
3125         /// <summary>
3126         /// Translates the data from a DbDataReader into sequence of entities.
3127         /// </summary>
3128         /// <typeparam name="TEntity">The element type of the resulting sequence</typeparam>
3129         /// <param name="reader">The DbDataReader to translate</param>
3130         /// <param name="entitySetName">The entity set in which results should be tracked. Null indicates there is no entity set.</param>
3131         /// <param name="mergeOption">Merge option to use for entity results.</param>
3132         /// <returns>The translated sequence of objects</returns>
3133         [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
Translate(DbDataReader reader, string entitySetName, MergeOption mergeOption)3134         public ObjectResult<TEntity> Translate<TEntity>(DbDataReader reader, string entitySetName, MergeOption mergeOption)
3135         {
3136             EntityUtil.CheckStringArgument(entitySetName, "entitySetName");
3137 
3138             // SQLBUDT 447285: Ensure the assembly containing the entity's CLR type
3139             // is loaded into the workspace. If the schema types are not loaded
3140             // metadata, cache & query would be unable to reason about the type. We
3141             // either auto-load <TEntity>'s assembly into the ObjectItemCollection or we
3142             // auto-load the user's calling assembly and its referenced assemblies.
3143             // If the entities in the user's result spans multiple assemblies, the
3144             // user must manually call LoadFromAssembly. *GetCallingAssembly returns
3145             // the assembly of the method that invoked the currently executing method.
3146             this.MetadataWorkspace.ImplicitLoadAssemblyForType(typeof(TEntity), System.Reflection.Assembly.GetCallingAssembly());
3147 
3148             return InternalTranslate<TEntity>(reader, entitySetName, mergeOption, false);
3149         }
3150 
InternalTranslate(DbDataReader reader, string entitySetName, MergeOption mergeOption, bool readerOwned)3151         private ObjectResult<TElement> InternalTranslate<TElement>(DbDataReader reader, string entitySetName, MergeOption mergeOption, bool readerOwned)
3152         {
3153             EntityUtil.CheckArgumentNull(reader, "reader");
3154             EntityUtil.CheckArgumentMergeOption(mergeOption);
3155             EntitySet entitySet = null;
3156             if (!string.IsNullOrEmpty(entitySetName))
3157             {
3158                 entitySet = this.GetEntitySetFromName(entitySetName);
3159             }
3160 
3161             // make sure all metadata is available (normally this is handled by the call to EntityConnection.Open,
3162             // but translate does not necessarily use the EntityConnection)
3163             EnsureMetadata();
3164 
3165             // get the expected EDM type
3166             EdmType modelEdmType;
3167             Type unwrappedTElement = Nullable.GetUnderlyingType(typeof(TElement)) ?? typeof(TElement);
3168             CollectionColumnMap columnMap;
3169             // for enums that are not in the model we use the enum underlying type
3170             if (MetadataHelper.TryDetermineCSpaceModelType<TElement>(this.MetadataWorkspace, out modelEdmType) ||
3171                 (unwrappedTElement.IsEnum && MetadataHelper.TryDetermineCSpaceModelType(unwrappedTElement.GetEnumUnderlyingType(), this.MetadataWorkspace, out modelEdmType)))
3172             {
3173                 if (entitySet != null && !entitySet.ElementType.IsAssignableFrom(modelEdmType))
3174                 {
3175                     throw EntityUtil.InvalidOperation(Strings.ObjectContext_InvalidEntitySetForStoreQuery(entitySet.EntityContainer.Name,
3176                         entitySet.Name, typeof(TElement)));
3177                 }
3178 
3179                 columnMap = ColumnMapFactory.CreateColumnMapFromReaderAndType(reader, modelEdmType, entitySet, null);
3180             }
3181             else
3182             {
3183                 columnMap = ColumnMapFactory.CreateColumnMapFromReaderAndClrType(reader, typeof(TElement), this.MetadataWorkspace);
3184             }
3185 
3186             // build a shaper for the column map to produce typed results
3187             System.Data.Common.QueryCache.QueryCacheManager cacheManager = this.MetadataWorkspace.GetQueryCacheManager();
3188             ShaperFactory<TElement> shaperFactory = Translator.TranslateColumnMap<TElement>(cacheManager, columnMap, this.MetadataWorkspace, null, mergeOption, false);
3189             Shaper<TElement> shaper = shaperFactory.Create(reader, this, this.MetadataWorkspace, mergeOption, readerOwned);
3190             return new ObjectResult<TElement>(shaper, entitySet, MetadataHelper.GetElementType(columnMap.Type), readerOwned);
3191         }
3192 
CreateStoreCommand(string commandText, params object[] parameters)3193         private DbCommand CreateStoreCommand(string commandText, params object[] parameters)
3194         {
3195             DbCommand command = this._connection.StoreConnection.CreateCommand();
3196             command.CommandText = commandText;
3197 
3198             // get relevant state from the object context
3199             if (this.CommandTimeout.HasValue)
3200             {
3201                 command.CommandTimeout = this.CommandTimeout.Value;
3202             }
3203             EntityTransaction entityTransaction = this._connection.CurrentTransaction;
3204             if (null != entityTransaction)
3205             {
3206                 command.Transaction = entityTransaction.StoreTransaction;
3207             }
3208 
3209             if (null != parameters && parameters.Length > 0)
3210             {
3211                 DbParameter[] dbParameters = new DbParameter[parameters.Length];
3212 
3213                 // three cases: all explicit DbParameters, no explicit DbParameters
3214                 // or a mix of the two (throw in the last case)
3215                 if (parameters.All(p => p is DbParameter))
3216                 {
3217                     for (int i = 0; i < parameters.Length; i++)
3218                     {
3219                         dbParameters[i] = (DbParameter)parameters[i];
3220                     }
3221                 }
3222                 else if (!parameters.Any(p => p is DbParameter))
3223                 {
3224                     string[] parameterNames = new string[parameters.Length];
3225                     string[] parameterSql = new string[parameters.Length];
3226                     for (int i = 0; i < parameters.Length; i++)
3227                     {
3228                         parameterNames[i] = string.Format(CultureInfo.InvariantCulture, "p{0}", i);
3229                         dbParameters[i] = command.CreateParameter();
3230                         dbParameters[i].ParameterName = parameterNames[i];
3231                         dbParameters[i].Value = parameters[i] ?? DBNull.Value;
3232 
3233                         // By default, we attempt to swap in a SQL Server friendly representation of the parameter.
3234                         // For other providers, users may write:
3235                         //
3236                         //      ExecuteStoreQuery("select * from foo f where f.X = ?", 1);
3237                         //
3238                         // rather than:
3239                         //
3240                         //      ExecuteStoreQuery("select * from foo f where f.X = {0}", 1);
3241                         parameterSql[i] = "@" + parameterNames[i];
3242                     }
3243                     command.CommandText = string.Format(CultureInfo.InvariantCulture, command.CommandText, parameterSql);
3244                 }
3245                 else
3246                 {
3247                     throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectContext_ExecuteCommandWithMixOfDbParameterAndValues);
3248                 }
3249 
3250                 command.Parameters.AddRange(dbParameters);
3251             }
3252 
3253             return command;
3254         }
3255 
3256         /// <summary>
3257         /// Creates the database using the current store connection and the metadata in the StoreItemCollection. Most of the actual work
3258         /// is done by the DbProviderServices implementation for the current store connection.
3259         /// </summary>
CreateDatabase()3260         public void CreateDatabase()
3261         {
3262             DbConnection storeConnection = this._connection.StoreConnection;
3263             DbProviderServices services = DbProviderServices.GetProviderServices(this.GetStoreItemCollection().StoreProviderFactory);
3264             services.CreateDatabase(storeConnection, this.CommandTimeout, this.GetStoreItemCollection());
3265         }
3266 
3267         /// <summary>
3268         /// Deletes the database that is specified as the database in the current store connection. Most of the actual work
3269         /// is done by the DbProviderServices implementation for the current store connection.
3270         /// </summary>
DeleteDatabase()3271         public void DeleteDatabase()
3272         {
3273             DbConnection storeConnection = this._connection.StoreConnection;
3274             DbProviderServices services = DbProviderServices.GetProviderServices(this.GetStoreItemCollection().StoreProviderFactory);
3275             services.DeleteDatabase(storeConnection, this.CommandTimeout, this.GetStoreItemCollection());
3276         }
3277 
3278         /// <summary>
3279         /// Checks if the database that is specified as the database in the current store connection exists on the store. Most of the actual work
3280         /// is done by the DbProviderServices implementation for the current store connection.
3281         /// </summary>
DatabaseExists()3282         public bool DatabaseExists()
3283         {
3284             DbConnection storeConnection = this._connection.StoreConnection;
3285             DbProviderServices services = DbProviderServices.GetProviderServices(this.GetStoreItemCollection().StoreProviderFactory);
3286             return services.DatabaseExists(storeConnection, this.CommandTimeout, this.GetStoreItemCollection());
3287         }
3288 
3289         /// <summary>
3290         /// Creates the sql script that can be used to create the database for the metadata in the StoreItemCollection. Most of the actual work
3291         /// is done by the DbProviderServices implementation for the current store connection.
3292         /// </summary>
CreateDatabaseScript()3293         public String CreateDatabaseScript()
3294         {
3295             DbProviderServices services = DbProviderServices.GetProviderServices(this.GetStoreItemCollection().StoreProviderFactory);
3296             string targetProviderManifestToken = this.GetStoreItemCollection().StoreProviderManifestToken;
3297             return services.CreateDatabaseScript(targetProviderManifestToken, this.GetStoreItemCollection());
3298         }
3299 
GetStoreItemCollection()3300         private StoreItemCollection GetStoreItemCollection()
3301         {
3302             var entityConnection = (EntityConnection)this.Connection;
3303             // retrieve the item collection from the entity connection rather than the context since:
3304             // a) it forces creation of the metadata workspace if it's not already there
3305             // b) the store item collection isn't guaranteed to exist on the context.MetadataWorkspace
3306             return (StoreItemCollection)entityConnection.GetMetadataWorkspace().GetItemCollection(DataSpace.SSpace);
3307         }
3308 
3309         #endregion //Methods
3310 
3311         #region Nested types
3312         /// <summary>
3313         /// Supports binding EntityClient parameters to Object Services parameters.
3314         /// </summary>
3315         private class ParameterBinder
3316         {
3317             private readonly EntityParameter _entityParameter;
3318             private readonly ObjectParameter _objectParameter;
3319 
ParameterBinder(EntityParameter entityParameter, ObjectParameter objectParameter)3320             internal ParameterBinder(EntityParameter entityParameter, ObjectParameter objectParameter)
3321             {
3322                 _entityParameter = entityParameter;
3323                 _objectParameter = objectParameter;
3324             }
3325 
OnDataReaderClosingHandler(object sender, EventArgs args)3326             internal void OnDataReaderClosingHandler(object sender, EventArgs args)
3327             {
3328                 // When the reader is closing, out/inout parameter values are set on the EntityParameter
3329                 // instance. Pass this value through to the corresponding ObjectParameter.
3330                 if (_entityParameter.Value != DBNull.Value && _objectParameter.MappableType.IsEnum)
3331                 {
3332                     _objectParameter.Value = Enum.ToObject(_objectParameter.MappableType, _entityParameter.Value);
3333                 }
3334                 else
3335                 {
3336                     _objectParameter.Value = _entityParameter.Value;
3337                 }
3338             }
3339         }
3340         #endregion
3341 
3342         internal CollectionColumnMap ColumnMapBuilder { get; set; }
3343     }
3344 }
3345