1 //------------------------------------------------------------------------------
2 // <copyright file="Shaper.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8 
9 namespace System.Data.Common.Internal.Materialization
10 {
11     using System.Collections.Generic;
12     using System.Data.Common.Utils;
13     using System.Data.Metadata.Edm;
14     using System.Data.Objects;
15     using System.Data.Objects.DataClasses;
16     using System.Data.Objects.Internal;
17     using System.Data.Spatial;
18     using System.Diagnostics;
19     using System.Reflection;
20 
21     /// <summary>
22     /// Shapes store reader values into EntityClient/ObjectQuery results. Also maintains
23     /// state used by materializer delegates.
24     /// </summary>
25     internal abstract class Shaper
26     {
27         #region constructor
28 
Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)29         internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)
30         {
31             Debug.Assert(context == null || workspace == context.MetadataWorkspace, "workspace must match context's workspace");
32 
33             this.Reader = reader;
34             this.MergeOption = mergeOption;
35             this.State = new object[stateCount];
36             this.Context = context;
37             this.Workspace = workspace;
38             this.AssociationSpaceMap = new Dictionary<AssociationType, AssociationType>();
39             this.spatialReader = new Singleton<DbSpatialDataReader>(CreateSpatialDataReader);
40         }
41 
42         #endregion
43 
44         #region OnMaterialized storage
45 
46         /// <summary>
47         /// Keeps track of the entities that have been materialized so that we can fire an OnMaterialized
48         /// for them before returning control to the caller.
49         /// </summary>
50         private IList<IEntityWrapper> _materializedEntities;
51 
52         #endregion
53 
54         #region runtime callable/accessible code
55 
56         // Code in this section is called from the delegates produced by the Translator.  It
57         // may not show up if you search using Find All References...use Find in Files instead.
58         //
59         // Many items on this class are public, simply to make the job of producing the
60         // expressions that use them simpler.  If you have a hankering to make them private,
61         // you will need to modify the code in the Translator that does the GetMethod/GetField
62         // to use BindingFlags.NonPublic | BindingFlags.Instance as well.
63         //
64         // Debug.Asserts that fire from the code in this region will probably create a
65         // SecurityException in the Coordinator's Read method since those are restricted when
66         // running the Shaper.
67 
68         /// <summary>
69         /// The store data reader we're pulling data from
70         /// </summary>
71         public readonly DbDataReader Reader;
72 
73         /// <summary>
74         /// The state slots we use in the coordinator expression.
75         /// </summary>
76         public readonly object[] State;
77 
78         /// <summary>
79         /// The context the shaper is performing for.
80         /// </summary>
81         public readonly ObjectContext Context;
82 
83         /// <summary>
84         /// The workspace we are performing for; yes we could get it from the context, but
85         /// it's much easier to just have it handy.
86         /// </summary>
87         public readonly MetadataWorkspace Workspace;
88 
89         /// <summary>
90         /// The merge option this shaper is performing under/for.
91         /// </summary>
92         public readonly MergeOption MergeOption;
93 
94         /// <summary>
95         /// A mapping of CSpace AssociationTypes to OSpace AssociationTypes
96         /// Used for faster lookup/retrieval of AssociationTypes during materialization
97         /// </summary>
98         private readonly Dictionary<AssociationType, AssociationType> AssociationSpaceMap;
99 
100         /// <summary>
101         /// Caches Tuples of EntitySet, AssociationType, and source member name for which RelatedEnds exist.
102         /// </summary>
103         private HashSet<Tuple<string, string, string>> _relatedEndCache;
104 
105         /// <summary>
106         /// Utility method used to evaluate a multi-discriminator column map. Takes
107         /// discriminator values and determines the appropriate entity type, then looks up
108         /// the appropriate handler and invokes it.
109         /// </summary>
Discriminate(object[] discriminatorValues, Func<object[], EntityType> discriminate, KeyValuePair<EntityType, Func<Shaper, TElement>>[] elementDelegates)110         public TElement Discriminate<TElement>(object[] discriminatorValues, Func<object[], EntityType> discriminate, KeyValuePair<EntityType, Func<Shaper, TElement>>[] elementDelegates)
111         {
112             EntityType entityType = discriminate(discriminatorValues);
113             Func<Shaper, TElement> elementDelegate = null;
114             foreach (KeyValuePair<EntityType, Func<Shaper, TElement>> typeDelegatePair in elementDelegates)
115             {
116                 if (typeDelegatePair.Key == entityType)
117                 {
118                     elementDelegate = typeDelegatePair.Value;
119                 }
120             }
121             return elementDelegate(this);
122         }
123 
HandleEntityNoTracking(IEntityWrapper wrappedEntity)124         public IEntityWrapper HandleEntityNoTracking<TEntity>(IEntityWrapper wrappedEntity)
125         {
126             Debug.Assert(null != wrappedEntity, "wrapped entity is null");
127             RegisterMaterializedEntityForEvent(wrappedEntity);
128             return wrappedEntity;
129         }
130 
131         /// <summary>
132         /// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
133         /// Handles state management for an entity returned by a query. Where an existing entry
134         /// exists, updates that entry and returns the existing entity. Otherwise, the entity
135         /// passed in is returned.
136         /// </summary>
HandleEntity(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet)137         public IEntityWrapper HandleEntity<TEntity>(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet)
138         {
139             Debug.Assert(MergeOption.NoTracking != this.MergeOption, "no need to HandleEntity if there's no tracking");
140             Debug.Assert(MergeOption.AppendOnly != this.MergeOption, "use HandleEntityAppendOnly instead...");
141             Debug.Assert(null != wrappedEntity, "wrapped entity is null");
142             Debug.Assert(null != wrappedEntity.Entity, "if HandleEntity is called, there must be an entity");
143 
144             IEntityWrapper result = wrappedEntity;
145 
146             // no entity set, so no tracking is required for this entity
147             if (null != (object)entityKey)
148             {
149                 Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
150 
151                 // check for an existing entity with the same key
152                 EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey);
153                 if (null != existingEntry && !existingEntry.IsKeyEntry)
154                 {
155                     Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
156                     UpdateEntry<TEntity>(wrappedEntity, existingEntry);
157                     result = existingEntry.WrappedEntity;
158                 }
159                 else
160                 {
161                     RegisterMaterializedEntityForEvent(result);
162                     if (null == existingEntry)
163                     {
164                         Context.ObjectStateManager.AddEntry(wrappedEntity, entityKey, entitySet, "HandleEntity", false);
165                     }
166                     else
167                     {
168                         Context.ObjectStateManager.PromoteKeyEntry(existingEntry, wrappedEntity, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
169                     }
170                 }
171             }
172             return result;
173         }
174 
175         /// <summary>
176         /// REQUIRES:: entity exists; MergeOption is AppendOnly
177         /// Handles state management for an entity with the given key. When the entity already exists
178         /// in the state manager, it is returned directly. Otherwise, the entityDelegate is invoked and
179         /// the resulting entity is returned.
180         /// </summary>
HandleEntityAppendOnly(Func<Shaper, IEntityWrapper> constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)181         public IEntityWrapper HandleEntityAppendOnly<TEntity>(Func<Shaper, IEntityWrapper> constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
182         {
183             Debug.Assert(this.MergeOption == MergeOption.AppendOnly, "only use HandleEntityAppendOnly when MergeOption is AppendOnly");
184             Debug.Assert(null != constructEntityDelegate, "must provide delegate to construct the entity");
185 
186             IEntityWrapper result;
187 
188             if (null == (object)entityKey)
189             {
190                 // no entity set, so no tracking is required for this entity, just
191                 // call the delegate to "materialize" it.
192                 result = constructEntityDelegate(this);
193                 RegisterMaterializedEntityForEvent(result);
194             }
195             else
196             {
197                 Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
198 
199                 // check for an existing entity with the same key
200                 EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey);
201                 if (null != existingEntry && !existingEntry.IsKeyEntry)
202                 {
203                     Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
204                     if (typeof(TEntity) != existingEntry.WrappedEntity.IdentityType)
205                     {
206                         throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, typeof(TEntity), existingEntry.WrappedEntity.IdentityType);
207                     }
208 
209                     if (EntityState.Added == existingEntry.State)
210                     {
211                         throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
212                     }
213                     result = existingEntry.WrappedEntity;
214                 }
215                 else
216                 {
217                     // We don't already have the entity, so construct it
218                     result = constructEntityDelegate(this);
219                     RegisterMaterializedEntityForEvent(result);
220                     if (null == existingEntry)
221                     {
222                         Context.ObjectStateManager.AddEntry(result, entityKey, entitySet, "HandleEntity", false);
223                     }
224                     else
225                     {
226                         Context.ObjectStateManager.PromoteKeyEntry(existingEntry, result, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
227                     }
228                 }
229             }
230             return result;
231         }
232 
233         /// <summary>
234         /// Call to ensure a collection of full-spanned elements are added
235         /// into the state manager properly.  We registers an action to be called
236         /// when the collection is closed that pulls the collection of full spanned
237         /// objects into the state manager.
238         /// </summary>
HandleFullSpanCollection(IEntityWrapper wrappedEntity, Coordinator<T_TargetEntity> coordinator, AssociationEndMember targetMember)239         public IEntityWrapper HandleFullSpanCollection<T_SourceEntity, T_TargetEntity>(IEntityWrapper wrappedEntity, Coordinator<T_TargetEntity> coordinator, AssociationEndMember targetMember)
240         {
241             Debug.Assert(null != wrappedEntity, "wrapped entity is null");
242             if (null != wrappedEntity.Entity)
243             {
244                 coordinator.RegisterCloseHandler((state, spannedEntities) => FullSpanAction(wrappedEntity, spannedEntities, targetMember));
245             }
246             return wrappedEntity;
247         }
248 
249         /// <summary>
250         /// Call to ensure a single full-spanned element is added into
251         /// the state manager properly.
252         /// </summary>
HandleFullSpanElement(IEntityWrapper wrappedSource, IEntityWrapper wrappedSpannedEntity, AssociationEndMember targetMember)253         public IEntityWrapper HandleFullSpanElement<T_SourceEntity, T_TargetEntity>(IEntityWrapper wrappedSource, IEntityWrapper wrappedSpannedEntity, AssociationEndMember targetMember)
254         {
255             Debug.Assert(null != wrappedSource, "wrapped entity is null");
256             if (wrappedSource.Entity == null)
257             {
258                 return wrappedSource;
259             }
260             List<IEntityWrapper> spannedEntities = null;
261             if (wrappedSpannedEntity.Entity != null)
262             {
263                 // There was a single entity in the column
264                 // Create a list so we can perform the same logic as a collection of entities
265                 spannedEntities = new List<IEntityWrapper>(1);
266                 spannedEntities.Add(wrappedSpannedEntity);
267             }
268             else
269             {
270                 EntityKey sourceKey = wrappedSource.EntityKey;
271                 CheckClearedEntryOnSpan(null, wrappedSource, sourceKey, targetMember);
272             }
273             FullSpanAction(wrappedSource, spannedEntities, targetMember);
274             return wrappedSource;
275         }
276 
277         /// <summary>
278         /// Call to ensure a target entities key is added into the state manager
279         /// properly
280         /// </summary>
HandleRelationshipSpan(IEntityWrapper wrappedEntity, EntityKey targetKey, AssociationEndMember targetMember)281         public IEntityWrapper HandleRelationshipSpan<T_SourceEntity>(IEntityWrapper wrappedEntity, EntityKey targetKey, AssociationEndMember targetMember)
282         {
283             if (null == wrappedEntity.Entity)
284             {
285                 return wrappedEntity;
286             }
287             Debug.Assert(targetMember != null);
288             Debug.Assert(targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One || targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
289 
290             EntityKey sourceKey = wrappedEntity.EntityKey;
291             AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
292             CheckClearedEntryOnSpan(targetKey, wrappedEntity, sourceKey, targetMember);
293 
294             if (null != (object)targetKey)
295             {
296                 EntitySet targetEntitySet;
297 
298                 EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
299                     targetKey.EntityContainerName, DataSpace.CSpace);
300 
301                 // find the correct AssociationSet
302                 AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer,
303                     targetKey.EntitySetName, (AssociationType)(targetMember.DeclaringType), targetMember.Name, out targetEntitySet);
304                 Debug.Assert(associationSet != null, "associationSet should not be null");
305 
306                 ObjectStateManager manager = Context.ObjectStateManager;
307                 EntityState newEntryState;
308                 // If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one
309                 if (!ObjectStateManager.TryUpdateExistingRelationships(this.Context, this.MergeOption, associationSet, sourceMember, sourceKey, wrappedEntity, targetMember, targetKey, /*setIsLoaded*/ true, out newEntryState))
310                 {
311                     // Try to find a state entry for the target key
312                     EntityEntry targetEntry = null;
313                     if (!manager.TryGetEntityEntry(targetKey, out targetEntry))
314                     {
315                         // no entry exists for the target key
316                         // create a key entry for the target
317                         targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
318                     }
319 
320                     // SQLBU 557105. For 1-1 relationships we have to take care of the relationships of targetEntity
321                     bool needNewRelationship = true;
322                     switch (sourceMember.RelationshipMultiplicity)
323                     {
324                         case RelationshipMultiplicity.ZeroOrOne:
325                         case RelationshipMultiplicity.One:
326                             // devnote: targetEntry can be a key entry (targetEntry.Entity == null),
327                             // but it that case this parameter won't be used in TryUpdateExistingRelationships
328                             needNewRelationship = !ObjectStateManager.TryUpdateExistingRelationships(this.Context,
329                                 this.MergeOption,
330                                 associationSet,
331                                 targetMember,
332                                 targetKey,
333                                 targetEntry.WrappedEntity,
334                                 sourceMember,
335                                 sourceKey,
336                                 /*setIsLoaded*/ true,
337                                 out newEntryState);
338 
339                             // It is possible that as part of removing existing relationships, the key entry was deleted
340                             // If that is the case, recreate the key entry
341                             if (targetEntry.State == EntityState.Detached)
342                             {
343                                 targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
344                             }
345                             break;
346                         case RelationshipMultiplicity.Many:
347                             // we always need a new relationship with Many-To-Many, if there was no exact match between these two entities, so do nothing
348                             break;
349                         default:
350                             Debug.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity");
351                             break;
352                     }
353 
354                     if (needNewRelationship)
355                     {
356 
357                         // If the target entry is a key entry, then we need to add a relation
358                         //   between the source and target entries
359                         // If we are in a state where we just need to add a new Deleted relation, we
360                         //   only need to do that and not touch the related ends
361                         // If the target entry is a full entity entry, then we need to add
362                         //   the target entity to the source collection or reference
363                         if (targetEntry.IsKeyEntry || newEntryState == EntityState.Deleted)
364                         {
365                             // Add a relationship between the source entity and the target key entry
366                             RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
367                             manager.AddNewRelation(wrapper, newEntryState);
368                         }
369                         else
370                         {
371                             Debug.Assert(!targetEntry.IsRelationship, "how IsRelationship?");
372                             if (targetEntry.State != EntityState.Deleted)
373                             {
374                                 // The entry contains an entity, do collection or reference fixup
375                                 // This will also try to create a new relationship entry or will revert the delete on an existing deleted relationship
376                                 ObjectStateManager.AddEntityToCollectionOrReference(
377                                     this.MergeOption, wrappedEntity, sourceMember,
378                                     targetEntry.WrappedEntity,
379                                     targetMember,
380                                     /*setIsLoaded*/ true,
381                                     /*relationshipAlreadyExists*/ false,
382                                     /* inKeyEntryPromotion */ false);
383                             }
384                             else
385                             {
386                                 // if the target entry is deleted, then the materializer needs to create a deleted relationship
387                                 // between the entity and the target entry so that if the entity is deleted, the update
388                                 // pipeline can find the relationship (even though it is deleted)
389                                 RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
390                                 manager.AddNewRelation(wrapper, EntityState.Deleted);
391                             }
392                         }
393                     }
394                 }
395             }
396             else
397             {
398                 RelatedEnd relatedEnd;
399                 if(TryGetRelatedEnd(wrappedEntity, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd))
400                 {
401                     SetIsLoadedForSpan(relatedEnd, false);
402                 }
403             }
404 
405             // else there is nothing else for us to do, the relationship has been handled already
406             return wrappedEntity;
407         }
408 
TryGetRelatedEnd(IEntityWrapper wrappedEntity, AssociationType associationType, string sourceEndName, string targetEndName, out RelatedEnd relatedEnd)409         private bool TryGetRelatedEnd(IEntityWrapper wrappedEntity, AssociationType associationType, string sourceEndName, string targetEndName, out RelatedEnd relatedEnd)
410         {
411             Debug.Assert(associationType.DataSpace == DataSpace.CSpace);
412 
413             // Get the OSpace AssociationType
414             AssociationType oSpaceAssociation;
415             if (!AssociationSpaceMap.TryGetValue((AssociationType)associationType, out oSpaceAssociation))
416             {
417                 oSpaceAssociation = this.Workspace.GetItemCollection(DataSpace.OSpace).GetItem<AssociationType>(associationType.FullName);
418                 AssociationSpaceMap[(AssociationType)associationType] = oSpaceAssociation;
419             }
420 
421             AssociationEndMember sourceEnd = null;
422             AssociationEndMember targetEnd = null;
423             foreach (var end in oSpaceAssociation.AssociationEndMembers)
424             {
425                 if (end.Name == sourceEndName)
426                 {
427                     sourceEnd = end;
428                 }
429                 else if (end.Name == targetEndName)
430                 {
431                     targetEnd = end;
432                 }
433             }
434 
435             if (sourceEnd != null && targetEnd != null)
436             {
437                 bool createRelatedEnd = false;
438                 if (wrappedEntity.EntityKey == null)
439                 {
440                     // Free-floating entity--key is null, so don't have EntitySet for validation, so always create RelatedEnd
441                     createRelatedEnd = true;
442                 }
443                 else
444                 {
445                     // It is possible, because of MEST, that we're trying to load a relationship that is valid for this EntityType
446                     // in metadata, but is not valid in this case because the specific entity is part of an EntitySet that is not
447                     // mapped in any AssociationSet for this association type.
448                     // The metadata structure makes checking for this somewhat time consuming because of the loop required.
449                     // Because the whole reason for this method is perf, we try to reduce the
450                     // impact of this check by caching positive hits in a HashSet so we don't have to do this for
451                     // every entity in a query.  (We could also cache misses, but since these only happen in MEST, which
452                     // is not common, we decided not to slow down the normal non-MEST case anymore by doing this.)
453                     var entitySet = wrappedEntity.EntityKey.GetEntitySet(this.Workspace);
454                     var relatedEndKey = Tuple.Create<string, string, string>(entitySet.Identity, associationType.Identity, sourceEndName);
455 
456                     if (_relatedEndCache == null)
457                     {
458                         _relatedEndCache = new HashSet<Tuple<string, string, string>>();
459                     }
460 
461                     if (_relatedEndCache.Contains(relatedEndKey))
462                     {
463                         createRelatedEnd = true;
464                     }
465                     else
466                     {
467                         foreach (var entitySetBase in entitySet.EntityContainer.BaseEntitySets)
468                         {
469                             if ((EdmType)entitySetBase.ElementType == associationType)
470                             {
471                                 if (((AssociationSet)entitySetBase).AssociationSetEnds[sourceEndName].EntitySet == entitySet)
472                                 {
473                                     createRelatedEnd = true;
474                                     _relatedEndCache.Add(relatedEndKey);
475                                     break;
476                                 }
477                             }
478                         }
479                     }
480                 }
481                 if (createRelatedEnd)
482                 {
483                     relatedEnd = LightweightCodeGenerator.GetRelatedEnd(wrappedEntity.RelationshipManager, sourceEnd, targetEnd, null);
484                     return true;
485                 }
486             }
487 
488             relatedEnd = null;
489             return false;
490         }
491 
492         /// <summary>
493         /// Sets the IsLoaded flag to "true"
494         /// There are also rules for when this can be set based on MergeOption and the current value(s) in the related end.
495         /// </summary>
SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)496         private void SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)
497         {
498             Debug.Assert(relatedEnd != null, "RelatedEnd should not be null");
499 
500             // We can now say this related end is "Loaded"
501             // The cases where we should set this to true are:
502             // AppendOnly: the related end is empty and does not point to a stub
503             // PreserveChanges: the related end is empty and does not point to a stub (otherwise, an Added item exists and IsLoaded should not change)
504             // OverwriteChanges: always
505             // NoTracking: always
506             if (!forceToTrue)
507             {
508                 // Detect the empty value state of the relatedEnd
509                 forceToTrue = relatedEnd.IsEmpty();
510                 EntityReference reference = relatedEnd as EntityReference;
511                 if (reference != null)
512                 {
513                     forceToTrue &= reference.EntityKey == null;
514                 }
515             }
516             if (forceToTrue || this.MergeOption == MergeOption.OverwriteChanges)
517             {
518                 relatedEnd.SetIsLoaded(true);
519             }
520         }
521 
522         /// <summary>
523         /// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
524         /// Calls through to HandleEntity after retrieving the EntityKey from the given entity.
525         /// Still need this so that the correct key will be used for iPOCOs that implement IEntityWithKey
526         /// in a non-default manner.
527         /// </summary>
HandleIEntityWithKey(IEntityWrapper wrappedEntity, EntitySet entitySet)528         public IEntityWrapper HandleIEntityWithKey<TEntity>(IEntityWrapper wrappedEntity, EntitySet entitySet)
529         {
530             Debug.Assert(null != wrappedEntity, "wrapped entity is null");
531             return HandleEntity<TEntity>(wrappedEntity, wrappedEntity.EntityKey, entitySet);
532         }
533 
534         /// <summary>
535         /// Calls through to the specified RecordState to set the value for the specified column ordinal.
536         /// </summary>
SetColumnValue(int recordStateSlotNumber, int ordinal, object value)537         public bool SetColumnValue(int recordStateSlotNumber, int ordinal, object value)
538         {
539             RecordState recordState = (RecordState)this.State[recordStateSlotNumber];
540             recordState.SetColumnValue(ordinal, value);
541             return true;  // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
542         }
543 
544         /// <summary>
545         /// Calls through to the specified RecordState to set the value for the EntityRecordInfo.
546         /// </summary>
SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)547         public bool SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)
548         {
549             RecordState recordState = (RecordState)this.State[recordStateSlotNumber];
550             recordState.SetEntityRecordInfo(entityKey, entitySet);
551             return true;  // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
552         }
553 
554         /// <summary>
555         /// REQUIRES:: should be called only by delegate allocating this state.
556         /// Utility method assigning a value to a state slot. Returns an arbitrary value
557         /// allowing the method call to be composed in a ShapeEmitter Expression delegate.
558         /// </summary>
SetState(int ordinal, T value)559         public bool SetState<T>(int ordinal, T value)
560         {
561             this.State[ordinal] = value;
562             return true;  // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
563         }
564 
565         /// <summary>
566         /// REQUIRES:: should be called only by delegate allocating this state.
567         /// Utility method assigning a value to a state slot and return the value, allowing
568         /// the value to be accessed/set in a ShapeEmitter Expression delegate and later
569         /// retrieved.
570         /// </summary>
SetStatePassthrough(int ordinal, T value)571         public T SetStatePassthrough<T>(int ordinal, T value)
572         {
573             this.State[ordinal] = value;
574             return value;
575         }
576 
577         /// <summary>
578         /// Used to retrieve a property value with exception handling. Normally compiled
579         /// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
580         /// but when an exception occurs we retry using this method to potentially get
581         /// a more useful error message to the user.
582         /// </summary>
GetPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName)583         public TProperty GetPropertyValueWithErrorHandling<TProperty>(int ordinal, string propertyName, string typeName)
584         {
585             TProperty result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName).GetValue(this.Reader, ordinal);
586             return result;
587         }
588 
589         /// <summary>
590         /// Used to retrieve a column value with exception handling. Normally compiled
591         /// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
592         /// but when an exception occurs we retry using this method to potentially get
593         /// a more useful error message to the user.
594         /// </summary>
GetColumnValueWithErrorHandling(int ordinal)595         public TColumn GetColumnValueWithErrorHandling<TColumn>(int ordinal)
596         {
597             TColumn result = new ColumnErrorHandlingValueReader<TColumn>().GetValue(this.Reader, ordinal);
598             return result;
599         }
600 
CreateSpatialDataReader()601         private DbSpatialDataReader CreateSpatialDataReader()
602         {
603             return SpatialHelpers.CreateSpatialDataReader(this.Workspace, this.Reader);
604         }
605         private readonly Singleton<DbSpatialDataReader> spatialReader;
606 
GetGeographyColumnValue(int ordinal)607         public DbGeography GetGeographyColumnValue(int ordinal)
608         {
609             return this.spatialReader.Value.GetGeography(ordinal);
610         }
611 
GetGeometryColumnValue(int ordinal)612         public DbGeometry GetGeometryColumnValue(int ordinal)
613         {
614             return this.spatialReader.Value.GetGeometry(ordinal);
615         }
616 
GetSpatialColumnValueWithErrorHandling(int ordinal, PrimitiveTypeKind spatialTypeKind)617         public TColumn GetSpatialColumnValueWithErrorHandling<TColumn>(int ordinal, PrimitiveTypeKind spatialTypeKind)
618         {
619             Debug.Assert(spatialTypeKind == PrimitiveTypeKind.Geography || spatialTypeKind == PrimitiveTypeKind.Geometry, "Spatial primitive type kind is not geography or geometry?");
620 
621             TColumn result;
622             if (spatialTypeKind == PrimitiveTypeKind.Geography)
623             {
624                 result = new ColumnErrorHandlingValueReader<TColumn>(
625                                 (reader, column) => (TColumn)(object)this.spatialReader.Value.GetGeography(column),
626                                 (reader, column) => this.spatialReader.Value.GetGeography(column)
627                          ).GetValue(this.Reader, ordinal);
628             }
629             else
630             {
631                 result = new ColumnErrorHandlingValueReader<TColumn>(
632                                 (reader, column) => (TColumn)(object)this.spatialReader.Value.GetGeometry(column),
633                                 (reader, column) => this.spatialReader.Value.GetGeometry(column)
634                          ).GetValue(this.Reader, ordinal);
635             }
636             return result;
637         }
638 
GetSpatialPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName, PrimitiveTypeKind spatialTypeKind)639         public TProperty GetSpatialPropertyValueWithErrorHandling<TProperty>(int ordinal, string propertyName, string typeName, PrimitiveTypeKind spatialTypeKind)
640         {
641             TProperty result;
642             if (Helper.IsGeographicTypeKind(spatialTypeKind))
643             {
644                 result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName,
645                                 (reader, column) => (TProperty)(object)this.spatialReader.Value.GetGeography(column),
646                                 (reader, column) => this.spatialReader.Value.GetGeography(column)
647                          ).GetValue(this.Reader, ordinal);
648             }
649             else
650             {
651                 Debug.Assert(Helper.IsGeometricTypeKind(spatialTypeKind));
652                 result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName,
653                             (reader, column) => (TProperty)(object)this.spatialReader.Value.GetGeometry(column),
654                             (reader, column) => this.spatialReader.Value.GetGeometry(column)
655                      ).GetValue(this.Reader, ordinal);
656             }
657 
658             return result;
659         }
660 
661         #endregion
662 
663         #region helper methods (used by runtime callable code)
664 
CheckClearedEntryOnSpan(object targetValue, IEntityWrapper wrappedSource, EntityKey sourceKey, AssociationEndMember targetMember)665         private void CheckClearedEntryOnSpan(object targetValue, IEntityWrapper wrappedSource, EntityKey sourceKey, AssociationEndMember targetMember)
666         {
667             // If a relationship does not exist on the server but does exist on the client,
668             // we may need to remove it, depending on the current state and the MergeOption
669             if ((null != (object)sourceKey) && (null == targetValue) &&
670                 (this.MergeOption == MergeOption.PreserveChanges ||
671                  this.MergeOption == MergeOption.OverwriteChanges))
672             {
673                 // When the spanned value is null, it may be because the spanned association applies to a
674                 // subtype of the entity's type, and the entity is not actually an instance of that type.
675                 AssociationEndMember sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetMember);
676                 EdmType expectedSourceType = ((RefType)sourceEnd.TypeUsage.EdmType).ElementType;
677                 TypeUsage entityTypeUsage;
678                 if (!this.Context.Perspective.TryGetType(wrappedSource.IdentityType, out entityTypeUsage) ||
679                     entityTypeUsage.EdmType.EdmEquals(expectedSourceType) ||
680                     TypeSemantics.IsSubTypeOf(entityTypeUsage.EdmType, expectedSourceType))
681                 {
682                     // Otherwise, the source entity is the correct type (exactly or a subtype) for the source
683                     // end of the spanned association, so validate that the relationhip that was spanned is
684                     // part of the Container owning the EntitySet of the root entity.
685                     // This can be done by comparing the EntitySet  of the row's entity to the relationships
686                     // in the Container and their AssociationSetEnd's type
687                     CheckClearedEntryOnSpan(sourceKey, wrappedSource, targetMember);
688                 }
689             }
690         }
691 
CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember)692         private void CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember)
693         {
694             Debug.Assert(null != (object)sourceKey);
695             Debug.Assert(wrappedSource != null);
696             Debug.Assert(wrappedSource.Entity != null);
697             Debug.Assert(targetMember != null);
698             Debug.Assert(this.Context != null);
699 
700             AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
701 
702             EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(sourceKey.EntityContainerName,
703                 DataSpace.CSpace);
704             EntitySet sourceEntitySet;
705             AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, sourceKey.EntitySetName,
706                 (AssociationType)sourceMember.DeclaringType, sourceMember.Name, out sourceEntitySet);
707 
708             if (associationSet != null)
709             {
710                 Debug.Assert(associationSet.AssociationSetEnds[sourceMember.Name].EntitySet == sourceEntitySet);
711                 ObjectStateManager.RemoveRelationships(Context, MergeOption, associationSet, sourceKey, sourceMember);
712             }
713         }
714 
715         /// <summary>
716         /// Wire's one or more full-spanned entities into the state manager; used by
717         /// both full-spanned collections and full-spanned entities.
718         /// </summary>
FullSpanAction(IEntityWrapper wrappedSource, IList<T_TargetEntity> spannedEntities, AssociationEndMember targetMember)719         private void FullSpanAction<T_TargetEntity>(IEntityWrapper wrappedSource, IList<T_TargetEntity> spannedEntities, AssociationEndMember targetMember)
720         {
721             Debug.Assert(null != wrappedSource, "wrapped entity is null");
722 
723             if (wrappedSource.Entity != null)
724             {
725                 EntityKey sourceKey = wrappedSource.EntityKey;
726                 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
727 
728                 RelatedEnd relatedEnd;
729                 if (TryGetRelatedEnd(wrappedSource, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd))
730                 {
731                     // Add members of the list to the source entity (item in column 0)
732                     int count = ObjectStateManager.UpdateRelationships(this.Context, this.MergeOption, (AssociationSet)relatedEnd.RelationshipSet, sourceMember, sourceKey, wrappedSource, targetMember, (List<T_TargetEntity>)spannedEntities, true);
733 
734                     SetIsLoadedForSpan(relatedEnd, count > 0);
735                 }
736             }
737         }
738 
739         #region update existing ObjectStateEntry
740 
UpdateEntry(IEntityWrapper wrappedEntity, EntityEntry existingEntry)741         private void UpdateEntry<TEntity>(IEntityWrapper wrappedEntity, EntityEntry existingEntry)
742         {
743             Debug.Assert(null != wrappedEntity, "wrapped entity is null");
744             Debug.Assert(null != wrappedEntity.Entity, "null entity");
745             Debug.Assert(null != existingEntry, "null ObjectStateEntry");
746             Debug.Assert(null != existingEntry.Entity, "ObjectStateEntry without Entity");
747 
748             Type clrType = typeof(TEntity);
749             if (clrType != existingEntry.WrappedEntity.IdentityType)
750             {
751                 throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, clrType, existingEntry.WrappedEntity.IdentityType);
752             }
753 
754             if (EntityState.Added == existingEntry.State)
755             {
756                 throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
757             }
758 
759             if (MergeOption.AppendOnly != MergeOption)
760             {   // existing entity, update CSpace values in place
761                 Debug.Assert(EntityState.Added != existingEntry.State, "entry in State=Added");
762                 Debug.Assert(EntityState.Detached != existingEntry.State, "entry in State=Detached");
763 
764                 if (MergeOption.OverwriteChanges == MergeOption)
765                 {
766                     if (EntityState.Deleted == existingEntry.State)
767                     {
768                         existingEntry.RevertDelete();
769                     }
770                     existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity);
771                     Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true);
772                     existingEntry.AcceptChanges();
773                     Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true);
774                 }
775                 else
776                 {
777                     Debug.Assert(MergeOption.PreserveChanges == MergeOption, "not MergeOption.PreserveChanges");
778                     if (EntityState.Unchanged == existingEntry.State)
779                     {
780                         // same behavior as MergeOption.OverwriteChanges
781                         existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity);
782                         Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true);
783                         existingEntry.AcceptChanges();
784                         Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true);
785                     }
786                     else
787                     {
788                         if (Context.ContextOptions.UseLegacyPreserveChangesBehavior)
789                         {
790                             // Do not mark properties as modified if they differ from the entity.
791                             existingEntry.UpdateRecordWithoutSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues);
792                         }
793                         else
794                         {
795                             // Mark properties as modified if they differ from the entity
796                             existingEntry.UpdateRecordWithSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues);
797                         }
798                     }
799                 }
800             }
801         }
802 
803         #endregion
804 
805         #endregion
806 
807         #region nested types
808         private abstract class ErrorHandlingValueReader<T>
809         {
810             private readonly Func<DbDataReader, int, T> getTypedValue;
811             private readonly Func<DbDataReader, int, object> getUntypedValue;
812 
ErrorHandlingValueReader(Func<DbDataReader, int, T> typedValueAccessor, Func<DbDataReader, int, object> untypedValueAccessor)813             protected ErrorHandlingValueReader(Func<DbDataReader, int, T> typedValueAccessor, Func<DbDataReader, int, object> untypedValueAccessor)
814             {
815                 this.getTypedValue = typedValueAccessor;
816                 this.getUntypedValue = untypedValueAccessor;
817             }
818 
ErrorHandlingValueReader()819             protected ErrorHandlingValueReader()
820                 : this(GetTypedValueDefault, GetUntypedValueDefault)
821             {
822             }
823 
GetTypedValueDefault(DbDataReader reader, int ordinal)824             private static T GetTypedValueDefault(DbDataReader reader, int ordinal)
825             {
826                 var underlyingType = Nullable.GetUnderlyingType(typeof(T));
827                 // The value read from the reader is of a primitive type. Such a value cannot be cast to a nullable enum type directly
828                 // but first needs to be cast to the non-nullable enum type. Therefore we will call this method for non-nullable
829                 // underlying enum type and cast to the target type.
830                 if (underlyingType != null && underlyingType.IsEnum)
831                 {
832                     var type = typeof(ErrorHandlingValueReader<>).MakeGenericType(underlyingType);
833                     return (T)type.GetMethod(MethodBase.GetCurrentMethod().Name, BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { reader, ordinal });
834                 }
835 
836                 // use the specific reader.GetXXX method
837                 bool isNullable;
838                 MethodInfo readerMethod = Translator.GetReaderMethod(typeof(T), out isNullable);
839                 T result = (T)readerMethod.Invoke(reader, new object[] { ordinal });
840                 return result;
841             }
842 
GetUntypedValueDefault(DbDataReader reader, int ordinal)843             private static object GetUntypedValueDefault(DbDataReader reader, int ordinal)
844             {
845                 return reader.GetValue(ordinal);
846             }
847 
848             /// <summary>
849             /// Gets value from reader using the same pattern as the materializer delegate. Avoids
850             /// the need to compile multiple delegates for error handling. If there is a failure
851             /// reading a value
852             /// </summary>
GetValue(DbDataReader reader, int ordinal)853             internal T GetValue(DbDataReader reader, int ordinal)
854             {
855                 T result;
856                 if (reader.IsDBNull(ordinal))
857                 {
858                     try
859                     {
860                         result = (T)(object)null;
861                     }
862                     catch (NullReferenceException)
863                     {
864                         // NullReferenceException is thrown when casting null to a value type.
865                         // We don't use isNullable here because of an issue with GetReaderMethod
866                         //
867                         throw CreateNullValueException();
868                     }
869                 }
870                 else
871                 {
872                     try
873                     {
874                         result = this.getTypedValue(reader, ordinal);
875                     }
876                     catch (Exception e)
877                     {
878                         if (EntityUtil.IsCatchableExceptionType(e))
879                         {
880                             // determine if the problem is with the result type
881                             // (note that if we throw on this call, it's ok
882                             // for it to percolate up -- we only intercept type
883                             // and null mismatches)
884                             object untypedResult = this.getUntypedValue(reader, ordinal);
885                             Type resultType = null == untypedResult ? null : untypedResult.GetType();
886                             if (!typeof(T).IsAssignableFrom(resultType))
887                             {
888                                 throw CreateWrongTypeException(resultType);
889                             }
890                         }
891                         throw;
892                     }
893                 }
894                 return result;
895             }
896 
897             /// <summary>
898             /// Creates the exception thrown when the reader returns a null value
899             /// for a non nullable property/column.
900             /// </summary>
CreateNullValueException()901             protected abstract Exception CreateNullValueException();
902 
903             /// <summary>
904             /// Creates the exception thrown when the reader returns a value with
905             /// an incompatible type.
906             /// </summary>
CreateWrongTypeException(Type resultType)907             protected abstract Exception CreateWrongTypeException(Type resultType);
908         }
909 
910         private class ColumnErrorHandlingValueReader<TColumn> : ErrorHandlingValueReader<TColumn>
911         {
ColumnErrorHandlingValueReader()912             internal ColumnErrorHandlingValueReader()
913             {
914             }
915 
ColumnErrorHandlingValueReader(Func<DbDataReader, int, TColumn> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)916             internal ColumnErrorHandlingValueReader(Func<DbDataReader, int, TColumn> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)
917                 : base(typedAccessor, untypedAccessor)
918             {
919             }
920 
CreateNullValueException()921             protected override Exception CreateNullValueException()
922             {
923                 return EntityUtil.ValueNullReferenceCast(typeof(TColumn));
924             }
925 
CreateWrongTypeException(Type resultType)926             protected override Exception  CreateWrongTypeException(Type resultType)
927             {
928                 return EntityUtil.ValueInvalidCast(resultType, typeof(TColumn));
929             }
930         }
931 
932         private class PropertyErrorHandlingValueReader<TProperty> : ErrorHandlingValueReader<TProperty>
933         {
934             private readonly string _propertyName;
935             private readonly string _typeName;
936 
PropertyErrorHandlingValueReader(string propertyName, string typeName)937             internal PropertyErrorHandlingValueReader(string propertyName, string typeName)
938                 : base()
939             {
940                 _propertyName = propertyName;
941                 _typeName = typeName;
942             }
943 
PropertyErrorHandlingValueReader(string propertyName, string typeName, Func<DbDataReader, int, TProperty> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)944             internal PropertyErrorHandlingValueReader(string propertyName, string typeName, Func<DbDataReader, int, TProperty> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)
945                 : base(typedAccessor, untypedAccessor)
946             {
947                 _propertyName = propertyName;
948                 _typeName = typeName;
949             }
950 
CreateNullValueException()951             protected override Exception CreateNullValueException()
952             {
953                 return EntityUtil.Constraint(
954                                         System.Data.Entity.Strings.Materializer_SetInvalidValue(
955                                         (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
956                                         _typeName, _propertyName, "null"));
957             }
958 
CreateWrongTypeException(Type resultType)959             protected override Exception CreateWrongTypeException(Type resultType)
960             {
961                 return EntityUtil.InvalidOperation(
962                                         System.Data.Entity.Strings.Materializer_SetInvalidValue(
963                                         (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
964                                         _typeName, _propertyName, resultType.Name));
965             }
966         }
967         #endregion
968 
969         #region OnMaterialized helpers
970 
RaiseMaterializedEvents()971         public void RaiseMaterializedEvents()
972         {
973             if (_materializedEntities != null)
974             {
975                 foreach (var wrappedEntity in _materializedEntities)
976                 {
977                     Context.OnObjectMaterialized(wrappedEntity.Entity);
978                 }
979                 _materializedEntities.Clear();
980             }
981         }
982 
InitializeForOnMaterialize()983         public void InitializeForOnMaterialize()
984         {
985             if (Context.OnMaterializedHasHandlers)
986             {
987                 if (_materializedEntities == null)
988                 {
989                     _materializedEntities = new List<IEntityWrapper>();
990                 }
991             }
992             else if (_materializedEntities != null)
993             {
994                 _materializedEntities = null;
995             }
996         }
997 
RegisterMaterializedEntityForEvent(IEntityWrapper wrappedEntity)998         protected void RegisterMaterializedEntityForEvent(IEntityWrapper wrappedEntity)
999         {
1000             if (_materializedEntities != null)
1001             {
1002                 _materializedEntities.Add(wrappedEntity);
1003             }
1004         }
1005 
1006         #endregion
1007     }
1008 
1009     /// <summary>
1010     /// Typed Shaper. Includes logic to enumerate results and wraps the _rootCoordinator,
1011     /// which includes materializer delegates for the root query collection.
1012     /// </summary>
1013     internal sealed class Shaper<T> : Shaper
1014     {
1015         #region private state
1016 
1017         /// <summary>
1018         /// Shapers and Coordinators work together in harmony to materialize the data
1019         /// from the store; the shaper contains the state, the coordinator contains the
1020         /// code.
1021         /// </summary>
1022         internal readonly Coordinator<T> RootCoordinator;
1023 
1024         /// <summary>
1025         /// Which type of query is this, object layer (true) or value layer (false)
1026         /// </summary>
1027         private readonly bool IsObjectQuery;
1028 
1029         /// <summary>
1030         /// Keeps track of whether we've completed processing or not.
1031         /// </summary>
1032         private bool _isActive;
1033 
1034         /// <summary>
1035         /// The enumerator we're using to read data; really only populated for value
1036         /// layer queries.
1037         /// </summary>
1038         private IEnumerator<T> _rootEnumerator;
1039 
1040         /// <summary>
1041         /// Whether the current value of _rootEnumerator has been returned by a bridge
1042         /// data reader.
1043         /// </summary>
1044         private bool _dataWaiting;
1045 
1046         /// <summary>
1047         /// Is the reader owned by the EF or was it supplied by the user?
1048         /// </summary>
1049         private bool _readerOwned;
1050 
1051         #endregion
1052 
1053         #region constructor
1054 
Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory<T> rootCoordinatorFactory, Action checkPermissions, bool readerOwned)1055         internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory<T> rootCoordinatorFactory, Action checkPermissions, bool readerOwned)
1056             : base(reader, context, workspace, mergeOption, stateCount)
1057         {
1058             RootCoordinator = new Coordinator<T>(rootCoordinatorFactory, /*parent*/ null, /*next*/ null);
1059             if (null != checkPermissions)
1060             {
1061                 checkPermissions();
1062             }
1063             IsObjectQuery = !(typeof(T) == typeof(RecordState));
1064             _isActive = true;
1065             RootCoordinator.Initialize(this);
1066             _readerOwned = readerOwned;
1067         }
1068 
1069         #endregion
1070 
1071         #region "public" surface area
1072 
1073         /// <summary>
1074         /// Events raised when the shaper has finished enumerating results. Useful for callback
1075         /// to set parameter values.
1076         /// </summary>
1077         internal event EventHandler OnDone;
1078 
1079         /// <summary>
1080         /// Used to handle the read-ahead requirements of value-layer queries.  This
1081         /// field indicates the status of the current value of the _rootEnumerator; when
1082         /// a bridge data reader "accepts responsibility" for the current value, it sets
1083         /// this to false.
1084         /// </summary>
1085         internal bool DataWaiting
1086         {
1087             get { return _dataWaiting; }
1088             set { _dataWaiting = value; }
1089         }
1090 
1091         /// <summary>
1092         /// The enumerator that the value-layer bridge will use to read data; all nested
1093         /// data readers need to use the same enumerator, so we put it on the Shaper, since
1094         /// that is something that all the nested data readers (and data records) have access
1095         /// to -- it prevents us from having to pass two objects around.
1096         /// </summary>
1097         internal IEnumerator<T> RootEnumerator
1098         {
1099             get
1100             {
1101                 if (_rootEnumerator == null)
1102                 {
1103                     InitializeRecordStates(RootCoordinator.CoordinatorFactory);
1104                     _rootEnumerator = GetEnumerator();
1105                 }
1106                 return _rootEnumerator;
1107             }
1108         }
1109 
1110         /// <summary>
1111         /// Initialize the RecordStateFactory objects in their StateSlots.
1112         /// </summary>
InitializeRecordStates(CoordinatorFactory coordinatorFactory)1113         private void InitializeRecordStates(CoordinatorFactory coordinatorFactory)
1114         {
1115             foreach (RecordStateFactory recordStateFactory in coordinatorFactory.RecordStateFactories)
1116             {
1117                 State[recordStateFactory.StateSlotNumber] = recordStateFactory.Create(coordinatorFactory);
1118             }
1119 
1120             foreach (CoordinatorFactory nestedCoordinatorFactory in coordinatorFactory.NestedCoordinators)
1121             {
1122                 InitializeRecordStates(nestedCoordinatorFactory);
1123             }
1124         }
1125 
GetEnumerator()1126         public IEnumerator<T> GetEnumerator()
1127         {
1128             // we can use a simple enumerator if there are no nested results, no keys and no "has data"
1129             // discriminator
1130             if (RootCoordinator.CoordinatorFactory.IsSimple)
1131             {
1132                 return new SimpleEnumerator(this);
1133             }
1134             else
1135             {
1136                 RowNestedResultEnumerator rowEnumerator = new Shaper<T>.RowNestedResultEnumerator(this);
1137 
1138                 if (this.IsObjectQuery)
1139                 {
1140                     return new ObjectQueryNestedEnumerator(rowEnumerator);
1141                 }
1142                 else
1143                 {
1144                     return (IEnumerator<T>)(object)(new RecordStateEnumerator(rowEnumerator));
1145                 }
1146             }
1147         }
1148 
1149         #endregion
1150 
1151         #region enumerator helpers
1152 
1153         /// <summary>
1154         /// Called when enumeration of results has completed.
1155         /// </summary>
Finally()1156         private void Finally()
1157         {
1158             if (_isActive)
1159             {
1160                 _isActive = false;
1161 
1162                 if (_readerOwned)
1163                 {
1164                     // I'd prefer not to special case this, but value-layer behavior is that you
1165                     // must explicitly close the data reader; if we automatically dispose of the
1166                     // reader here, we won't have that behavior.
1167                     if (IsObjectQuery)
1168                     {
1169                         this.Reader.Dispose();
1170                     }
1171 
1172                     // This case includes when the ObjectResult is disposed before it
1173                     // created an ObjectQueryEnumeration; at this time, the connection can be released
1174                     if (this.Context != null)
1175                     {
1176                         this.Context.ReleaseConnection();
1177                     }
1178                 }
1179 
1180                 if (null != this.OnDone)
1181                 {
1182                     this.OnDone(this, new EventArgs());
1183                 }
1184             }
1185         }
1186 
1187         /// <summary>
1188         /// Reads the next row from the store. If there is a failure, throws an exception message
1189         /// in some scenarios (note that we respond to failure rather than anticipate failure,
1190         /// avoiding repeated checks in the inner materialization loop)
1191         /// </summary>
StoreRead()1192         private bool StoreRead()
1193         {
1194             bool readSucceeded;
1195             try
1196             {
1197                 readSucceeded = this.Reader.Read();
1198             }
1199             catch (Exception e)
1200             {
1201                 // check if the reader is closed; if so, throw friendlier exception
1202                 if (this.Reader.IsClosed)
1203                 {
1204                     const string operation = "Read";
1205                     throw EntityUtil.DataReaderClosed(operation);
1206                 }
1207 
1208                 // wrap exception if necessary
1209                 if (EntityUtil.IsCatchableEntityExceptionType(e))
1210                 {
1211                     throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_StoreReaderFailed, e);
1212                 }
1213                 throw;
1214             }
1215             return readSucceeded;
1216         }
1217 
1218         /// <summary>
1219         /// Notify ObjectContext that we are about to start materializing an element
1220         /// </summary>
StartMaterializingElement()1221         private void StartMaterializingElement()
1222         {
1223             if (Context != null)
1224             {
1225                 Context.InMaterialization = true;
1226                 InitializeForOnMaterialize();
1227             }
1228         }
1229 
1230         /// <summary>
1231         /// Notify ObjectContext that we are finished materializing the element
1232         /// </summary>
StopMaterializingElement()1233         private void StopMaterializingElement()
1234         {
1235             if (Context != null)
1236             {
1237                 Context.InMaterialization = false;
1238                 RaiseMaterializedEvents();
1239             }
1240         }
1241 
1242         #endregion
1243 
1244         #region simple enumerator
1245 
1246         /// <summary>
1247         /// Optimized enumerator for queries not including nested results.
1248         /// </summary>
1249         private class SimpleEnumerator : IEnumerator<T>
1250         {
1251             private readonly Shaper<T> _shaper;
1252 
SimpleEnumerator(Shaper<T> shaper)1253             internal SimpleEnumerator(Shaper<T> shaper)
1254             {
1255                 _shaper = shaper;
1256             }
1257 
1258             public T Current
1259             {
1260                 get { return _shaper.RootCoordinator.Current; }
1261             }
1262 
1263             object System.Collections.IEnumerator.Current
1264             {
1265                 get { return _shaper.RootCoordinator.Current; }
1266             }
1267 
Dispose()1268             public void Dispose()
1269             {
1270                 // Technically, calling GC.SuppressFinalize is not required because the class does not
1271                 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1272                 // in the future, and prevents an FxCop warning.
1273                 GC.SuppressFinalize(this);
1274                 // For backwards compatibility, we set the current value to the
1275                 // default value, so you can still call Current.
1276                 _shaper.RootCoordinator.SetCurrentToDefault();
1277                 _shaper.Finally();
1278             }
1279 
MoveNext()1280             public bool MoveNext()
1281             {
1282                 if (!_shaper._isActive)
1283                 {
1284                     return false;
1285                 }
1286                 if (_shaper.StoreRead())
1287                 {
1288                     try
1289                     {
1290                         _shaper.StartMaterializingElement();
1291                         _shaper.RootCoordinator.ReadNextElement(_shaper);
1292                     }
1293                     finally
1294                     {
1295                         _shaper.StopMaterializingElement();
1296                     }
1297                     return true;
1298                 }
1299                 this.Dispose();
1300                 return false;
1301             }
1302 
Reset()1303             public void Reset()
1304             {
1305                 throw EntityUtil.NotSupported();
1306             }
1307         }
1308 
1309         #endregion
1310 
1311         #region nested enumerator
1312 
1313         /// <summary>
1314         /// Enumerates (for each row in the input) an array of all coordinators producing new elements. The array
1315         /// contains a position for each 'depth' in the result. A null value in any position indicates that no new
1316         /// results were produced for the given row at the given depth. It is possible for a row to contain no
1317         /// results for any row.
1318         /// </summary>
1319         private class RowNestedResultEnumerator : IEnumerator<Coordinator[]>
1320         {
1321             private readonly Shaper<T> _shaper;
1322             private readonly Coordinator[] _current;
1323 
RowNestedResultEnumerator(Shaper<T> shaper)1324             internal RowNestedResultEnumerator(Shaper<T> shaper)
1325             {
1326                 _shaper = shaper;
1327                 _current = new Coordinator[_shaper.RootCoordinator.MaxDistanceToLeaf() + 1];
1328             }
1329 
1330             public Coordinator[] Current
1331             {
1332                 get { return _current; }
1333             }
1334 
Dispose()1335             public void Dispose()
1336             {
1337                 // Technically, calling GC.SuppressFinalize is not required because the class does not
1338                 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1339                 // in the future, and prevents an FxCop warning.
1340                 GC.SuppressFinalize(this);
1341                 _shaper.Finally();
1342             }
1343 
1344             object System.Collections.IEnumerator.Current
1345             {
1346                 get { return _current; }
1347             }
1348 
MoveNext()1349             public bool MoveNext()
1350             {
1351                 Coordinator currentCoordinator = _shaper.RootCoordinator;
1352 
1353                 try
1354                 {
1355                     _shaper.StartMaterializingElement();
1356 
1357                     if (!_shaper.StoreRead())
1358                     {
1359                         // Reset all collections
1360                         this.RootCoordinator.ResetCollection(_shaper);
1361                         return false;
1362                     }
1363 
1364                     int depth = 0;
1365                     bool haveInitializedChildren = false;
1366                     for (; depth < _current.Length; depth++)
1367                     {
1368                         // find a coordinator at this depth that currently has data (if any)
1369                         while (currentCoordinator != null && !currentCoordinator.CoordinatorFactory.HasData(_shaper))
1370                         {
1371                             currentCoordinator = currentCoordinator.Next;
1372                         }
1373                         if (null == currentCoordinator)
1374                         {
1375                             break;
1376                         }
1377 
1378                         // check if this row contains a new element for this coordinator
1379                         if (currentCoordinator.HasNextElement(_shaper))
1380                         {
1381                             // if we have children and haven't initialized them yet, do so now
1382                             if (!haveInitializedChildren && null != currentCoordinator.Child)
1383                             {
1384                                 currentCoordinator.Child.ResetCollection(_shaper);
1385                             }
1386                             haveInitializedChildren = true;
1387 
1388                             // read the next element
1389                             currentCoordinator.ReadNextElement(_shaper);
1390 
1391                             // place the coordinator in the result array to indicate there is a new
1392                             // element at this depth
1393                             _current[depth] = currentCoordinator;
1394                         }
1395                         else
1396                         {
1397                             // clear out the coordinator in result array to indicate there is no new
1398                             // element at this depth
1399                             _current[depth] = null;
1400                         }
1401 
1402                         // move to child (in the next iteration we deal with depth + 1
1403                         currentCoordinator = currentCoordinator.Child;
1404                     }
1405 
1406                     // clear out all positions below the depth we reached before we ran out of data
1407                     for (; depth < _current.Length; depth++)
1408                     {
1409                         _current[depth] = null;
1410                     }
1411                 }
1412                 finally
1413                 {
1414                     _shaper.StopMaterializingElement();
1415                 }
1416 
1417                 return true;
1418             }
1419 
Reset()1420             public void Reset()
1421             {
1422                 throw EntityUtil.NotSupported();
1423             }
1424 
1425             internal Coordinator<T> RootCoordinator
1426             {
1427                 get { return _shaper.RootCoordinator; }
1428             }
1429         }
1430 
1431         /// <summary>
1432         /// Wraps RowNestedResultEnumerator and yields results appropriate to an ObjectQuery instance. In particular,
1433         /// root level elements (T) are returned only after aggregating all child elements.
1434         /// </summary>
1435         private class ObjectQueryNestedEnumerator : IEnumerator<T>
1436         {
1437             private readonly RowNestedResultEnumerator _rowEnumerator;
1438             private T _previousElement;
1439             private State _state;
1440 
ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)1441             internal ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)
1442             {
1443                 _rowEnumerator = rowEnumerator;
1444                 _previousElement = default(T);
1445                 _state = State.Start;
1446             }
1447 
1448             public T Current { get { return _previousElement; } }
1449 
Dispose()1450             public void Dispose()
1451             {
1452                 // Technically, calling GC.SuppressFinalize is not required because the class does not
1453                 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1454                 // in the future, and prevents an FxCop warning.
1455                 GC.SuppressFinalize(this);
1456                 _rowEnumerator.Dispose();
1457             }
1458 
1459             object System.Collections.IEnumerator.Current { get { return this.Current; } }
1460 
MoveNext()1461             public bool MoveNext()
1462             {
1463                 // See the documentation for enum State to understand the behaviors and requirements
1464                 // for each state.
1465                 switch (_state)
1466                 {
1467                     case State.Start:
1468                         {
1469                             if (TryReadToNextElement())
1470                             {
1471                                 // if there's an element in the reader...
1472                                 ReadElement();
1473                             }
1474                             else
1475                             {
1476                                 // no data at all...
1477                                 _state = State.NoRows;
1478                             }
1479                         };
1480                         break;
1481                     case State.Reading:
1482                         {
1483                             ReadElement();
1484                         };
1485                         break;
1486                     case State.NoRowsLastElementPending:
1487                         {
1488                             // nothing to do but move to the next state...
1489                             _state = State.NoRows;
1490                         };
1491                         break;
1492                 }
1493 
1494                 bool result;
1495                 if (_state == State.NoRows)
1496                 {
1497                     _previousElement = default(T);
1498                     result = false;
1499                 }
1500                 else
1501                 {
1502                     result = true;
1503                 }
1504 
1505                 return result;
1506             }
1507 
1508             /// <summary>
1509             /// Requires: the row is currently positioned at the start of an element.
1510             ///
1511             /// Reads all rows in the element and sets up state for the next element (if any).
1512             /// </summary>
ReadElement()1513             private void ReadElement()
1514             {
1515                 // remember the element we're currently reading
1516                 _previousElement = _rowEnumerator.RootCoordinator.Current;
1517 
1518                 // now we need to read to the next element (or the end of the
1519                 // reader) so that we can return the first element
1520                 if (TryReadToNextElement())
1521                 {
1522                     // we're positioned at the start of the next element (which
1523                     // corresponds to the 'reading' state)
1524                     _state = State.Reading;
1525                 }
1526                 else
1527                 {
1528                     // we're positioned at the end of the reader
1529                     _state = State.NoRowsLastElementPending;
1530                 }
1531             }
1532 
1533             /// <summary>
1534             /// Reads rows until the start of a new element is found. If no element
1535             /// is found before all rows are consumed, returns false.
1536             /// </summary>
TryReadToNextElement()1537             private bool TryReadToNextElement()
1538             {
1539                 while (_rowEnumerator.MoveNext())
1540                 {
1541                     // if we hit a new element, return true
1542                     if (_rowEnumerator.Current[0] != null)
1543                     {
1544                         return true;
1545                     }
1546                 }
1547                 return false;
1548             }
1549 
Reset()1550             public void Reset()
1551             {
1552                 _rowEnumerator.Reset();
1553             }
1554 
1555             /// <summary>
1556             /// Describes the state of this enumerator with respect to the _rowEnumerator
1557             /// it wraps.
1558             /// </summary>
1559             private enum State
1560             {
1561                 /// <summary>
1562                 /// No rows have been read yet
1563                 /// </summary>
1564                 Start,
1565 
1566                 /// <summary>
1567                 /// Positioned at the start of a new root element. The previous element must
1568                 /// be stored in _previousElement. We read ahead in this manner so that
1569                 /// the previous element is fully populated (all of its children loaded)
1570                 /// before returning.
1571                 /// </summary>
1572                 Reading,
1573 
1574                 /// <summary>
1575                 /// Positioned past the end of the rows. The last element in the enumeration
1576                 /// has not yet been returned to the user however, and is stored in _previousElement.
1577                 /// </summary>
1578                 NoRowsLastElementPending,
1579 
1580                 /// <summary>
1581                 /// Positioned past the end of the rows. The last element has been returned to
1582                 /// the user.
1583                 /// </summary>
1584                 NoRows,
1585             }
1586         }
1587 
1588         /// <summary>
1589         /// Wraps RowNestedResultEnumerator and yields results appropriate to an EntityReader instance. In particular,
1590         /// yields RecordState whenever a new element becomes available at any depth in the result hierarchy.
1591         /// </summary>
1592         private class RecordStateEnumerator : IEnumerator<RecordState>
1593         {
1594             private readonly RowNestedResultEnumerator _rowEnumerator;
1595             private RecordState _current;
1596 
1597             /// <summary>
1598             /// Gets depth of coordinator we're currently consuming. If _depth == -1, it means we haven't started
1599             /// to consume the next row yet.
1600             /// </summary>
1601             private int _depth;
1602             private bool _readerConsumed;
1603 
RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator)1604             internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator)
1605             {
1606                 _rowEnumerator = rowEnumerator;
1607                 _current = null;
1608                 _depth = -1;
1609                 _readerConsumed = false;
1610             }
1611 
1612             public RecordState Current
1613             {
1614                 get { return _current; }
1615             }
1616 
Dispose()1617             public void Dispose()
1618             {
1619                 // Technically, calling GC.SuppressFinalize is not required because the class does not
1620                 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1621                 // in the future, and prevents an FxCop warning.
1622                 GC.SuppressFinalize(this);
1623                 _rowEnumerator.Dispose();
1624             }
1625 
1626             object System.Collections.IEnumerator.Current
1627             {
1628                 get { return _current; }
1629             }
1630 
MoveNext()1631             public bool MoveNext()
1632             {
1633                 if (!_readerConsumed)
1634                 {
1635                     while (true)
1636                     {
1637                         // keep on cycling until we find a result
1638                         if (-1 == _depth || _rowEnumerator.Current.Length == _depth)
1639                         {
1640                             // time to move to the next row...
1641                             if (!_rowEnumerator.MoveNext())
1642                             {
1643                                 // no more rows...
1644                                 _current = null;
1645                                 _readerConsumed = true;
1646                                 break;
1647                             }
1648 
1649                             _depth = 0;
1650                         }
1651 
1652                         // check for results at the current depth
1653                         Coordinator currentCoordinator = _rowEnumerator.Current[_depth];
1654                         if (null != currentCoordinator)
1655                         {
1656                             _current = ((Coordinator<RecordState>)currentCoordinator).Current;
1657                             _depth++;
1658                             break;
1659                         }
1660 
1661                         _depth++;
1662                     }
1663                 }
1664 
1665                 return !_readerConsumed;
1666             }
1667 
Reset()1668             public void Reset()
1669             {
1670                 _rowEnumerator.Reset();
1671             }
1672         }
1673 
1674         #endregion
1675 
1676     }
1677 }
1678