shaper.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataEntity / System / Data / Common / internal / materialization / shaper.cs / 1305376 / shaper.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// [....] 
// [....]
//----------------------------------------------------------------------------- 
 
using System.Collections.Generic;
using System.Data.Common.Utils; 
using System.Linq;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses; 
using System.Data.Objects.Internal;
using System.Diagnostics; 
using System.Reflection; 

namespace System.Data.Common.Internal.Materialization 
{
    /// 
    /// Shapes store reader values into EntityClient/ObjectQuery results. Also maintains
    /// state used by materializer delegates. 
    /// 
    internal abstract class Shaper 
    { 
        #region constructor
 
        internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount)
        {
            Debug.Assert(context == null || workspace == context.MetadataWorkspace, "workspace must match context's workspace");
 
            this.Reader = reader;
            this.MergeOption = mergeOption; 
            this.State = new object[stateCount]; 
            this.Context = context;
            this.Workspace = workspace; 
            this.AssociationSpaceMap = new Dictionary();
        }

        #endregion 

        #region OnMaterialized storage 
 
        /// 
        /// Keeps track of the entities that have been materialized so that we can fire an OnMaterialized 
        /// for them before returning control to the caller.
        /// 
        private IList _materializedEntities;
 
        #endregion
 
        #region runtime callable/accessible code 

        // Code in this section is called from the delegates produced by the Translator.  It 
        // may not show up if you search using Find All References...use Find in Files instead.
        //
        // Many items on this class are public, simply to make the job of producing the
        // expressions that use them simpler.  If you have a hankering to make them private, 
        // you will need to modify the code in the Translator that does the GetMethod/GetField
        // to use BindingFlags.NonPublic | BindingFlags.Instance as well. 
        // 
        // Debug.Asserts that fire from the code in this region will probably create a
        // SecurityException in the Coordinator's Read method since those are restricted when 
        // running the Shaper.

        /// 
        /// The store data reader we're pulling data from 
        /// 
        public readonly DbDataReader Reader; 
 
        /// 
        /// The state slots we use in the coordinator expression. 
        /// 
        public readonly object[] State;

        ///  
        /// The context the shaper is performing for.
        ///  
        public readonly ObjectContext Context; 

        ///  
        /// The workspace we are performing for; yes we could get it from the context, but
        /// it's much easier to just have it handy.
        /// 
        public readonly MetadataWorkspace Workspace; 

        ///  
        /// The merge option this shaper is performing under/for. 
        /// 
        public readonly MergeOption MergeOption; 

        /// 
        /// A mapping of CSpace AssociationTypes to OSpace AssociationTypes
        /// Used for faster lookup/retrieval of AssociationTypes during materialization 
        /// 
        private readonly Dictionary AssociationSpaceMap; 
 
        /// 
        /// Caches Tuples of EntitySet, AssociationType, and source member name for which RelatedEnds exist. 
        /// 
        private HashSet> _relatedEndCache;

        ///  
        /// Utility method used to evaluate a multi-discriminator column map. Takes
        /// discriminator values and determines the appropriate entity type, then looks up 
        /// the appropriate handler and invokes it. 
        /// 
        public TElement Discriminate(object[] discriminatorValues, Func discriminate, KeyValuePair>[] elementDelegates) 
        {
            EntityType entityType = discriminate(discriminatorValues);
            Func elementDelegate = null;
            foreach (KeyValuePair> typeDelegatePair in elementDelegates) 
            {
                if (typeDelegatePair.Key == entityType) 
                { 
                    elementDelegate = typeDelegatePair.Value;
                } 
            }
            return elementDelegate(this);
        }
 
        public IEntityWrapper HandleEntityNoTracking(IEntityWrapper wrappedEntity)
        { 
            Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 
            RegisterMaterializedEntityForEvent(wrappedEntity);
            return wrappedEntity; 
        }

        /// 
        /// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges 
        /// Handles state management for an entity returned by a query. Where an existing entry
        /// exists, updates that entry and returns the existing entity. Otherwise, the entity 
        /// passed in is returned. 
        /// 
        public IEntityWrapper HandleEntity(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet) 
        {
            Debug.Assert(MergeOption.NoTracking != this.MergeOption, "no need to HandleEntity if there's no tracking");
            Debug.Assert(MergeOption.AppendOnly != this.MergeOption, "use HandleEntityAppendOnly instead...");
            Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 
            Debug.Assert(null != wrappedEntity.Entity, "if HandleEntity is called, there must be an entity");
 
            IEntityWrapper result = wrappedEntity; 

            // no entity set, so no tracking is required for this entity 
            if (null != (object)entityKey)
            {
                Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set");
 
                // check for an existing entity with the same key
                EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey); 
                if (null != existingEntry && !existingEntry.IsKeyEntry) 
                {
                    Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey"); 
                    UpdateEntry(wrappedEntity, existingEntry);
                    result = existingEntry.WrappedEntity;
                }
                else 
                {
                    RegisterMaterializedEntityForEvent(result); 
                    if (null == existingEntry) 
                    {
                        Context.ObjectStateManager.AddEntry(wrappedEntity, entityKey, entitySet, "HandleEntity", false); 
                    }
                    else
                    {
                        Context.ObjectStateManager.PromoteKeyEntry(existingEntry, wrappedEntity, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity"); 
                    }
                } 
            } 
            return result;
        } 

