1
//------------------------------------------------------------------------------
2 // <copyright file="Shaper.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
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
;
22 /// Shapes store reader values into EntityClient/ObjectQuery results. Also maintains
23 /// state used by materializer delegates.
25 internal abstract class Shaper
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");
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
);
44 #region OnMaterialized storage
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.
50 private IList
<IEntityWrapper
> _materializedEntities
;
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.
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.
69 /// The store data reader we're pulling data from
71 public readonly DbDataReader Reader
;
74 /// The state slots we use in the coordinator expression.
76 public readonly object[] State
;
79 /// The context the shaper is performing for.
81 public readonly ObjectContext Context
;
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.
87 public readonly MetadataWorkspace Workspace
;
90 /// The merge option this shaper is performing under/for.
92 public readonly MergeOption MergeOption
;
95 /// A mapping of CSpace AssociationTypes to OSpace AssociationTypes
96 /// Used for faster lookup/retrieval of AssociationTypes during materialization
98 private readonly Dictionary
<AssociationType
, AssociationType
> AssociationSpaceMap
;
101 /// Caches Tuples of EntitySet, AssociationType, and source member name for which RelatedEnds exist.
103 private HashSet
<Tuple
<string, string, string>> _relatedEndCache
;
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.
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
;
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.
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
;
161 RegisterMaterializedEntityForEvent(result
);
162 if (null == existingEntry
)
164 Context
.ObjectStateManager
.AddEntry(wrappedEntity
, entityKey
, entitySet
, "HandleEntity", false);
168 Context
.ObjectStateManager
.PromoteKeyEntry(existingEntry
, wrappedEntity
, (IExtendedDataRecord
)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
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.
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
);
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
;
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);
226 Context
.ObjectStateManager
.PromoteKeyEntry(existingEntry
, result
, (IExtendedDataRecord
)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
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.
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
;
250 /// Call to ensure a single full-spanned element is added into
251 /// the state manager properly.
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
);
270 EntityKey sourceKey
= wrappedSource
.EntityKey
;
271 CheckClearedEntryOnSpan(null, wrappedSource
, sourceKey
, targetMember
);
273 FullSpanAction(wrappedSource
, spannedEntities
, targetMember
);
274 return wrappedSource
;
278 /// Call to ensure a target entities key is added into the state manager
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
,
333 targetEntry
.WrappedEntity
,
336 /*setIsLoaded*/ true,
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
);
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
350 Debug
.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity");
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
);
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
,
380 /*setIsLoaded*/ true,
381 /*relationshipAlreadyExists*/ false,
382 /* inKeyEntryPromotion */ false);
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
);
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
)
429 else if (end
.Name
== targetEndName
)
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;
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;
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
);
481 if (createRelatedEnd
)
483 relatedEnd
= LightweightCodeGenerator
.GetRelatedEnd(wrappedEntity
.RelationshipManager
, sourceEnd
, targetEnd
, null);
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.
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
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);
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.
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
);
535 /// Calls through to the specified RecordState to set the value for the specified column ordinal.
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.
545 /// Calls through to the specified RecordState to set the value for the EntityRecordInfo.
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.
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.
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.
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
571 public T SetStatePassthrough
<T
>(int ordinal
, T
value)
573 this.State
[ordinal
] = value;
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.
583 public TProperty GetPropertyValueWithErrorHandling
<TProperty
>(int ordinal
, string propertyName
, string typeName
)
585 TProperty result
= new PropertyErrorHandlingValueReader
<TProperty
>(propertyName
, typeName
).GetValue(this.Reader
, ordinal
);
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.
595 public TColumn GetColumnValueWithErrorHandling
<TColumn
>(int ordinal
)
597 TColumn result
= new ColumnErrorHandlingValueReader
<TColumn
>().GetValue(this.Reader
, ordinal
);
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?");
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
);
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
);
639 public TProperty GetSpatialPropertyValueWithErrorHandling
<TProperty
>(int ordinal
, string propertyName
, string typeName
, PrimitiveTypeKind spatialTypeKind
)
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
);
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
);
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
,
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
);
716 /// Wire's one or more full-spanned entities into the state manager; used by
717 /// both full-spanned collections and full-spanned entities.
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);
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);
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
);
795 // Mark properties as modified if they differ from the entity
796 existingEntry
.UpdateRecordWithSetModified(wrappedEntity
.Entity
, existingEntry
.EditableOriginalValues
);
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
838 MethodInfo readerMethod
= Translator
.GetReaderMethod(typeof(T
), out isNullable
);
839 T result
= (T
)readerMethod
.Invoke(reader
, new object[] { ordinal }
);
843 private static object GetUntypedValueDefault(DbDataReader reader
, int ordinal
)
845 return reader
.GetValue(ordinal
);
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
853 internal T
GetValue(DbDataReader reader
, int ordinal
)
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();
874 result
= this.getTypedValue(reader
, ordinal
);
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
);
898 /// Creates the exception thrown when the reader returns a null value
899 /// for a non nullable property/column.
901 protected abstract Exception
CreateNullValueException();
904 /// Creates the exception thrown when the reader returns a value with
905 /// an incompatible type.
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
)
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
));
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
);
1010 /// Typed Shaper. Includes logic to enumerate results and wraps the _rootCoordinator,
1011 /// which includes materializer delegates for the root query collection.
1013 internal sealed class Shaper
<T
> : Shaper
1015 #region private state
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
1022 internal readonly Coordinator
<T
> RootCoordinator
;
1025 /// Which type of query is this, object layer (true) or value layer (false)
1027 private readonly bool IsObjectQuery
;
1030 /// Keeps track of whether we've completed processing or not.
1032 private bool _isActive
;
1035 /// The enumerator we're using to read data; really only populated for value
1038 private IEnumerator
<T
> _rootEnumerator
;
1041 /// Whether the current value of _rootEnumerator has been returned by a bridge
1044 private bool _dataWaiting
;
1047 /// Is the reader owned by the EF or was it supplied by the user?
1049 private 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
)
1058 RootCoordinator
= new Coordinator
<T
>(rootCoordinatorFactory
, /*parent*/ null, /*next*/ null);
1059 if (null != checkPermissions
)
1063 IsObjectQuery
= !(typeof(T
) == typeof(RecordState
));
1065 RootCoordinator
.Initialize(this);
1066 _readerOwned
= readerOwned
;
1071 #region "public" surface area
1074 /// Events raised when the shaper has finished enumerating results. Useful for callback
1075 /// to set parameter values.
1077 internal event EventHandler OnDone
;
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
1085 internal bool DataWaiting
1087 get { return _dataWaiting; }
1088 set { _dataWaiting = value; }
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.
1097 internal IEnumerator
<T
> RootEnumerator
1101 if (_rootEnumerator
== null)
1103 InitializeRecordStates(RootCoordinator
.CoordinatorFactory
);
1104 _rootEnumerator
= GetEnumerator();
1106 return _rootEnumerator
;
1111 /// Initialize the RecordStateFactory objects in their StateSlots.
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"
1130 if (RootCoordinator
.CoordinatorFactory
.IsSimple
)
1132 return new SimpleEnumerator(this);
1136 RowNestedResultEnumerator rowEnumerator
= new Shaper
<T
>.RowNestedResultEnumerator(this);
1138 if (this.IsObjectQuery
)
1140 return new ObjectQueryNestedEnumerator(rowEnumerator
);
1144 return (IEnumerator
<T
>)(object)(new RecordStateEnumerator(rowEnumerator
));
1151 #region enumerator helpers
1154 /// Called when enumeration of results has completed.
1156 private void Finally()
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.
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());
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)
1192 private bool StoreRead()
1197 readSucceeded
= this.Reader
.Read();
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
);
1215 return readSucceeded
;
1219 /// Notify ObjectContext that we are about to start materializing an element
1221 private void StartMaterializingElement()
1223 if (Context
!= null)
1225 Context
.InMaterialization
= true;
1226 InitializeForOnMaterialize();
1231 /// Notify ObjectContext that we are finished materializing the element
1233 private void StopMaterializingElement()
1235 if (Context
!= null)
1237 Context
.InMaterialization
= false;
1238 RaiseMaterializedEvents();
1244 #region simple enumerator
1247 /// Optimized enumerator for queries not including nested results.
1249 private class SimpleEnumerator
: IEnumerator
<T
>
1251 private readonly Shaper
<T
> _shaper
;
1253 internal SimpleEnumerator(Shaper
<T
> shaper
)
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();
1280 public bool MoveNext()
1282 if (!_shaper
._isActive
)
1286 if (_shaper
.StoreRead())
1290 _shaper
.StartMaterializingElement();
1291 _shaper
.RootCoordinator
.ReadNextElement(_shaper
);
1295 _shaper
.StopMaterializingElement();
1305 throw EntityUtil
.NotSupported();
1311 #region nested enumerator
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.
1319 private class RowNestedResultEnumerator
: IEnumerator
<Coordinator
[]>
1321 private readonly Shaper
<T
> _shaper
;
1322 private readonly Coordinator
[] _current
;
1324 internal RowNestedResultEnumerator(Shaper
<T
> 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);
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
);
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
)
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
;
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;
1414 _shaper
.StopMaterializingElement();
1422 throw EntityUtil
.NotSupported();
1425 internal Coordinator
<T
> RootCoordinator
1427 get { return _shaper.RootCoordinator; }
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.
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
1469 if (TryReadToNextElement())
1471 // if there's an element in the reader...
1476 // no data at all...
1477 _state
= State
.NoRows
;
1486 case State
.NoRowsLastElementPending
:
1488 // nothing to do but move to the next state...
1489 _state
= State
.NoRows
;
1495 if (_state
== State
.NoRows
)
1497 _previousElement
= default(T
);
1509 /// Requires: the row is currently positioned at the start of an element.
1511 /// Reads all rows in the element and sets up state for the next element (if any).
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
;
1528 // we're positioned at the end of the reader
1529 _state
= State
.NoRowsLastElementPending
;
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.
1537 private bool TryReadToNextElement()
1539 while (_rowEnumerator
.MoveNext())
1541 // if we hit a new element, return true
1542 if (_rowEnumerator
.Current
[0] != null)
1552 _rowEnumerator
.Reset();
1556 /// Describes the state of this enumerator with respect to the _rowEnumerator
1562 /// No rows have been read yet
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.
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.
1578 NoRowsLastElementPending
,
1581 /// Positioned past the end of the rows. The last element has been returned to
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.
1592 private class RecordStateEnumerator
: IEnumerator
<RecordState
>
1594 private readonly RowNestedResultEnumerator _rowEnumerator
;
1595 private RecordState _current
;
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.
1602 private bool _readerConsumed
;
1604 internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator
)
1606 _rowEnumerator
= rowEnumerator
;
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
)
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())
1645 _readerConsumed
= true;
1652 // check for results at the current depth
1653 Coordinator currentCoordinator
= _rowEnumerator
.Current
[_depth
];
1654 if (null != currentCoordinator
)
1656 _current
= ((Coordinator
<RecordState
>)currentCoordinator
).Current
;
1665 return !_readerConsumed
;
1670 _rowEnumerator
.Reset();