Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Common / Internal / Materialization / Shaper.cs
blobdb09b27ac82253ffdcbfef0e43f2e1ca397e0f9b
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 //------------------------------------------------------------------------------
9 namespace System.Data.Common.Internal.Materialization
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;
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
27 #region constructor
29 internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)
31 Debug.Assert(context == null || workspace == context.MetadataWorkspace, "workspace must match context's workspace");
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);
42 #endregion
44 #region OnMaterialized storage
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;
52 #endregion
54 #region runtime callable/accessible code
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.
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.
68 /// <summary>
69 /// The store data reader we're pulling data from
70 /// </summary>
71 public readonly DbDataReader Reader;
73 /// <summary>
74 /// The state slots we use in the coordinator expression.
75 /// </summary>
76 public readonly object[] State;
78 /// <summary>
79 /// The context the shaper is performing for.
80 /// </summary>
81 public readonly ObjectContext Context;
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;
89 /// <summary>
90 /// The merge option this shaper is performing under/for.
91 /// </summary>
92 public readonly MergeOption MergeOption;
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;
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;
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>
110 public TElement Discriminate<TElement>(object[] discriminatorValues, Func<object[], EntityType> discriminate, KeyValuePair<EntityType, Func<Shaper, TElement>>[] elementDelegates)
112 EntityType entityType = discriminate(discriminatorValues);
113 Func<Shaper, TElement> elementDelegate = null;
114 foreach (KeyValuePair<EntityType, Func<Shaper, TElement>> typeDelegatePair in elementDelegates)
116 if (typeDelegatePair.Key == entityType)
118 elementDelegate = typeDelegatePair.Value;
121 return elementDelegate(this);
124 public IEntityWrapper HandleEntityNoTracking<TEntity>(IEntityWrapper wrappedEntity)
126 Debug.Assert(null != wrappedEntity, "wrapped entity is null");
127 RegisterMaterializedEntityForEvent(wrappedEntity);
128 return wrappedEntity;
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>
137 public IEntityWrapper HandleEntity<TEntity>(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet)
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");
144 IEntityWrapper result = wrappedEntity;
146 // no entity set, so no tracking is required for this entity
147 if (null != (object)entityKey)
149 Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
151 // check for an existing entity with the same key
152 EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey);
153 if (null != existingEntry && !existingEntry.IsKeyEntry)
155 Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
156 UpdateEntry<TEntity>(wrappedEntity, existingEntry);
157 result = existingEntry.WrappedEntity;
159 else
161 RegisterMaterializedEntityForEvent(result);
162 if (null == existingEntry)
164 Context.ObjectStateManager.AddEntry(wrappedEntity, entityKey, entitySet, "HandleEntity", false);
166 else
168 Context.ObjectStateManager.PromoteKeyEntry(existingEntry, wrappedEntity, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
172 return result;
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>
181 public IEntityWrapper HandleEntityAppendOnly<TEntity>(Func<Shaper, IEntityWrapper> constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
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");
186 IEntityWrapper result;
188 if (null == (object)entityKey)
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);
195 else
197 Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
199 // check for an existing entity with the same key
200 EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey);
201 if (null != existingEntry && !existingEntry.IsKeyEntry)
203 Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
204 if (typeof(TEntity) != existingEntry.WrappedEntity.IdentityType)
206 throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, typeof(TEntity), existingEntry.WrappedEntity.IdentityType);
209 if (EntityState.Added == existingEntry.State)
211 throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
213 result = existingEntry.WrappedEntity;
215 else
217 // We don't already have the entity, so construct it
218 result = constructEntityDelegate(this);
219 RegisterMaterializedEntityForEvent(result);
220 if (null == existingEntry)
222 Context.ObjectStateManager.AddEntry(result, entityKey, entitySet, "HandleEntity", false);
224 else
226 Context.ObjectStateManager.PromoteKeyEntry(existingEntry, result, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
230 return result;
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>
239 public IEntityWrapper HandleFullSpanCollection<T_SourceEntity, T_TargetEntity>(IEntityWrapper wrappedEntity, Coordinator<T_TargetEntity> coordinator, AssociationEndMember targetMember)
241 Debug.Assert(null != wrappedEntity, "wrapped entity is null");
242 if (null != wrappedEntity.Entity)
244 coordinator.RegisterCloseHandler((state, spannedEntities) => FullSpanAction(wrappedEntity, spannedEntities, targetMember));
246 return wrappedEntity;
249 /// <summary>
250 /// Call to ensure a single full-spanned element is added into
251 /// the state manager properly.
252 /// </summary>
253 public IEntityWrapper HandleFullSpanElement<T_SourceEntity, T_TargetEntity>(IEntityWrapper wrappedSource, IEntityWrapper wrappedSpannedEntity, AssociationEndMember targetMember)
255 Debug.Assert(null != wrappedSource, "wrapped entity is null");
256 if (wrappedSource.Entity == null)
258 return wrappedSource;
260 List<IEntityWrapper> spannedEntities = null;
261 if (wrappedSpannedEntity.Entity != null)
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);
268 else
270 EntityKey sourceKey = wrappedSource.EntityKey;
271 CheckClearedEntryOnSpan(null, wrappedSource, sourceKey, targetMember);
273 FullSpanAction(wrappedSource, spannedEntities, targetMember);
274 return wrappedSource;
277 /// <summary>
278 /// Call to ensure a target entities key is added into the state manager
279 /// properly
280 /// </summary>
281 public IEntityWrapper HandleRelationshipSpan<T_SourceEntity>(IEntityWrapper wrappedEntity, EntityKey targetKey, AssociationEndMember targetMember)
283 if (null == wrappedEntity.Entity)
285 return wrappedEntity;
287 Debug.Assert(targetMember != null);
288 Debug.Assert(targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One || targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
290 EntityKey sourceKey = wrappedEntity.EntityKey;
291 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
292 CheckClearedEntryOnSpan(targetKey, wrappedEntity, sourceKey, targetMember);
294 if (null != (object)targetKey)
296 EntitySet targetEntitySet;
298 EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
299 targetKey.EntityContainerName, DataSpace.CSpace);
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");
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))
311 // Try to find a state entry for the target key
312 EntityEntry targetEntry = null;
313 if (!manager.TryGetEntityEntry(targetKey, out targetEntry))
315 // no entry exists for the target key
316 // create a key entry for the target
317 targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
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)
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);
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)
343 targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
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;
354 if (needNewRelationship)
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)
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);
369 else
371 Debug.Assert(!targetEntry.IsRelationship, "how IsRelationship?");
372 if (targetEntry.State != EntityState.Deleted)
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);
384 else
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);
396 else
398 RelatedEnd relatedEnd;
399 if(TryGetRelatedEnd(wrappedEntity, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd))
401 SetIsLoadedForSpan(relatedEnd, false);
405 // else there is nothing else for us to do, the relationship has been handled already
406 return wrappedEntity;
409 private bool TryGetRelatedEnd(IEntityWrapper wrappedEntity, AssociationType associationType, string sourceEndName, string targetEndName, out RelatedEnd relatedEnd)
411 Debug.Assert(associationType.DataSpace == DataSpace.CSpace);
413 // Get the OSpace AssociationType
414 AssociationType oSpaceAssociation;
415 if (!AssociationSpaceMap.TryGetValue((AssociationType)associationType, out oSpaceAssociation))
417 oSpaceAssociation = this.Workspace.GetItemCollection(DataSpace.OSpace).GetItem<AssociationType>(associationType.FullName);
418 AssociationSpaceMap[(AssociationType)associationType] = oSpaceAssociation;
421 AssociationEndMember sourceEnd = null;
422 AssociationEndMember targetEnd = null;
423 foreach (var end in oSpaceAssociation.AssociationEndMembers)
425 if (end.Name == sourceEndName)
427 sourceEnd = end;
429 else if (end.Name == targetEndName)
431 targetEnd = end;
435 if (sourceEnd != null && targetEnd != null)
437 bool createRelatedEnd = false;
438 if (wrappedEntity.EntityKey == null)
440 // Free-floating entity--key is null, so don't have EntitySet for validation, so always create RelatedEnd
441 createRelatedEnd = true;
443 else
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);
456 if (_relatedEndCache == null)
458 _relatedEndCache = new HashSet<Tuple<string, string, string>>();
461 if (_relatedEndCache.Contains(relatedEndKey))
463 createRelatedEnd = true;
465 else
467 foreach (var entitySetBase in entitySet.EntityContainer.BaseEntitySets)
469 if ((EdmType)entitySetBase.ElementType == associationType)
471 if (((AssociationSet)entitySetBase).AssociationSetEnds[sourceEndName].EntitySet == entitySet)
473 createRelatedEnd = true;
474 _relatedEndCache.Add(relatedEndKey);
475 break;
481 if (createRelatedEnd)
483 relatedEnd = LightweightCodeGenerator.GetRelatedEnd(wrappedEntity.RelationshipManager, sourceEnd, targetEnd, null);
484 return true;
488 relatedEnd = null;
489 return false;
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>
496 private void SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)
498 Debug.Assert(relatedEnd != null, "RelatedEnd should not be null");
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)
508 // Detect the empty value state of the relatedEnd
509 forceToTrue = relatedEnd.IsEmpty();
510 EntityReference reference = relatedEnd as EntityReference;
511 if (reference != null)
513 forceToTrue &= reference.EntityKey == null;
516 if (forceToTrue || this.MergeOption == MergeOption.OverwriteChanges)
518 relatedEnd.SetIsLoaded(true);
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>
528 public IEntityWrapper HandleIEntityWithKey<TEntity>(IEntityWrapper wrappedEntity, EntitySet entitySet)
530 Debug.Assert(null != wrappedEntity, "wrapped entity is null");
531 return HandleEntity<TEntity>(wrappedEntity, wrappedEntity.EntityKey, entitySet);
534 /// <summary>
535 /// Calls through to the specified RecordState to set the value for the specified column ordinal.
536 /// </summary>
537 public bool SetColumnValue(int recordStateSlotNumber, int ordinal, object value)
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.
544 /// <summary>
545 /// Calls through to the specified RecordState to set the value for the EntityRecordInfo.
546 /// </summary>
547 public bool SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)
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.
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>
559 public bool SetState<T>(int ordinal, T value)
561 this.State[ordinal] = value;
562 return true; // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
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>
571 public T SetStatePassthrough<T>(int ordinal, T value)
573 this.State[ordinal] = value;
574 return value;
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>
583 public TProperty GetPropertyValueWithErrorHandling<TProperty>(int ordinal, string propertyName, string typeName)
585 TProperty result = new PropertyErrorHandlingValueReader<TProperty>(propertyName, typeName).GetValue(this.Reader, ordinal);
586 return result;
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>
595 public TColumn GetColumnValueWithErrorHandling<TColumn>(int ordinal)
597 TColumn result = new ColumnErrorHandlingValueReader<TColumn>().GetValue(this.Reader, ordinal);
598 return result;
601 private DbSpatialDataReader CreateSpatialDataReader()
603 return SpatialHelpers.CreateSpatialDataReader(this.Workspace, this.Reader);
605 private readonly Singleton<DbSpatialDataReader> spatialReader;
607 public DbGeography GetGeographyColumnValue(int ordinal)
609 return this.spatialReader.Value.GetGeography(ordinal);
612 public DbGeometry GetGeometryColumnValue(int ordinal)
614 return this.spatialReader.Value.GetGeometry(ordinal);
617 public TColumn GetSpatialColumnValueWithErrorHandling<TColumn>(int ordinal, PrimitiveTypeKind spatialTypeKind)
619 Debug.Assert(spatialTypeKind == PrimitiveTypeKind.Geography || spatialTypeKind == PrimitiveTypeKind.Geometry, "Spatial primitive type kind is not geography or geometry?");
621 TColumn result;
622 if (spatialTypeKind == PrimitiveTypeKind.Geography)
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);
629 else
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);
636 return result;
639 public TProperty GetSpatialPropertyValueWithErrorHandling<TProperty>(int ordinal, string propertyName, string typeName, PrimitiveTypeKind spatialTypeKind)
641 TProperty result;
642 if (Helper.IsGeographicTypeKind(spatialTypeKind))
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);
649 else
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);
658 return result;
661 #endregion
663 #region helper methods (used by runtime callable code)
665 private void CheckClearedEntryOnSpan(object targetValue, IEntityWrapper wrappedSource, EntityKey sourceKey, AssociationEndMember targetMember)
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))
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))
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);
692 private void CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember)
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);
700 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
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);
708 if (associationSet != null)
710 Debug.Assert(associationSet.AssociationSetEnds[sourceMember.Name].EntitySet == sourceEntitySet);
711 ObjectStateManager.RemoveRelationships(Context, MergeOption, associationSet, sourceKey, sourceMember);
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>
719 private void FullSpanAction<T_TargetEntity>(IEntityWrapper wrappedSource, IList<T_TargetEntity> spannedEntities, AssociationEndMember targetMember)
721 Debug.Assert(null != wrappedSource, "wrapped entity is null");
723 if (wrappedSource.Entity != null)
725 EntityKey sourceKey = wrappedSource.EntityKey;
726 AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
728 RelatedEnd relatedEnd;
729 if (TryGetRelatedEnd(wrappedSource, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd))
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);
734 SetIsLoadedForSpan(relatedEnd, count > 0);
739 #region update existing ObjectStateEntry
741 private void UpdateEntry<TEntity>(IEntityWrapper wrappedEntity, EntityEntry existingEntry)
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");
748 Type clrType = typeof(TEntity);
749 if (clrType != existingEntry.WrappedEntity.IdentityType)
751 throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, clrType, existingEntry.WrappedEntity.IdentityType);
754 if (EntityState.Added == existingEntry.State)
756 throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
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");
764 if (MergeOption.OverwriteChanges == MergeOption)
766 if (EntityState.Deleted == existingEntry.State)
768 existingEntry.RevertDelete();
770 existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity);
771 Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true);
772 existingEntry.AcceptChanges();
773 Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true);
775 else
777 Debug.Assert(MergeOption.PreserveChanges == MergeOption, "not MergeOption.PreserveChanges");
778 if (EntityState.Unchanged == existingEntry.State)
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);
786 else
788 if (Context.ContextOptions.UseLegacyPreserveChangesBehavior)
790 // Do not mark properties as modified if they differ from the entity.
791 existingEntry.UpdateRecordWithoutSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues);
793 else
795 // Mark properties as modified if they differ from the entity
796 existingEntry.UpdateRecordWithSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues);
803 #endregion
805 #endregion
807 #region nested types
808 private abstract class ErrorHandlingValueReader<T>
810 private readonly Func<DbDataReader, int, T> getTypedValue;
811 private readonly Func<DbDataReader, int, object> getUntypedValue;
813 protected ErrorHandlingValueReader(Func<DbDataReader, int, T> typedValueAccessor, Func<DbDataReader, int, object> untypedValueAccessor)
815 this.getTypedValue = typedValueAccessor;
816 this.getUntypedValue = untypedValueAccessor;
819 protected ErrorHandlingValueReader()
820 : this(GetTypedValueDefault, GetUntypedValueDefault)
824 private static T GetTypedValueDefault(DbDataReader reader, int ordinal)
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)
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 });
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;
843 private static object GetUntypedValueDefault(DbDataReader reader, int ordinal)
845 return reader.GetValue(ordinal);
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>
853 internal T GetValue(DbDataReader reader, int ordinal)
855 T result;
856 if (reader.IsDBNull(ordinal))
860 result = (T)(object)null;
862 catch (NullReferenceException)
864 // NullReferenceException is thrown when casting null to a value type.
865 // We don't use isNullable here because of an issue with GetReaderMethod
867 throw CreateNullValueException();
870 else
874 result = this.getTypedValue(reader, ordinal);
876 catch (Exception e)
878 if (EntityUtil.IsCatchableExceptionType(e))
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))
888 throw CreateWrongTypeException(resultType);
891 throw;
894 return result;
897 /// <summary>
898 /// Creates the exception thrown when the reader returns a null value
899 /// for a non nullable property/column.
900 /// </summary>
901 protected abstract Exception CreateNullValueException();
903 /// <summary>
904 /// Creates the exception thrown when the reader returns a value with
905 /// an incompatible type.
906 /// </summary>
907 protected abstract Exception CreateWrongTypeException(Type resultType);
910 private class ColumnErrorHandlingValueReader<TColumn> : ErrorHandlingValueReader<TColumn>
912 internal ColumnErrorHandlingValueReader()
916 internal ColumnErrorHandlingValueReader(Func<DbDataReader, int, TColumn> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)
917 : base(typedAccessor, untypedAccessor)
921 protected override Exception CreateNullValueException()
923 return EntityUtil.ValueNullReferenceCast(typeof(TColumn));
926 protected override Exception CreateWrongTypeException(Type resultType)
928 return EntityUtil.ValueInvalidCast(resultType, typeof(TColumn));
932 private class PropertyErrorHandlingValueReader<TProperty> : ErrorHandlingValueReader<TProperty>
934 private readonly string _propertyName;
935 private readonly string _typeName;
937 internal PropertyErrorHandlingValueReader(string propertyName, string typeName)
938 : base()
940 _propertyName = propertyName;
941 _typeName = typeName;
944 internal PropertyErrorHandlingValueReader(string propertyName, string typeName, Func<DbDataReader, int, TProperty> typedAccessor, Func<DbDataReader, int, object> untypedAccessor)
945 : base(typedAccessor, untypedAccessor)
947 _propertyName = propertyName;
948 _typeName = typeName;
951 protected override Exception CreateNullValueException()
953 return EntityUtil.Constraint(
954 System.Data.Entity.Strings.Materializer_SetInvalidValue(
955 (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
956 _typeName, _propertyName, "null"));
959 protected override Exception CreateWrongTypeException(Type resultType)
961 return EntityUtil.InvalidOperation(
962 System.Data.Entity.Strings.Materializer_SetInvalidValue(
963 (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
964 _typeName, _propertyName, resultType.Name));
967 #endregion
969 #region OnMaterialized helpers
971 public void RaiseMaterializedEvents()
973 if (_materializedEntities != null)
975 foreach (var wrappedEntity in _materializedEntities)
977 Context.OnObjectMaterialized(wrappedEntity.Entity);
979 _materializedEntities.Clear();
983 public void InitializeForOnMaterialize()
985 if (Context.OnMaterializedHasHandlers)
987 if (_materializedEntities == null)
989 _materializedEntities = new List<IEntityWrapper>();
992 else if (_materializedEntities != null)
994 _materializedEntities = null;
998 protected void RegisterMaterializedEntityForEvent(IEntityWrapper wrappedEntity)
1000 if (_materializedEntities != null)
1002 _materializedEntities.Add(wrappedEntity);
1006 #endregion
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
1015 #region private state
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;
1024 /// <summary>
1025 /// Which type of query is this, object layer (true) or value layer (false)
1026 /// </summary>
1027 private readonly bool IsObjectQuery;
1029 /// <summary>
1030 /// Keeps track of whether we've completed processing or not.
1031 /// </summary>
1032 private bool _isActive;
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;
1040 /// <summary>
1041 /// Whether the current value of _rootEnumerator has been returned by a bridge
1042 /// data reader.
1043 /// </summary>
1044 private bool _dataWaiting;
1046 /// <summary>
1047 /// Is the reader owned by the EF or was it supplied by the user?
1048 /// </summary>
1049 private bool _readerOwned;
1051 #endregion
1053 #region constructor
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)
1058 RootCoordinator = new Coordinator<T>(rootCoordinatorFactory, /*parent*/ null, /*next*/ null);
1059 if (null != checkPermissions)
1061 checkPermissions();
1063 IsObjectQuery = !(typeof(T) == typeof(RecordState));
1064 _isActive = true;
1065 RootCoordinator.Initialize(this);
1066 _readerOwned = readerOwned;
1069 #endregion
1071 #region "public" surface area
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;
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
1087 get { return _dataWaiting; }
1088 set { _dataWaiting = value; }
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
1101 if (_rootEnumerator == null)
1103 InitializeRecordStates(RootCoordinator.CoordinatorFactory);
1104 _rootEnumerator = GetEnumerator();
1106 return _rootEnumerator;
1110 /// <summary>
1111 /// Initialize the RecordStateFactory objects in their StateSlots.
1112 /// </summary>
1113 private void InitializeRecordStates(CoordinatorFactory coordinatorFactory)
1115 foreach (RecordStateFactory recordStateFactory in coordinatorFactory.RecordStateFactories)
1117 State[recordStateFactory.StateSlotNumber] = recordStateFactory.Create(coordinatorFactory);
1120 foreach (CoordinatorFactory nestedCoordinatorFactory in coordinatorFactory.NestedCoordinators)
1122 InitializeRecordStates(nestedCoordinatorFactory);
1126 public IEnumerator<T> GetEnumerator()
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)
1132 return new SimpleEnumerator(this);
1134 else
1136 RowNestedResultEnumerator rowEnumerator = new Shaper<T>.RowNestedResultEnumerator(this);
1138 if (this.IsObjectQuery)
1140 return new ObjectQueryNestedEnumerator(rowEnumerator);
1142 else
1144 return (IEnumerator<T>)(object)(new RecordStateEnumerator(rowEnumerator));
1149 #endregion
1151 #region enumerator helpers
1153 /// <summary>
1154 /// Called when enumeration of results has completed.
1155 /// </summary>
1156 private void Finally()
1158 if (_isActive)
1160 _isActive = false;
1162 if (_readerOwned)
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)
1169 this.Reader.Dispose();
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)
1176 this.Context.ReleaseConnection();
1180 if (null != this.OnDone)
1182 this.OnDone(this, new EventArgs());
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>
1192 private bool StoreRead()
1194 bool readSucceeded;
1197 readSucceeded = this.Reader.Read();
1199 catch (Exception e)
1201 // check if the reader is closed; if so, throw friendlier exception
1202 if (this.Reader.IsClosed)
1204 const string operation = "Read";
1205 throw EntityUtil.DataReaderClosed(operation);
1208 // wrap exception if necessary
1209 if (EntityUtil.IsCatchableEntityExceptionType(e))
1211 throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_StoreReaderFailed, e);
1213 throw;
1215 return readSucceeded;
1218 /// <summary>
1219 /// Notify ObjectContext that we are about to start materializing an element
1220 /// </summary>
1221 private void StartMaterializingElement()
1223 if (Context != null)
1225 Context.InMaterialization = true;
1226 InitializeForOnMaterialize();
1230 /// <summary>
1231 /// Notify ObjectContext that we are finished materializing the element
1232 /// </summary>
1233 private void StopMaterializingElement()
1235 if (Context != null)
1237 Context.InMaterialization = false;
1238 RaiseMaterializedEvents();
1242 #endregion
1244 #region simple enumerator
1246 /// <summary>
1247 /// Optimized enumerator for queries not including nested results.
1248 /// </summary>
1249 private class SimpleEnumerator : IEnumerator<T>
1251 private readonly Shaper<T> _shaper;
1253 internal SimpleEnumerator(Shaper<T> shaper)
1255 _shaper = shaper;
1258 public T Current
1260 get { return _shaper.RootCoordinator.Current; }
1263 object System.Collections.IEnumerator.Current
1265 get { return _shaper.RootCoordinator.Current; }
1268 public void Dispose()
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();
1280 public bool MoveNext()
1282 if (!_shaper._isActive)
1284 return false;
1286 if (_shaper.StoreRead())
1290 _shaper.StartMaterializingElement();
1291 _shaper.RootCoordinator.ReadNextElement(_shaper);
1293 finally
1295 _shaper.StopMaterializingElement();
1297 return true;
1299 this.Dispose();
1300 return false;
1303 public void Reset()
1305 throw EntityUtil.NotSupported();
1309 #endregion
1311 #region nested enumerator
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[]>
1321 private readonly Shaper<T> _shaper;
1322 private readonly Coordinator[] _current;
1324 internal RowNestedResultEnumerator(Shaper<T> shaper)
1326 _shaper = shaper;
1327 _current = new Coordinator[_shaper.RootCoordinator.MaxDistanceToLeaf() + 1];
1330 public Coordinator[] Current
1332 get { return _current; }
1335 public void Dispose()
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();
1344 object System.Collections.IEnumerator.Current
1346 get { return _current; }
1349 public bool MoveNext()
1351 Coordinator currentCoordinator = _shaper.RootCoordinator;
1355 _shaper.StartMaterializingElement();
1357 if (!_shaper.StoreRead())
1359 // Reset all collections
1360 this.RootCoordinator.ResetCollection(_shaper);
1361 return false;
1364 int depth = 0;
1365 bool haveInitializedChildren = false;
1366 for (; depth < _current.Length; depth++)
1368 // find a coordinator at this depth that currently has data (if any)
1369 while (currentCoordinator != null && !currentCoordinator.CoordinatorFactory.HasData(_shaper))
1371 currentCoordinator = currentCoordinator.Next;
1373 if (null == currentCoordinator)
1375 break;
1378 // check if this row contains a new element for this coordinator
1379 if (currentCoordinator.HasNextElement(_shaper))
1381 // if we have children and haven't initialized them yet, do so now
1382 if (!haveInitializedChildren && null != currentCoordinator.Child)
1384 currentCoordinator.Child.ResetCollection(_shaper);
1386 haveInitializedChildren = true;
1388 // read the next element
1389 currentCoordinator.ReadNextElement(_shaper);
1391 // place the coordinator in the result array to indicate there is a new
1392 // element at this depth
1393 _current[depth] = currentCoordinator;
1395 else
1397 // clear out the coordinator in result array to indicate there is no new
1398 // element at this depth
1399 _current[depth] = null;
1402 // move to child (in the next iteration we deal with depth + 1
1403 currentCoordinator = currentCoordinator.Child;
1406 // clear out all positions below the depth we reached before we ran out of data
1407 for (; depth < _current.Length; depth++)
1409 _current[depth] = null;
1412 finally
1414 _shaper.StopMaterializingElement();
1417 return true;
1420 public void Reset()
1422 throw EntityUtil.NotSupported();
1425 internal Coordinator<T> RootCoordinator
1427 get { return _shaper.RootCoordinator; }
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>
1437 private readonly RowNestedResultEnumerator _rowEnumerator;
1438 private T _previousElement;
1439 private State _state;
1441 internal ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)
1443 _rowEnumerator = rowEnumerator;
1444 _previousElement = default(T);
1445 _state = State.Start;
1448 public T Current { get { return _previousElement; } }
1450 public void Dispose()
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();
1459 object System.Collections.IEnumerator.Current { get { return this.Current; } }
1461 public bool MoveNext()
1463 // See the documentation for enum State to understand the behaviors and requirements
1464 // for each state.
1465 switch (_state)
1467 case State.Start:
1469 if (TryReadToNextElement())
1471 // if there's an element in the reader...
1472 ReadElement();
1474 else
1476 // no data at all...
1477 _state = State.NoRows;
1480 break;
1481 case State.Reading:
1483 ReadElement();
1485 break;
1486 case State.NoRowsLastElementPending:
1488 // nothing to do but move to the next state...
1489 _state = State.NoRows;
1491 break;
1494 bool result;
1495 if (_state == State.NoRows)
1497 _previousElement = default(T);
1498 result = false;
1500 else
1502 result = true;
1505 return result;
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>
1513 private void ReadElement()
1515 // remember the element we're currently reading
1516 _previousElement = _rowEnumerator.RootCoordinator.Current;
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())
1522 // we're positioned at the start of the next element (which
1523 // corresponds to the 'reading' state)
1524 _state = State.Reading;
1526 else
1528 // we're positioned at the end of the reader
1529 _state = State.NoRowsLastElementPending;
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>
1537 private bool TryReadToNextElement()
1539 while (_rowEnumerator.MoveNext())
1541 // if we hit a new element, return true
1542 if (_rowEnumerator.Current[0] != null)
1544 return true;
1547 return false;
1550 public void Reset()
1552 _rowEnumerator.Reset();
1555 /// <summary>
1556 /// Describes the state of this enumerator with respect to the _rowEnumerator
1557 /// it wraps.
1558 /// </summary>
1559 private enum State
1561 /// <summary>
1562 /// No rows have been read yet
1563 /// </summary>
1564 Start,
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,
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,
1580 /// <summary>
1581 /// Positioned past the end of the rows. The last element has been returned to
1582 /// the user.
1583 /// </summary>
1584 NoRows,
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>
1594 private readonly RowNestedResultEnumerator _rowEnumerator;
1595 private RecordState _current;
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;
1604 internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator)
1606 _rowEnumerator = rowEnumerator;
1607 _current = null;
1608 _depth = -1;
1609 _readerConsumed = false;
1612 public RecordState Current
1614 get { return _current; }
1617 public void Dispose()
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();
1626 object System.Collections.IEnumerator.Current
1628 get { return _current; }
1631 public bool MoveNext()
1633 if (!_readerConsumed)
1635 while (true)
1637 // keep on cycling until we find a result
1638 if (-1 == _depth || _rowEnumerator.Current.Length == _depth)
1640 // time to move to the next row...
1641 if (!_rowEnumerator.MoveNext())
1643 // no more rows...
1644 _current = null;
1645 _readerConsumed = true;
1646 break;
1649 _depth = 0;
1652 // check for results at the current depth
1653 Coordinator currentCoordinator = _rowEnumerator.Current[_depth];
1654 if (null != currentCoordinator)
1656 _current = ((Coordinator<RecordState>)currentCoordinator).Current;
1657 _depth++;
1658 break;
1661 _depth++;
1665 return !_readerConsumed;
1668 public void Reset()
1670 _rowEnumerator.Reset();
1674 #endregion