        /// 
        /// REQUIRES:: entity exists; MergeOption is AppendOnly
        /// Handles state management for an entity with the given key. When the entity already exists 
        /// in the state manager, it is returned directly. Otherwise, the entityDelegate is invoked and
        /// the resulting entity is returned. 
        ///  
        public IEntityWrapper HandleEntityAppendOnly(Func constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
        { 
            Debug.Assert(this.MergeOption == MergeOption.AppendOnly, "only use HandleEntityAppendOnly when MergeOption is AppendOnly");
            Debug.Assert(null != constructEntityDelegate, "must provide delegate to construct the entity");

            IEntityWrapper result; 

            if (null == (object)entityKey) 
            { 
                // no entity set, so no tracking is required for this entity, just
                // call the delegate to "materialize" it. 
                result = constructEntityDelegate(this);
                RegisterMaterializedEntityForEvent(result);
            }
            else 
            {
                Debug.Assert(null != entitySet, "if there is an entity key, there must also be an entity set"); 
 
                // check for an existing entity with the same key
                EntityEntry existingEntry = this.Context.ObjectStateManager.FindEntityEntry(entityKey); 
                if (null != existingEntry && !existingEntry.IsKeyEntry)
                {
                    Debug.Assert(existingEntry.EntityKey.Equals(entityKey), "Found ObjectStateEntry with wrong EntityKey");
                    if (typeof(TEntity) != existingEntry.WrappedEntity.IdentityType) 
                    {
                        throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, typeof(TEntity), existingEntry.WrappedEntity.IdentityType); 
                    } 

                    if (EntityState.Added == existingEntry.State) 
                    {
                        throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
                    }
                    result = existingEntry.WrappedEntity; 
                }
                else 
                { 
                    // We don't already have the entity, so construct it
                    result = constructEntityDelegate(this); 
                    RegisterMaterializedEntityForEvent(result);
                    if (null == existingEntry)
                    {
                        Context.ObjectStateManager.AddEntry(result, entityKey, entitySet, "HandleEntity", false); 
                    }
                    else 
                    { 
                        Context.ObjectStateManager.PromoteKeyEntry(existingEntry, result, (IExtendedDataRecord)null, false, /*setIsLoaded*/ true, /*keyEntryInitialized*/ false, "HandleEntity");
                    } 
                }
            }
            return result;
        } 

        ///  
        /// Call to ensure a collection of full-spanned elements are added 
        /// into the state manager properly.  We registers an action to be called
        /// when the collection is closed that pulls the collection of full spanned 
        /// objects into the state manager.
        /// 
        public IEntityWrapper HandleFullSpanCollection(IEntityWrapper wrappedEntity, Coordinator coordinator, AssociationEndMember targetMember)
        { 
            Debug.Assert(null != wrappedEntity, "wrapped entity is null");
            if (null != wrappedEntity.Entity) 
            { 
                coordinator.RegisterCloseHandler((state, spannedEntities) => FullSpanAction(wrappedEntity, spannedEntities, targetMember));
            } 
            return wrappedEntity;
        }

        ///  
        /// Call to ensure a single full-spanned element is added into
        /// the state manager properly. 
        ///  
        public IEntityWrapper HandleFullSpanElement(IEntityWrapper wrappedSource, IEntityWrapper wrappedSpannedEntity, AssociationEndMember targetMember)
        { 
            Debug.Assert(null != wrappedSource, "wrapped entity is null");
            if (wrappedSource.Entity == null)
            {
                return wrappedSource; 
            }
            List spannedEntities = null; 
            if (wrappedSpannedEntity.Entity != null) 
            {
                // There was a single entity in the column 
                // Create a list so we can perform the same logic as a collection of entities
                spannedEntities = new List(1);
                spannedEntities.Add(wrappedSpannedEntity);
            } 
            else
            { 
                EntityKey sourceKey = wrappedSource.EntityKey; 
                CheckClearedEntryOnSpan(null, wrappedSource, sourceKey, targetMember);
            } 
            FullSpanAction(wrappedSource, spannedEntities, targetMember);
            return wrappedSource;
        }
 
        /// 
        /// Call to ensure a target entities key is added into the state manager 
        /// properly 
        /// 
        public IEntityWrapper HandleRelationshipSpan(IEntityWrapper wrappedEntity, EntityKey targetKey, AssociationEndMember targetMember) 
        {
            if (null == wrappedEntity.Entity)
            {
                return wrappedEntity; 
            }
            Debug.Assert(targetMember != null); 
            Debug.Assert(targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One || targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne); 

            EntityKey sourceKey = wrappedEntity.EntityKey; 
            AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
            CheckClearedEntryOnSpan(targetKey, wrappedEntity, sourceKey, targetMember);

            if (null != (object)targetKey) 
            {
                EntitySet targetEntitySet; 
 
                EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
                    targetKey.EntityContainerName, DataSpace.CSpace); 

                // find the correct AssociationSet
                AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer,
                    targetKey.EntitySetName, (AssociationType)(targetMember.DeclaringType), targetMember.Name, out targetEntitySet); 
                Debug.Assert(associationSet != null, "associationSet should not be null");
 
                ObjectStateManager manager = Context.ObjectStateManager; 
                EntityState newEntryState;
                // If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one 
                if (!ObjectStateManager.TryUpdateExistingRelationships(this.Context, this.MergeOption, associationSet, sourceMember, sourceKey, wrappedEntity, targetMember, targetKey, /*setIsLoaded*/ true, out newEntryState))
                {
                    // Try to find a state entry for the target key
                    EntityEntry targetEntry = null; 
                    if (!manager.TryGetEntityEntry(targetKey, out targetEntry))
                    { 
                        // no entry exists for the target key 
                        // create a key entry for the target
                        targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet); 
                    }

                    // SQLBU 557105. For 1-1 relationships we have to take care of the relationships of targetEntity
                    bool needNewRelationship = true; 
                    switch (sourceMember.RelationshipMultiplicity)
                    { 
                        case RelationshipMultiplicity.ZeroOrOne: 
                        case RelationshipMultiplicity.One:
                            // devnote: targetEntry can be a key entry (targetEntry.Entity == null), 
                            // but it that case this parameter won't be used in TryUpdateExistingRelationships
                            needNewRelationship = !ObjectStateManager.TryUpdateExistingRelationships(this.Context,
                                this.MergeOption,
                                associationSet, 
                                targetMember,
                                targetKey, 
                                targetEntry.WrappedEntity, 
                                sourceMember,
                                sourceKey, 
                                /*setIsLoaded*/ true,
                                out newEntryState);

                            // It is possible that as part of removing existing relationships, the key entry was deleted 
                            // If that is the case, recreate the key entry
                            if (targetEntry.State == EntityState.Detached) 
                            { 
                                targetEntry = manager.AddKeyEntry(targetKey, targetEntitySet);
                            } 
                            break;
                        case RelationshipMultiplicity.Many:
                            // we always need a new relationship with Many-To-Many, if there was no exact match between these two entities, so do nothing
                            break; 
                        default:
                            Debug.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity"); 
                            break; 
                    }
 
                    if (needNewRelationship)
                    {

                        // If the target entry is a key entry, then we need to add a relation 
                        //   between the source and target entries
                        // If we are in a state where we just need to add a new Deleted relation, we 
                        //   only need to do that and not touch the related ends 
                        // If the target entry is a full entity entry, then we need to add
                        //   the target entity to the source collection or reference 
                        if (targetEntry.IsKeyEntry || newEntryState == EntityState.Deleted)
                        {
                            // Add a relationship between the source entity and the target key entry
                            RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey); 
                            manager.AddNewRelation(wrapper, newEntryState);
                        } 
                        else 
                        {
                            Debug.Assert(!targetEntry.IsRelationship, "how IsRelationship?"); 
                            if (targetEntry.State != EntityState.Deleted)
                            {
                                // The entry contains an entity, do collection or reference fixup
                                // This will also try to create a new relationship entry or will revert the delete on an existing deleted relationship 
                                ObjectStateManager.AddEntityToCollectionOrReference(
                                    this.MergeOption, wrappedEntity, sourceMember, 
                                    targetEntry.WrappedEntity, 
                                    targetMember,
                                    /*setIsLoaded*/ true, 
                                    /*relationshipAlreadyExists*/ false,
                                    /* inKeyEntryPromotion */ false);
                            }
                            else 
                            {
                                // if the target entry is deleted, then the materializer needs to create a deleted relationship 
                                // between the entity and the target entry so that if the entity is deleted, the update 
                                // pipeline can find the relationship (even though it is deleted)
                                RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey); 
                                manager.AddNewRelation(wrapper, EntityState.Deleted);
                            }
                        }
                    } 
                }
            } 
            else 
            {
                RelatedEnd relatedEnd; 
                if(TryGetRelatedEnd(wrappedEntity, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd))
                {
                    SetIsLoadedForSpan(relatedEnd, false);
                } 
            }
 
            // else there is nothing else for us to do, the relationship has been handled already 
            return wrappedEntity;
        } 

        private bool TryGetRelatedEnd(IEntityWrapper wrappedEntity, AssociationType associationType, string sourceEndName, string targetEndName, out RelatedEnd relatedEnd)
        {
            Debug.Assert(associationType.DataSpace == DataSpace.CSpace); 

            // Get the OSpace AssociationType 
            AssociationType oSpaceAssociation; 
            if (!AssociationSpaceMap.TryGetValue((AssociationType)associationType, out oSpaceAssociation))
            { 
                oSpaceAssociation = this.Workspace.GetItemCollection(DataSpace.OSpace).GetItem(associationType.FullName);
                AssociationSpaceMap[(AssociationType)associationType] = oSpaceAssociation;
            }
 
            AssociationEndMember sourceEnd = null;
            AssociationEndMember targetEnd = null; 
            foreach (var end in oSpaceAssociation.AssociationEndMembers) 
            {
                if (end.Name == sourceEndName) 
                {
                    sourceEnd = end;
                }
                else if (end.Name == targetEndName) 
                {
                    targetEnd = end; 
                } 
            }
 
            if (sourceEnd != null && targetEnd != null)
            {
                bool createRelatedEnd = false;
                if (wrappedEntity.EntityKey == null) 
                {
                    // Free-floating entity--key is null, so don't have EntitySet for validation, so always create RelatedEnd 
                    createRelatedEnd = true; 
                }
                else 
                {
                    // It is possible, because of MEST, that we're trying to load a relationship that is valid for this EntityType
                    // in metadata, but is not valid in this case because the specific entity is part of an EntitySet that is not
                    // mapped in any AssociationSet for this association type. 
                    // The metadata structure makes checking for this somewhat time consuming because of the loop required.
                    // Because the whole reason for this method is perf, we try to reduce the 
                    // impact of this check by caching positive hits in a HashSet so we don't have to do this for 
                    // every entity in a query.  (We could also cache misses, but since these only happen in MEST, which
                    // is not common, we decided not to slow down the normal non-MEST case anymore by doing this.) 
                    var entitySet = wrappedEntity.EntityKey.GetEntitySet(this.Workspace);
                    var relatedEndKey = Tuple.Create(entitySet.Identity, associationType.Identity, sourceEndName);

                    if (_relatedEndCache == null) 
                    {
                        _relatedEndCache = new HashSet>(); 
                    } 

                    if (_relatedEndCache.Contains(relatedEndKey)) 
                    {
                        createRelatedEnd = true;
                    }
                    else 
                    {
                        foreach (var entitySetBase in entitySet.EntityContainer.BaseEntitySets) 
                        { 
                            if ((EdmType)entitySetBase.ElementType == associationType)
                            { 
                                if (((AssociationSet)entitySetBase).AssociationSetEnds[sourceEndName].EntitySet == entitySet)
                                {
                                    createRelatedEnd = true;
                                    _relatedEndCache.Add(relatedEndKey); 
                                    break;
                                } 
                            } 
                        }
                    } 
                }
                if (createRelatedEnd)
                {
                    relatedEnd = LightweightCodeGenerator.GetRelatedEnd(wrappedEntity.RelationshipManager, sourceEnd, targetEnd, null); 
                    return true;
                } 
            } 

            relatedEnd = null; 
            return false;
        }

        ///  
        /// Sets the IsLoaded flag to "true"
        /// There are also rules for when this can be set based on MergeOption and the current value(s) in the related end. 
        ///  
        private void SetIsLoadedForSpan(RelatedEnd relatedEnd, bool forceToTrue)
        { 
            Debug.Assert(relatedEnd != null, "RelatedEnd should not be null");

            // We can now say this related end is "Loaded"
            // The cases where we should set this to true are: 
            // AppendOnly: the related end is empty and does not point to a stub
            // PreserveChanges: the related end is empty and does not point to a stub (otherwise, an Added item exists and IsLoaded should not change) 
            // OverwriteChanges: always 
            // NoTracking: always
            if (!forceToTrue) 
            {
                // Detect the empty value state of the relatedEnd
                forceToTrue = relatedEnd.IsEmpty();
                EntityReference reference = relatedEnd as EntityReference; 
                if (reference != null)
                { 
                    forceToTrue &= reference.EntityKey == null; 
                }
            } 
            if (forceToTrue || this.MergeOption == MergeOption.OverwriteChanges)
            {
                relatedEnd.SetIsLoaded(true);
            } 
        }
 
        ///  
        /// REQUIRES:: entity is not null and MergeOption is OverwriteChanges or PreserveChanges
        /// Calls through to HandleEntity after retrieving the EntityKey from the given entity. 
        /// Still need this so that the correct key will be used for iPOCOs that implement IEntityWithKey
        /// in a non-default manner.
        /// 
        public IEntityWrapper HandleIEntityWithKey(IEntityWrapper wrappedEntity, EntitySet entitySet) 
        {
            Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 
            return HandleEntity(wrappedEntity, wrappedEntity.EntityKey, entitySet); 
        }
 
        /// 
        /// Calls through to the specified RecordState to set the value for the specified column ordinal.
        /// 
        public bool SetColumnValue(int recordStateSlotNumber, int ordinal, object value) 
        {
            RecordState recordState = (RecordState)this.State[recordStateSlotNumber]; 
            recordState.SetColumnValue(ordinal, value); 
            return true;  // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
        } 

        /// 
        /// Calls through to the specified RecordState to set the value for the EntityRecordInfo.
        ///  
        public bool SetEntityRecordInfo(int recordStateSlotNumber, EntityKey entityKey, EntitySet entitySet)
        { 
            RecordState recordState = (RecordState)this.State[recordStateSlotNumber]; 
            recordState.SetEntityRecordInfo(entityKey, entitySet);
            return true;  // TRICKY: return true so we can use BitwiseOr expressions to string these guys together. 
        }

        /// 
        /// REQUIRES:: should be called only by delegate allocating this state. 
        /// Utility method assigning a value to a state slot. Returns an arbitrary value
        /// allowing the method call to be composed in a ShapeEmitter Expression delegate. 
        ///  
        public bool SetState(int ordinal, T value)
        { 
            this.State[ordinal] = value;
            return true;  // TRICKY: return true so we can use BitwiseOr expressions to string these guys together.
        }
 
        /// 
        /// REQUIRES:: should be called only by delegate allocating this state. 
        /// Utility method assigning a value to a state slot and return the value, allowing 
        /// the value to be accessed/set in a ShapeEmitter Expression delegate and later
        /// retrieved. 
        /// 
        public T SetStatePassthrough(int ordinal, T value)
        {
            this.State[ordinal] = value; 
            return value;
        } 
 
        /// 
        /// Used to retrieve a property value with exception handling. Normally compiled 
        /// delegates directly call typed methods on the DbDataReader (e.g. GetInt32)
        /// but when an exception occurs we retry using this method to potentially get
        /// a more useful error message to the user.
        ///  
        public TProperty GetPropertyValueWithErrorHandling(int ordinal, string propertyName, string typeName)
        { 
            TProperty result = new PropertyErrorHandlingValueReader(propertyName, typeName).GetValue(this.Reader, ordinal); 
            return result;
        } 

        /// 
        /// Used to retrieve a column value with exception handling. Normally compiled
        /// delegates directly call typed methods on the DbDataReader (e.g. GetInt32) 
        /// but when an exception occurs we retry using this method to potentially get
        /// a more useful error message to the user. 
        ///  
        public TColumn GetColumnValueWithErrorHandling(int ordinal)
        { 
            TColumn result = new ColumnErrorHandlingValueReader().GetValue(this.Reader, ordinal);
            return result;
        }
 
        #endregion
 
        #region helper methods (used by runtime callable code) 

        private void CheckClearedEntryOnSpan(object targetValue, IEntityWrapper wrappedSource, EntityKey sourceKey, AssociationEndMember targetMember) 
        {
            // If a relationship does not exist on the server but does exist on the client,
            // we may need to remove it, depending on the current state and the MergeOption
            if ((null != (object)sourceKey) && (null == targetValue) && 
                (this.MergeOption == MergeOption.PreserveChanges ||
                 this.MergeOption == MergeOption.OverwriteChanges)) 
            { 
                // When the spanned value is null, it may be because the spanned association applies to a
                // subtype of the entity's type, and the entity is not actually an instance of that type. 
                AssociationEndMember sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetMember);
                EdmType expectedSourceType = ((RefType)sourceEnd.TypeUsage.EdmType).ElementType;
                TypeUsage entityTypeUsage;
                if (!this.Context.Perspective.TryGetType(wrappedSource.IdentityType, out entityTypeUsage) || 
                    entityTypeUsage.EdmType.EdmEquals(expectedSourceType) ||
                    TypeSemantics.IsSubTypeOf(entityTypeUsage.EdmType, expectedSourceType)) 
                { 
                    // Otherwise, the source entity is the correct type (exactly or a subtype) for the source
                    // end of the spanned association, so validate that the relationhip that was spanned is 
                    // part of the Container owning the EntitySet of the root entity.
                    // This can be done by comparing the EntitySet  of the row's entity to the relationships
                    // in the Container and their AssociationSetEnd's type
                    CheckClearedEntryOnSpan(sourceKey, wrappedSource, targetMember); 
                }
            } 
        } 

        private void CheckClearedEntryOnSpan(EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember) 
        {
            Debug.Assert(null != (object)sourceKey);
            Debug.Assert(wrappedSource != null);
            Debug.Assert(wrappedSource.Entity != null); 
            Debug.Assert(targetMember != null);
            Debug.Assert(this.Context != null); 
 
            AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember);
 
            EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(sourceKey.EntityContainerName,
                DataSpace.CSpace);
            EntitySet sourceEntitySet;
            AssociationSet associationSet = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, sourceKey.EntitySetName, 
                (AssociationType)sourceMember.DeclaringType, sourceMember.Name, out sourceEntitySet);
 
            if (associationSet != null) 
            {
                Debug.Assert(associationSet.AssociationSetEnds[sourceMember.Name].EntitySet == sourceEntitySet); 
                ObjectStateManager.RemoveRelationships(Context, MergeOption, associationSet, sourceKey, sourceMember);
            }
        }
 
        /// 
        /// Wire's one or more full-spanned entities into the state manager; used by 
        /// both full-spanned collections and full-spanned entities. 
        /// 
        private void FullSpanAction(IEntityWrapper wrappedSource, IList spannedEntities, AssociationEndMember targetMember) 
        {
            Debug.Assert(null != wrappedSource, "wrapped entity is null");

            if (wrappedSource.Entity != null) 
            {
                EntityKey sourceKey = wrappedSource.EntityKey; 
                AssociationEndMember sourceMember = MetadataHelper.GetOtherAssociationEnd(targetMember); 

                RelatedEnd relatedEnd; 
                if (TryGetRelatedEnd(wrappedSource, (AssociationType)targetMember.DeclaringType, sourceMember.Name, targetMember.Name, out relatedEnd))
                {
                    // Add members of the list to the source entity (item in column 0)
                    int count = ObjectStateManager.UpdateRelationships(this.Context, this.MergeOption, (AssociationSet)relatedEnd.RelationshipSet, sourceMember, sourceKey, wrappedSource, targetMember, (List)spannedEntities, true); 

                    SetIsLoadedForSpan(relatedEnd, count > 0); 
                } 
            }
        } 

        #region update existing ObjectStateEntry

        private void UpdateEntry(IEntityWrapper wrappedEntity, EntityEntry existingEntry) 
        {
            Debug.Assert(null != wrappedEntity, "wrapped entity is null"); 
            Debug.Assert(null != wrappedEntity.Entity, "null entity"); 
            Debug.Assert(null != existingEntry, "null ObjectStateEntry");
            Debug.Assert(null != existingEntry.Entity, "ObjectStateEntry without Entity"); 

            Type clrType = typeof(TEntity);
            if (clrType != existingEntry.WrappedEntity.IdentityType)
            { 
                throw EntityUtil.RecyclingEntity(existingEntry.EntityKey, clrType, existingEntry.WrappedEntity.IdentityType);
            } 
 
            if (EntityState.Added == existingEntry.State)
            { 
                throw EntityUtil.AddedEntityAlreadyExists(existingEntry.EntityKey);
            }

            if (MergeOption.AppendOnly != MergeOption) 
            {   // existing entity, update CSpace values in place
                Debug.Assert(EntityState.Added != existingEntry.State, "entry in State=Added"); 
                Debug.Assert(EntityState.Detached != existingEntry.State, "entry in State=Detached"); 

                if (MergeOption.OverwriteChanges == MergeOption) 
                {
                    if (EntityState.Deleted == existingEntry.State)
                    {
                        existingEntry.RevertDelete(); 
                    }
                    existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity); 
                    Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true); 
                    existingEntry.AcceptChanges();
                    Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true); 
                }
                else
                {
                    Debug.Assert(MergeOption.PreserveChanges == MergeOption, "not MergeOption.PreserveChanges"); 
                    if (EntityState.Unchanged == existingEntry.State)
                    { 
                        // same behavior as MergeOption.OverwriteChanges 
                        existingEntry.UpdateCurrentValueRecord(wrappedEntity.Entity);
                        Context.ObjectStateManager.ForgetEntryWithConceptualNull(existingEntry, resetAllKeys: true); 
                        existingEntry.AcceptChanges();
                        Context.ObjectStateManager.FixupReferencesByForeignKeys(existingEntry, replaceAddedRefs: true);
                    }
                    else 
                    {
                        if (Context.ContextOptions.UseLegacyPreserveChangesBehavior) 
                        { 
                            // Do not mark properties as modified if they differ from the entity.
                            existingEntry.UpdateRecordWithoutSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues); 
                        }
                        else
                        {
                            // Mark properties as modified if they differ from the entity 
                            existingEntry.UpdateRecordWithSetModified(wrappedEntity.Entity, existingEntry.EditableOriginalValues);
                        } 
                    } 
                }
            } 
        }

        #endregion
 
        #endregion
 
        #region nested types 
        private abstract class ErrorHandlingValueReader
        { 
            /// 
            /// Gets value from reader using the same pattern as the materializer delegate. Avoids
            /// the need to compile multiple delegates for error handling. If there is a failure
            /// reading a value 
            /// 
            internal T GetValue(DbDataReader reader, int ordinal) 
            { 
                T result;
                bool isNullable; 
                MethodInfo readerMethod = Translator.GetReaderMethod(typeof(T), out isNullable);
                if (reader.IsDBNull(ordinal))
                {
                    try 
                    {
                        result = (T)(object)null; 
                    } 
                    catch (NullReferenceException)
                    { 
                        // NullReferenceException is thrown when casting null to a value type.
                        // We don't use isNullable here because of an issue with GetReaderMethod
                        //
                        throw CreateNullValueException(); 
                    }
                } 
                else 
                {
                    try 
                    {
                        // use the specific reader.GetXXX method
                        result = (T)readerMethod.Invoke(reader, new object[] { ordinal });
                    } 
                    catch (Exception e)
                    { 
                        if (EntityUtil.IsCatchableExceptionType(e)) 
                        {
                            // determine if the problem is with the result type 
                            // (note that if we throw on this call, it's ok
                            // for it to percolate up -- we only intercept type
                            // and null mismatches)
                            object untypedResult = reader.GetValue(ordinal); 
                            Type resultType = null == untypedResult ? null : untypedResult.GetType();
                            if (!typeof(T).IsAssignableFrom(resultType)) 
                            { 
                                throw CreateWrongTypeException(resultType);
                            } 
                        }
                        throw;
                    }
                } 
                return result;
            } 
 
            /// 
            /// Creates the exception thrown when the reader returns a null value 
            /// for a non nullable property/column.
            /// 
            protected abstract Exception CreateNullValueException();
 
            /// 
            /// Creates the exception thrown when the reader returns a value with 
            /// an incompatible type. 
            /// 
            protected abstract Exception CreateWrongTypeException(Type resultType); 
        }

        private class ColumnErrorHandlingValueReader : ErrorHandlingValueReader
        { 
            internal ColumnErrorHandlingValueReader()
            { 
            } 

            protected override Exception CreateNullValueException() 
            {
                return EntityUtil.ValueNullReferenceCast(typeof(TColumn));
            }
 
            protected override Exception  CreateWrongTypeException(Type resultType)
            { 
                return EntityUtil.ValueInvalidCast(resultType, typeof(TColumn)); 
            }
        } 

        private class PropertyErrorHandlingValueReader : ErrorHandlingValueReader
        {
            private readonly string _propertyName; 
            private readonly string _typeName;
 
            internal PropertyErrorHandlingValueReader(string propertyName, string typeName) 
            {
                _propertyName = propertyName; 
                _typeName = typeName;
            }

            protected override Exception CreateNullValueException() 
            {
                return EntityUtil.Constraint( 
                                        System.Data.Entity.Strings.Materializer_SetInvalidValue( 
                                        (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name,
                                        _typeName, _propertyName, "null")); 
            }

            protected override Exception CreateWrongTypeException(Type resultType)
            { 
                return EntityUtil.InvalidOperation(
                                        System.Data.Entity.Strings.Materializer_SetInvalidValue( 
                                        (Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty)).Name, 
                                        _typeName, _propertyName, resultType.Name));
            } 
        }
        #endregion

        #region OnMaterialized helpers 

        public void RaiseMaterializedEvents() 
        { 
            if (_materializedEntities != null)
            { 
                foreach (var wrappedEntity in _materializedEntities)
                {
                    Context.OnObjectMaterialized(wrappedEntity.Entity);
                } 
                _materializedEntities.Clear();
            } 
        } 

        public void InitializeForOnMaterialize() 
        {
            if (Context.OnMaterializedHasHandlers)
            {
                if (_materializedEntities == null) 
                {
                    _materializedEntities = new List(); 
                } 
            }
            else if (_materializedEntities != null) 
            {
                _materializedEntities = null;
            }
        } 

        protected void RegisterMaterializedEntityForEvent(IEntityWrapper wrappedEntity) 
        { 
            if (_materializedEntities != null)
            { 
                _materializedEntities.Add(wrappedEntity);
            }
        }
 
        #endregion
    } 
 
    /// 
    /// Typed Shaper. Includes logic to enumerate results and wraps the _rootCoordinator, 
    /// which includes materializer delegates for the root query collection.
    /// 
    internal sealed class Shaper : Shaper
    { 
        #region private state
 
        ///  
        /// Shapers and Coordinators work together in harmony to materialize the data
        /// from the store; the shaper contains the state, the coordinator contains the 
        /// code.
        /// 
        internal readonly Coordinator RootCoordinator;
 
        /// 
        /// Which type of query is this, object layer (true) or value layer (false) 
        ///  
        private readonly bool IsObjectQuery;
 
        /// 
        /// Keeps track of whether we've completed processing or not.
        /// 
        private bool _isActive; 

        ///  
        /// The enumerator we're using to read data; really only populated for value 
        /// layer queries.
        ///  
        private IEnumerator _rootEnumerator;

        /// 
        /// Whether the current value of _rootEnumerator has been returned by a bridge 
        /// data reader.
        ///  
        private bool _dataWaiting; 

        ///  
        /// Is the reader owned by the EF or was it supplied by the user?
        /// 
        private bool _readerOwned;
 
        #endregion
 
        #region constructor 

        internal Shaper(DbDataReader reader, ObjectContext context, MetadataWorkspace workspace, MergeOption mergeOption, int stateCount, CoordinatorFactory rootCoordinatorFactory, Action checkPermissions, bool readerOwned) 
            : base(reader, context, workspace, mergeOption, stateCount)
        {
            RootCoordinator = new Coordinator(rootCoordinatorFactory, /*parent*/ null, /*next*/ null);
            if (null != checkPermissions) 
            {
                checkPermissions(); 
            } 
            IsObjectQuery = !(typeof(T) == typeof(RecordState));
            _isActive = true; 
            RootCoordinator.Initialize(this);
            _readerOwned = readerOwned;
        }
 
        #endregion
 
        #region "public" surface area 

        ///  
        /// Events raised when the shaper has finished enumerating results. Useful for callback
        /// to set parameter values.
        /// 
        internal event EventHandler OnDone; 

        ///  
        /// Used to handle the read-ahead requirements of value-layer queries.  This 
        /// field indicates the status of the current value of the _rootEnumerator; when
        /// a bridge data reader "accepts responsibility" for the current value, it sets 
        /// this to false.
        /// 
        internal bool DataWaiting
        { 
            get { return _dataWaiting; }
            set { _dataWaiting = value; } 
        } 

        ///  
        /// The enumerator that the value-layer bridge will use to read data; all nested
        /// data readers need to use the same enumerator, so we put it on the Shaper, since
        /// that is something that all the nested data readers (and data records) have access
        /// to -- it prevents us from having to pass two objects around. 
        /// 
        internal IEnumerator RootEnumerator 
        { 
            get
            { 
                if (_rootEnumerator == null)
                {
                    InitializeRecordStates(RootCoordinator.CoordinatorFactory);
                    _rootEnumerator = GetEnumerator(); 
                }
                return _rootEnumerator; 
            } 
        }
 
        /// 
        /// Initialize the RecordStateFactory objects in their StateSlots.
        /// 
        private void InitializeRecordStates(CoordinatorFactory coordinatorFactory) 
        {
            foreach (RecordStateFactory recordStateFactory in coordinatorFactory.RecordStateFactories) 
            { 
                State[recordStateFactory.StateSlotNumber] = recordStateFactory.Create(coordinatorFactory);
            } 

            foreach (CoordinatorFactory nestedCoordinatorFactory in coordinatorFactory.NestedCoordinators)
            {
                InitializeRecordStates(nestedCoordinatorFactory); 
            }
        } 
 
        public IEnumerator GetEnumerator()
        { 
            // we can use a simple enumerator if there are no nested results, no keys and no "has data"
            // discriminator
            if (RootCoordinator.CoordinatorFactory.IsSimple)
            { 
                return new SimpleEnumerator(this);
            } 
            else 
            {
                RowNestedResultEnumerator rowEnumerator = new Shaper.RowNestedResultEnumerator(this); 

                if (this.IsObjectQuery)
                {
                    return new ObjectQueryNestedEnumerator(rowEnumerator); 
                }
                else 
                { 
                    return (IEnumerator)(object)(new RecordStateEnumerator(rowEnumerator));
                } 
            }
        }

        #endregion 

        #region enumerator helpers 
 
        /// 
        /// Called when enumeration of results has completed. 
        /// 
        private void Finally()
        {
            if (_isActive) 
            {
                _isActive = false; 
 
                if (_readerOwned)
                { 
                    // I'd prefer not to special case this, but value-layer behavior is that you
                    // must explicitly close the data reader; if we automatically dispose of the
                    // reader here, we won't have that behavior.
                    if (IsObjectQuery) 
                    {
                        this.Reader.Dispose(); 
                    } 

                    // This case includes when the ObjectResult is disposed before it 
                    // created an ObjectQueryEnumeration; at this time, the connection can be released
                    if (this.Context != null)
                    {
                        this.Context.ReleaseConnection(); 
                    }
                } 
 
                if (null != this.OnDone)
                { 
                    this.OnDone(this, new EventArgs());
                }
            }
        } 

        ///  
        /// Reads the next row from the store. If there is a failure, throws an exception message 
        /// in some scenarios (note that we respond to failure rather than anticipate failure,
        /// avoiding repeated checks in the inner materialization loop) 
        /// 
        private bool StoreRead()
        {
            bool readSucceeded; 
            try
            { 
                readSucceeded = this.Reader.Read(); 
            }
            catch (Exception e) 
            {
                // check if the reader is closed; if so, throw friendlier exception
                if (this.Reader.IsClosed)
                { 
                    const string operation = "Read";
                    throw EntityUtil.DataReaderClosed(operation); 
                } 

                // wrap exception if necessary 
                if (EntityUtil.IsCatchableEntityExceptionType(e))
                {
                    throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_StoreReaderFailed, e);
                } 
                throw;
            } 
            return readSucceeded; 
        }
 
        /// 
        /// Notify ObjectContext that we are about to start materializing an element
        /// 
        private void StartMaterializingElement() 
        {
            if (Context != null) 
            { 
                Context.InMaterialization = true;
                InitializeForOnMaterialize(); 
            }
        }

        ///  
        /// Notify ObjectContext that we are finished materializing the element
        ///  
        private void StopMaterializingElement() 
        {
            if (Context != null) 
            {
                Context.InMaterialization = false;
                RaiseMaterializedEvents();
            } 
        }
 
        #endregion 

        #region simple enumerator 

        /// 
        /// Optimized enumerator for queries not including nested results.
        ///  
        private class SimpleEnumerator : IEnumerator
        { 
            private readonly Shaper _shaper; 

            internal SimpleEnumerator(Shaper shaper) 
            {
                _shaper = shaper;
            }
 
            public T Current
            { 
                get { return _shaper.RootCoordinator.Current; } 
            }
 
            object System.Collections.IEnumerator.Current
            {
                get { return _shaper.RootCoordinator.Current; }
            } 

            public void Dispose() 
            { 
                // Technically, calling GC.SuppressFinalize is not required because the class does not
                // have a finalizer, but it does no harm, protects against the case where a finalizer is added 
                // in the future, and prevents an FxCop warning.
                GC.SuppressFinalize(this);
                // For backwards compatibility, we set the current value to the
                // default value, so you can still call Current. 
                _shaper.RootCoordinator.SetCurrentToDefault();
                _shaper.Finally(); 
            } 

            public bool MoveNext() 
            {
                if (!_shaper._isActive)
                {
                    return false; 
                }
                if (_shaper.StoreRead()) 
                { 
                    try
                    { 
                        _shaper.StartMaterializingElement();
                        _shaper.RootCoordinator.ReadNextElement(_shaper);
                    }
                    finally 
                    {
                        _shaper.StopMaterializingElement(); 
                    } 
                    return true;
                } 
                this.Dispose();
                return false;
            }
 
            public void Reset()
            { 
                throw EntityUtil.NotSupported(); 
            }
        } 

        #endregion

        #region nested enumerator 

        ///  
        /// Enumerates (for each row in the input) an array of all coordinators producing new elements. The array 
        /// contains a position for each 'depth' in the result. A null value in any position indicates that no new
        /// results were produced for the given row at the given depth. It is possible for a row to contain no 
        /// results for any row.
        /// 
        private class RowNestedResultEnumerator : IEnumerator
        { 
            private readonly Shaper _shaper;
            private readonly Coordinator[] _current; 
 
            internal RowNestedResultEnumerator(Shaper shaper)
            { 
                _shaper = shaper;
                _current = new Coordinator[_shaper.RootCoordinator.MaxDistanceToLeaf() + 1];
            }
 
            public Coordinator[] Current
            { 
                get { return _current; } 
            }
 
            public void Dispose()
            {
                // Technically, calling GC.SuppressFinalize is not required because the class does not
                // have a finalizer, but it does no harm, protects against the case where a finalizer is added 
                // in the future, and prevents an FxCop warning.
                GC.SuppressFinalize(this); 
                _shaper.Finally(); 
            }
 
            object System.Collections.IEnumerator.Current
            {
                get { return _current; }
            } 

            public bool MoveNext() 
            { 
                Coordinator currentCoordinator = _shaper.RootCoordinator;
 
                try
                {
                    _shaper.StartMaterializingElement();
 
                    if (!_shaper.StoreRead())
                    { 
                        // Reset all collections 
                        this.RootCoordinator.ResetCollection(_shaper);
                        return false; 
                    }

                    int depth = 0;
                    bool haveInitializedChildren = false; 
                    for (; depth < _current.Length; depth++)
                    { 
                        // find a coordinator at this depth that currently has data (if any) 
                        while (currentCoordinator != null && !currentCoordinator.CoordinatorFactory.HasData(_shaper))
                        { 
                            currentCoordinator = currentCoordinator.Next;
                        }
                        if (null == currentCoordinator)
                        { 
                            break;
                        } 
 
                        // check if this row contains a new element for this coordinator
                        if (currentCoordinator.HasNextElement(_shaper)) 
                        {
                            // if we have children and haven't initialized them yet, do so now
                            if (!haveInitializedChildren && null != currentCoordinator.Child)
                            { 
                                currentCoordinator.Child.ResetCollection(_shaper);
                            } 
                            haveInitializedChildren = true; 

                            // read the next element 
                            currentCoordinator.ReadNextElement(_shaper);

                            // place the coordinator in the result array to indicate there is a new
                            // element at this depth 
                            _current[depth] = currentCoordinator;
                        } 
                        else 
                        {
                            // clear out the coordinator in result array to indicate there is no new 
                            // element at this depth
                            _current[depth] = null;
                        }
 
                        // move to child (in the next iteration we deal with depth + 1
                        currentCoordinator = currentCoordinator.Child; 
                    } 

                    // clear out all positions below the depth we reached before we ran out of data 
                    for (; depth < _current.Length; depth++)
                    {
                        _current[depth] = null;
                    } 
                }
                finally 
                { 
                    _shaper.StopMaterializingElement();
                } 

                return true;
            }
 
            public void Reset()
            { 
                throw EntityUtil.NotSupported(); 
            }
 
            internal Coordinator RootCoordinator
            {
                get { return _shaper.RootCoordinator; }
            } 
        }
 
        ///  
        /// Wraps RowNestedResultEnumerator and yields results appropriate to an ObjectQuery instance. In particular,
        /// root level elements (T) are returned only after aggregating all child elements. 
        /// 
        private class ObjectQueryNestedEnumerator : IEnumerator
        {
            private readonly RowNestedResultEnumerator _rowEnumerator; 
            private T _previousElement;
            private State _state; 
 
            internal ObjectQueryNestedEnumerator(RowNestedResultEnumerator rowEnumerator)
            { 
                _rowEnumerator = rowEnumerator;
                _previousElement = default(T);
                _state = State.Start;
            } 

            public T Current { get { return _previousElement; } } 
 
            public void Dispose()
            { 
                // Technically, calling GC.SuppressFinalize is not required because the class does not
                // have a finalizer, but it does no harm, protects against the case where a finalizer is added
                // in the future, and prevents an FxCop warning.
                GC.SuppressFinalize(this); 
                _rowEnumerator.Dispose();
            } 
 
            object System.Collections.IEnumerator.Current { get { return this.Current; } }
 
            public bool MoveNext()
            {
                // See the documentation for enum State to understand the behaviors and requirements
                // for each state. 
                switch (_state)
                { 
                    case State.Start: 
                        {
                            if (TryReadToNextElement()) 
                            {
                                // if there's an element in the reader...
                                ReadElement();
                            } 
                            else
                            { 
                                // no data at all... 
                                _state = State.NoRows;
                            } 
                        };
                        break;
                    case State.Reading:
                        { 
                            ReadElement();
                        }; 
                        break; 
                    case State.NoRowsLastElementPending:
                        { 
                            // nothing to do but move to the next state...
                            _state = State.NoRows;
                        };
                        break; 
                }
 
                bool result; 
                if (_state == State.NoRows)
                { 
                    _previousElement = default(T);
                    result = false;
                }
                else 
                {
                    result = true; 
                } 

                return result; 
            }

            /// 
            /// Requires: the row is currently positioned at the start of an element. 
            ///
            /// Reads all rows in the element and sets up state for the next element (if any). 
            ///  
            private void ReadElement()
            { 
                // remember the element we're currently reading
                _previousElement = _rowEnumerator.RootCoordinator.Current;

                // now we need to read to the next element (or the end of the 
                // reader) so that we can return the first element
                if (TryReadToNextElement()) 
                { 
                    // we're positioned at the start of the next element (which
                    // corresponds to the 'reading' state) 
                    _state = State.Reading;
                }
                else
                { 
                    // we're positioned at the end of the reader
                    _state = State.NoRowsLastElementPending; 
                } 
            }
 
            /// 
            /// Reads rows until the start of a new element is found. If no element
            /// is found before all rows are consumed, returns false.
            ///  
            private bool TryReadToNextElement()
            { 
                while (_rowEnumerator.MoveNext()) 
                {
                    // if we hit a new element, return true 
                    if (_rowEnumerator.Current[0] != null)
                    {
                        return true;
                    } 
                }
                return false; 
            } 

            public void Reset() 
            {
                _rowEnumerator.Reset();
            }
 
            /// 
            /// Describes the state of this enumerator with respect to the _rowEnumerator 
            /// it wraps. 
            /// 
            private enum State 
            {
                /// 
                /// No rows have been read yet
                ///  
                Start,
 
                ///  
                /// Positioned at the start of a new root element. The previous element must
                /// be stored in _previousElement. We read ahead in this manner so that 
                /// the previous element is fully populated (all of its children loaded)
                /// before returning.
                /// 
                Reading, 

                ///  
                /// Positioned past the end of the rows. The last element in the enumeration 
                /// has not yet been returned to the user however, and is stored in _previousElement.
                ///  
                NoRowsLastElementPending,

                /// 
                /// Positioned past the end of the rows. The last element has been returned to 
                /// the user.
                ///  
                NoRows, 
            }
        } 

        /// 
        /// Wraps RowNestedResultEnumerator and yields results appropriate to an EntityReader instance. In particular,
        /// yields RecordState whenever a new element becomes available at any depth in the result hierarchy. 
        /// 
        private class RecordStateEnumerator : IEnumerator 
        { 
            private readonly RowNestedResultEnumerator _rowEnumerator;
            private RecordState _current; 

            /// 
            /// Gets depth of coordinator we're currently consuming. If _depth == -1, it means we haven't started
            /// to consume the next row yet. 
            /// 
            private int _depth; 
            private bool _readerConsumed; 

            internal RecordStateEnumerator(RowNestedResultEnumerator rowEnumerator) 
            {
                _rowEnumerator = rowEnumerator;
                _current = null;
                _depth = -1; 
                _readerConsumed = false;
            } 
 
            public RecordState Current
            { 
                get { return _current; }
            }

            public void Dispose() 
            {
                // Technically, calling GC.SuppressFinalize is not required because the class does not 
                // have a finalizer, but it does no harm, protects against the case where a finalizer is added 
                // in the future, and prevents an FxCop warning.
                GC.SuppressFinalize(this); 
                _rowEnumerator.Dispose();
            }

            object System.Collections.IEnumerator.Current 
            {
                get { return _current; } 
            } 

            public bool MoveNext() 
            {
                if (!_readerConsumed)
                {
                    while (true) 
                    {
                        // keep on cycling until we find a result 
                        if (-1 == _depth || _rowEnumerator.Current.Length == _depth) 
                        {
                            // time to move to the next row... 
                            if (!_rowEnumerator.MoveNext())
                            {
                                // no more rows...
                                _current = null; 
                                _readerConsumed = true;
                                break; 
                            } 

                            _depth = 0; 
                        }

                        // check for results at the current depth
                        Coordinator currentCoordinator = _rowEnumerator.Current[_depth]; 
                        if (null != currentCoordinator)
                        { 
                            _current = ((Coordinator)currentCoordinator).Current; 
                            _depth++;
                            break; 
                        }

                        _depth++;
                    } 
                }
 
                return !_readerConsumed; 
            }
 
            public void Reset()
            {
                _rowEnumerator.Reset();
            } 
        }
 
        #endregion 

    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.


                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK