ObjectStateManager.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataEntity / System / Data / Objects / ObjectStateManager.cs / 1 / ObjectStateManager.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 
//          DEV notes
//   1- I should never need to set/get any value in terms of clayer 
//
//
//
//--------------------------------------------------------------------- 
using System;
using System.Data; 
using System.Collections.Generic; 
using System.Collections;
using System.ComponentModel; 
using System.Data.Common;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.Data.Objects.DataClasses; 
using System.Diagnostics;
using System.Globalization; 
using System.Reflection; 
using System.Text;
using System.Data.Common.Utils; 
using System.Data.Objects.Internal;

namespace System.Data.Objects
{ 
    /// 
    /// implementation of ObjectStateManager class 
    ///  
    public class ObjectStateManager : IEntityStateManager
    { 
        // dictionaries (one for each entity state) that store cache entries that represent entities
        // these are only non-null when there is an entity in respective state, must always check for null before using
        private Dictionary _addedEntityStore;
        private Dictionary _modifiedEntityStore; 
        private Dictionary _deletedEntityStore;
        private Dictionary _unchangedEntityStore; 
        private Dictionary _keylessEntityStore; 

        // dictionaries (one for each entity state) that store cache entries that represent relationships 
        // these are only non-null when there is an relationship in respective state, must always check for null before using
        private Dictionary _addedRelationshipStore;
        private Dictionary _deletedRelationshipStore;
        private Dictionary _unchangedRelationshipStore; 

        // mapping from EdmType or EntitySetQualifiedType to StateManagerTypeMetadata 
        private readonly Dictionary _metadataStore; 
        private readonly Dictionary _metadataMapping;
 
        private readonly MetadataWorkspace _metadataWorkspace;

        // delegate for notifying changs in collection
        private CollectionChangeEventHandler onObjectStateManagerChangedDelegate; 
        private CollectionChangeEventHandler onEntityDeletedDelegate;
 
        // Flag to indicate if we are in the middle of relationship fixup. 
        // This is set and cleared only during ResetEntityKey, because only in that case
        // do we allow setting a value on a non-null EntityKey property 
        private bool _inRelationshipFixup;

        private ComplexTypeMaterializer _complexTypeMaterializer; // materializer instance that can be used to create complex types with just a metadata workspace
 
        // Dictionary used to recovery after exception in ObjectContext.AttachTo()
        private Dictionary> _promotedRelationships; 
        internal Dictionary> PromotedRelationships 
        {
            get { return _promotedRelationships; } 
        }

        // Dictionary used to recovery after exception in ObjectContext.AttachTo()
        private Dictionary _promotedKeyEntries; 
        internal Dictionary PromotedKeyEntries
        { 
            get { return _promotedKeyEntries; } 
        }
 
        #region Private Fields for ObjectStateEntry change tracking

        /// 
        /// The object on which the change was made, could be an entity or a complex object 
        /// Also see comments for _changingOldValue
        ///  
        private object _changingObject; 

        ///  
        /// _changingMember and _changingEntityMember should be the same for changes to
        /// top-level entity properties. They are only different for complex members.
        /// Also see comments for _changingOldValue.
        ///  
        private string _changingMember;
 
        ///  
        /// The top-level entity property that is changing. This could be the actual property
        /// that is changing, or the change could be occurring one or more levels down in the hierarchy 
        /// on a complex object.
        /// Also see comments for _changingOldValue
        /// 
        private string _changingEntityMember; 

        ///  
        /// EntityState of the entry during the changing notification. Used to detect 
        /// if the state has changed between the changing and changed notifications, which we do not allow.
        /// Also see comments for _changingOldValue 
        /// 
        private EntityState _changingState;

        ///  
        /// True if we are in a state where original values are to be stored during a changed notification
        /// This flags gets set during the changing notification and is used during changed to determine 
        /// if we need to save to original values or not. 
        /// Also see comments for _changingOldValue
        private bool _saveOriginalValues; 

        /// 
        /// Cache entity property/changing object/original value here between changing and changed events
        ///  
        /// 
        /// Only single threading is supported and changing/changed calls cannot be nested. Consecutive calls to Changing 
        /// overwrite previously cached values. Calls to Changed must have been preceded by a Changing call on the same property 
        ///
        /// a) user starts property value change with a call to 
        ///    IEntityChangeTracker.EntityMemberChanging or IEntityChangeTracker.EntityComplexMemberChanging
        /// b) The public interface methods call EntityValueChanging, which caches the original value
        /// c) new property value is stored on object
        /// d) users completes the property value change with a call to 
        ///    IEntityChangeTracker.EntityMemberChanged or IEntityChangeTracker.EntityComplexMemberChanged
        /// e} The public interface methods call EntityValueChanged, which saves the cached value in the original values record 
        ///  
        private object _changingOldValue;
 
        #endregion

        /// 
        /// ObjectStateManager ctor. 
        /// 
        ///  
        public ObjectStateManager(MetadataWorkspace metadataWorkspace) 
        {
            EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace"); 
            _metadataWorkspace = metadataWorkspace;

            _metadataStore = new Dictionary();
            _metadataMapping = new Dictionary(EntitySetQualifiedType.EqualityComparer); 
        }
 
        #region Internal Properties for ObjectStateEntry change tracking 

        internal object ChangingObject 
        {
            get { return _changingObject; }
            set { _changingObject = value; }
        } 

        internal string ChangingEntityMember 
        { 
            get { return _changingEntityMember; }
            set { _changingEntityMember = value; } 
        }

        internal string ChangingMember
        { 
            get { return _changingMember; }
            set { _changingMember = value; } 
        } 

        internal EntityState ChangingState 
        {
            get { return _changingState; }
            set { _changingState = value; }
        } 

        internal bool SaveOriginalValues 
        { 
            get { return _saveOriginalValues; }
            set { _saveOriginalValues = value; } 
        }

        internal object ChangingOldValue
        { 
            get { return _changingOldValue; }
            set { _changingOldValue = value; } 
        } 

        // Used by ObjectStateEntry to determine if it's safe to set a value 
        // on a non-null IEntity.EntityKey property
        internal bool InRelationshipFixup
        {
            get { return _inRelationshipFixup; } 
        }
 
        internal ComplexTypeMaterializer ComplexTypeMaterializer 
        {
            get 
            {
                if (_complexTypeMaterializer == null)
                {
                    _complexTypeMaterializer = new ComplexTypeMaterializer(this.MetadataWorkspace); 
                }
                return _complexTypeMaterializer; 
            } 
        }
 
        #endregion


        ///  
        /// MetadataWorkspace property
        ///  
        /// MetadataWorkspace 
        public MetadataWorkspace MetadataWorkspace
        { 
            get
            {
                return _metadataWorkspace;
            } 
        }
 
        #region events ObjectStateManagerChanged / EntityDeleted 
        /// 
        /// Event to notify changes in the collection. 
        /// 
        public event CollectionChangeEventHandler ObjectStateManagerChanged
        {
            add 
            {
                onObjectStateManagerChangedDelegate += value; 
            } 
            remove
            { 
                onObjectStateManagerChangedDelegate -= value;
            }
        }
 
        internal event CollectionChangeEventHandler EntityDeleted
        { 
            add 
            {
                onEntityDeletedDelegate += value; 
            }
            remove
            {
                onEntityDeletedDelegate -= value; 
            }
        } 
 
        internal void OnObjectStateManagerChanged(CollectionChangeAction action, object obj)
        { 
            if (onObjectStateManagerChangedDelegate != null)
            {
                onObjectStateManagerChangedDelegate(this, new CollectionChangeEventArgs(action, obj));
            } 
        }
 
        private void OnEntityDeleted(CollectionChangeAction action, object obj) 
        {
            if (onEntityDeletedDelegate != null) 
            {
                onEntityDeletedDelegate(this, new CollectionChangeEventArgs(action, obj));
            }
        } 
        #endregion
 
        ///  
        /// Adds an object stub to the cache.
        ///  
        /// the key of the object to add
        /// the entity set of the given object
        ///
        internal ObjectStateEntry AddKeyEntry(EntityKey entityKey, EntitySet entitySet) 
        {
            Debug.Assert((object)entityKey != null, "entityKey cannot be null."); 
            Debug.Assert(entitySet != null, "entitySet must be non-null."); 

            // We need to determine if an equivalent entry already exists; 
            // this is illegal in certain cases.
            ObjectStateEntry existingEntry = FindObjectStateEntry(entityKey);
            if (existingEntry != null)
            { 
                throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
            } 
 
            // Get a StateManagerTypeMetadata for the entity type.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entitySet.ElementType); 

            // Create a cache entry.
            ObjectStateEntry newEntry = new EntityEntry(entityKey, entitySet, this, typeMetadata);
 
            // A new entity is being added.
            AddEntityEntryToDictionary(newEntry, newEntry.State); 
 
            return newEntry;
        } 

        /// 
        /// Adds an object to the ObjectStateManager.
        ///  
        /// the object to add
        /// the entity set of the given object 
        /// Name of the argument passed to a public method, for use in exceptions. 
        /// Indicates whether the entity is added or unchanged.
        internal void AddEntry(object dataObject, EntityKey passedKey, EntitySet entitySet, string argumentName, bool isAdded) 
        {

            Debug.Assert(dataObject != null, "dataObject cannot be null.");
            Debug.Assert(entitySet != null, "entitySet must be non-null."); 
            // shadowValues is allowed to be null
            Debug.Assert(argumentName != null, "argumentName cannot be null."); 
 
            EntityKey entityKey = passedKey;
 
            // Get a StateManagerTypeMetadata for the entity type.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(dataObject.GetType(), entitySet);

            // dataObject's type should match to type that can be contained by the entityset 
            EdmType entityEdmType = typeMetadata.CdmMetadata.EdmType;
            //OC Mapping will make sure that non-abstract type in O space is always mapped to a non-abstract type in C space 
            Debug.Assert(!entityEdmType.Abstract, "non-abstract type in O space is mapped to abstract type in C space"); 
            if ((isAdded) && !entitySet.ElementType.IsAssignableFrom(entityEdmType))
            { 
                throw EntityUtil.EntityTypeDoesNotMatchEntitySet(dataObject.GetType().Name, TypeHelpers.GetFullName(entitySet), argumentName);
            }

            IEntityWithKey entityWithKey = dataObject as IEntityWithKey; 
            // The entityKey may be passed via the argument or via dataObject as IEntity.
            if (null != entityWithKey) 
            { 
                EntityKey dataObjectEntityKey = entityWithKey.EntityKey;
#if DEBUG 
                if ((object)dataObjectEntityKey != null && (object)entityKey != null)
                {
                    Debug.Assert(dataObjectEntityKey == entityKey, "The passed key and the key on dataObject must match.");
                } 
#endif
                if (null != (object)dataObjectEntityKey) 
                { 
                    entityKey = dataObjectEntityKey;
                    // These two checks verify that entityWithKey.EntityKey implemented by the user on a (I)POCO entity returns what it was given. 
                    EntityUtil.CheckEntityKeyNull(entityKey);
                    EntityUtil.CheckEntityKeysMatch(entityWithKey, entityKey);
                }
            } 

 
#if DEBUG 
            if ((object)entityKey != null && !entityKey.IsTemporary && !isAdded)
            { 
                // If the entity already has a permanent key, and we were invoked
                // from the materializer, assert that the key is correct.  We don't check
                // for temporary keys because temporary keys don't contain values.
                // Note that this method is only called in debug builds. 
                // entityKey is passed because the key has not yet been placed into the ObjectStateManager.
                AssertKeyMatchesEntity(dataObject, entityKey, entitySet); 
            } 
#endif
 
            if ((object)entityKey == null) // Neither entityWithKey.EntityKey nor the passed entityKey were non-null.
            {
                // If the entity does not have a key, create and add a temporary key.
                entityKey = new EntityKey(entitySet); 
                EntityUtil.SetKeyOntoEntity(entityWithKey, entityKey);
            } 
 
            // Create a cache entry.
            ObjectStateEntry newEntry = new EntityEntry(dataObject, entityKey, entitySet, this, typeMetadata, isAdded); 

            //Verify that the entityKey is set correctly--also checks entry.EK and entity.EK internally
            Debug.Assert(entityKey == newEntry.EntityKey, "The key on the new entry was not set correctly");
 
            // We need to determine if an equivalent entry already exists; this is illegal
            // in certain cases. 
            ObjectStateEntry existingEntry; 
            if ((isAdded) && (null != (existingEntry = FindObjectStateEntry(entityKey))))
            { 
                if (existingEntry.Entity != dataObject)
                {
                    throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
                } 
                // key does exist but entity is the same, it is being re-added ;
                // no-op when Add(entity) 
                // NOTE we don't want to re-add entities in other then Added state 
                if (existingEntry.State != EntityState.Added)  // (state == DataRowState.Unchanged && state == DataRowState.Modified)
                { 
                    throw EntityUtil.ObjectStateManagerDoesnotAllowToReAddUnchangedOrModifiedOrDeletedEntity(existingEntry.State);
                }
            }
            else 
            {
                // ObjectMaterializer will have already determined the existing entry doesn't exist 
                Debug.Assert(null == FindObjectStateEntry(entityKey), "should not have existing entry"); 

                if (newEntry.State == EntityState.Added && !entityKey.IsTemporary) 
                {
                    // If the entity doesn't already exist in the state manager
                    // and we intend to put the entity in the Added state (i.e.,
                    // AddEntry() was invoked from ObjectContext.AddObject()), 
                    // the entity's key must be temporary, because we require
                    // that all entries in the Added state have temporary keys. 
                    throw EntityUtil.CannotAddEntityWithKeyAttached(); 
                }
 
                // A new entity is being added.
                newEntry.AttachObjectStateManagerToEntity();
                AddEntityEntryToDictionary(newEntry, newEntry.State);
                // fire ColectionChanged event  only when a new entity is added to cache 
                OnObjectStateManagerChanged(CollectionChangeAction.Add, newEntry.Entity);
            } 
        } 

        // devnote: see comment to SQLBU 555615 in ObjectContext.AttachSingleObject() 
        internal void PromoteKeyEntryInitialization(ObjectStateEntry keyEntry,
            object entity,
            IExtendedDataRecord shadowValues,
            bool replacingEntry) 
        {
            Debug.Assert(keyEntry != null, "keyEntry must be non-null."); 
            Debug.Assert(entity != null, "entity cannot be null."); 
            // shadowValues is allowed to be null
 
            // Future Enhancement: Fixup already has this information, don't rediscover it
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entity.GetType(), (EntitySet)keyEntry.EntitySet);
            keyEntry.PromoteKeyEntry(entity, shadowValues, typeMetadata);
            AddEntryToKeylessStore(entity, keyEntry); 

            if (replacingEntry) 
            { 
                // if we are replacing an exising entry, then clean the entity's change tracker
                // so that it can be reset to this newly promoted entry 
                EntityUtil.SetChangeTrackerOntoEntity(entity, (IEntityChangeTracker)null);
            }
            // A new entity is being added.
            keyEntry.AttachObjectStateManagerToEntity(); 

            OnObjectStateManagerChanged(CollectionChangeAction.Add, keyEntry.Entity); 
        } 

        ///  
        /// Upgrades an entity key entry in the cache to a a regular entity
        /// 
        /// the key entry that exists in the state manager
        /// the object to add 
        /// a data record representation of the entity's values, including any values in shadow state
        /// True if this promoted key entry is replacing an existing detached entry 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds 
        /// Name of the argument passed to a public method, for use in exceptions.
        internal void PromoteKeyEntry(ObjectStateEntry keyEntry, 
            object entity,
            IExtendedDataRecord shadowValues,
            bool replacingEntry,
            bool setIsLoaded, 
            bool keyEntryInitialized,
            string argumentName) 
        { 
            Debug.Assert(keyEntry != null, "keyEntry must be non-null.");
            Debug.Assert(entity != null, "entity cannot be null."); 
            // shadowValues is allowed to be null
            Debug.Assert(argumentName != null, "argumentName cannot be null.");

            if (!keyEntryInitialized) 
            {
                this.PromoteKeyEntryInitialization(keyEntry, entity, shadowValues, replacingEntry); 
            } 

            bool doCleanup = true; 
            try
            {
                // We don't need to worry about the KeyEntry <-- Relationship --> KeyEntry because a key entry must
                // reference a non-key enrty. Fix up ther other side of the relationship. 
                // Get all the relationships that currently exist for this key entry
                foreach (ObjectStateEntry relationshipEntry in CopyOfRelationshipsByKey(keyEntry.EntityKey)) 
                { 
                    if (relationshipEntry.State != EntityState.Deleted)
                    { 
                        // Find the association ends that correspond to the source and target
                        AssociationEndMember sourceMember = keyEntry.GetAssociationEndMember(relationshipEntry);
                        AssociationEndMember targetMember = MetadataHelper.GetOtherAssociationEnd(sourceMember);
 
                        // Find the other end of the relationship
                        ObjectStateEntry targetEntry = keyEntry.GetOtherEndOfRelationship(relationshipEntry); 
 
                        // Here we are promoting based on a non-db retrieval so we use Append rules
                        AddEntityToCollectionOrReference( 
                            MergeOption.AppendOnly,
                            entity as IEntityWithRelationships,
                            sourceMember,
                            targetEntry.Entity as IEntityWithRelationships, 
                            targetMember,
                            /*setIsLoaded*/ setIsLoaded, 
                            /*relationshipAlreadyExists*/ true, 
                            /*inKeyEntryPromotion*/ true);
                    } 
                }
                doCleanup = false;
            }
            finally 
            {
                if (doCleanup) 
                { 
                    keyEntry.DetachObjectStateManagerFromEntity();
                    RemoveEntryFromKeylessStore(entity); 
                    keyEntry.DegradeEntry();
                }
            }
 
            if (this.IsAttachTracking)
            { 
                this.PromotedKeyEntries.Add(entity, keyEntry); 
            }
        } 

        internal void TrackPromotedRelationship(RelatedEnd relatedEnd, object entity)
        {
            Debug.Assert(relatedEnd != null); 
            Debug.Assert(entity != null);
            Debug.Assert(this.IsAttachTracking, "This method should be called only from ObjectContext.AttachTo (indirectly)"); 
 
            IList entities;
            if (!this.PromotedRelationships.TryGetValue(relatedEnd, out entities)) 
            {
                entities = new List();
                this.PromotedRelationships.Add(relatedEnd, entities);
            } 
            entities.Add(entity);
        } 
 
        internal void DegradePromotedRelationships()
        { 
            Debug.Assert(this.IsAttachTracking, "This method should be called only from the cleanup code");

            foreach (var pair in this.PromotedRelationships)
            { 
                foreach (object entity in pair.Value)
                { 
                    Debug.Assert((entity as IEntityWithRelationships) != null, 
                        "Entity which doesn't implement IEntityWithRelationships is in the recovery collection");
 
                    if (pair.Key.RemoveEntityFromLocallyCachedCollection(entity as IEntityWithRelationships, /*resetIsLoaded*/ false))
                    {
                        pair.Key.OnAssociationChanged(CollectionChangeAction.Remove, entity);
                    } 
                }
            } 
        } 

        ///  
        /// Performs non-generic collection or reference fixup between two entities
        /// This method should only be used in scenarios where we are automatically hooking up relationships for
        /// the user, and not in cases where they are manually setting relationships.
        ///  
        /// The MergeOption to use to decide how to resolve EntityReference conflicts
        /// The entity instance on the source side of the relationship 
        /// The AssociationEndMember that contains the metadata for the source entity 
        /// The entity instance on the source side of the relationship
        /// The AssociationEndMember that contains the metadata for the target entity 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds
        /// Whether or not the relationship entry already exists in the cache for these entities
        /// Whether this method is used in key entry promotion
        internal static void AddEntityToCollectionOrReference( 
            MergeOption mergeOption,
            IEntityWithRelationships sourceEntity, 
            AssociationEndMember sourceMember, 
            IEntityWithRelationships targetEntity,
            AssociationEndMember targetMember, 
            bool setIsLoaded,
            bool relationshipAlreadyExists,
            bool inKeyEntryPromotion)
        { 
            // Call GetRelatedEnd to retrieve the related end on the source entity that points to the target entity
            RelatedEnd relatedEnd = (RelatedEnd)sourceEntity.RelationshipManager.GetRelatedEnd(sourceMember.DeclaringType.FullName, targetMember.Name); 
 
            // EntityReference can only have one value
            if (targetMember.RelationshipMultiplicity != RelationshipMultiplicity.Many) 
            {
                switch (mergeOption)
                {
                    case MergeOption.NoTracking: 
                        // if using NoTracking, we have no way of determining identity resolution.
                        // Throw an exception saying the EntityReference is already populated and to try using 
                        // a different MergeOption 
                        if (!relatedEnd.IsEmpty())
                        { 
                            throw EntityUtil.CannotAddMoreThanOneEntityToEntityReferenceTryOtherMergeOption();
                        }
                        break;
                    case MergeOption.AppendOnly: 
                        // SQLBU 551031
                        // In key entry promotion case, detect that sourceEntity is already related to some entity in the context, 
                        // so it cannot be related to another entity being attached (relation 1-1). 
                        // Without this check we would throw exception from RelatedEnd.Add() but the exception message couldn't
                        // properly describe what has happened. 
                        if (inKeyEntryPromotion &&
                            !relatedEnd.IsEmpty())
                        {
                            throw EntityUtil.EntityConflictsWithKeyEntry(); 
                        }
                        break; 
 
                    case MergeOption.PreserveChanges:
                    case MergeOption.OverwriteChanges: 
                        // Retrieve the current target entity and the relationship
                        IEntityWithRelationships currentTargetEntity = null;
                        foreach (IEntityWithRelationships target in relatedEnd)
                        { 
                            Debug.Assert(currentTargetEntity == null, "Found more than one entry in an EntityReference");
                            currentTargetEntity = target; 
                        } 

                        if (currentTargetEntity != null) 
                        {
                            Debug.Assert(targetEntity != currentTargetEntity, "Source entity is already related to this target entity");

                            // The source entity is already related to a different target, so before we hook it up to the new target, 
                            // disconnect the existing related ends and detach the relationship entry
                            ObjectStateEntry relationshipEntry = relatedEnd.FindRelationshipEntryInObjectStateManager(currentTargetEntity); 
                            Debug.Assert(relationshipEntry != null, "Could not find relationship entry."); 

                            relatedEnd.RemoveAll(); 

                            // If the relationship was Added prior to the above RemoveAll, it will have already been detached
                            // If it was Unchanged, it is now Deleted and should be detached
                            // It should never have been Deleted before now, because we just got currentTargetEntity from the related end 
                            if (relationshipEntry.State == EntityState.Deleted)
                            { 
                                relationshipEntry.AcceptChanges(); 
                            }
 
                            Debug.Assert(relationshipEntry.State == EntityState.Detached, "relationshipEntry should be Detached");
                        }
                        break;
                } 
            }
 
            RelatedEnd targetRelatedEnd = null; 
            if (mergeOption == MergeOption.NoTracking)
            { 
                targetRelatedEnd = relatedEnd.GetOtherEndOfRelationship(targetEntity);
                if (targetRelatedEnd.IsLoaded)
                {
                    // The EntityCollection has already been loaded as part of the query and adding additional 
                    // entities would cause duplicate entries
                    throw EntityUtil.CannotFillTryDifferentMergeOption(targetRelatedEnd.SourceRoleName, targetRelatedEnd.RelationshipName); 
                } 
            }
 
            // Add the target entity
            relatedEnd.Add(targetEntity,
                true /*applyConstraints*/,
                true /*addRelationshipAsUnchanged*/, 
                relationshipAlreadyExists /*relationshipAlreadyExists*/);
 
            Debug.Assert(!(inKeyEntryPromotion && sourceEntity.RelationshipManager.Context == null), 
                "sourceEntity has been just attached to the context in PromoteKeyEntry, so Context shouldn't be null");
            Debug.Assert( 
                !(inKeyEntryPromotion &&
                sourceEntity.RelationshipManager.Context.ObjectStateManager.IsAttachTracking &&
                (setIsLoaded || mergeOption == MergeOption.NoTracking)),
                "This verifies that UpdateRelatedEnd is a no-op in a keyEntryPromotion case when the method is called indirectly from ObjectContext.AttachTo"); 

            // If either end is an EntityReference, we may need to set IsLoaded or the DetachedEntityKey 
            UpdateRelatedEnd(relatedEnd, targetEntity, setIsLoaded, mergeOption); 
            // we may have already retrieved the target end above, but if not, just get it now
            if (targetRelatedEnd == null) 
            {
                targetRelatedEnd = relatedEnd.GetOtherEndOfRelationship(targetEntity);
            }
            UpdateRelatedEnd(targetRelatedEnd, sourceEntity, setIsLoaded, mergeOption); 

            // In case the method was called from ObjectContext.AttachTo, we have to track relationships which were "promoted" 
            // Tracked relationships are used in recovery code of AttachTo. 
            if (inKeyEntryPromotion && sourceEntity.RelationshipManager.Context.ObjectStateManager.IsAttachTracking)
            { 
                sourceEntity.RelationshipManager.Context.ObjectStateManager.TrackPromotedRelationship(relatedEnd, targetEntity);
                sourceEntity.RelationshipManager.Context.ObjectStateManager.TrackPromotedRelationship(targetRelatedEnd, sourceEntity);
            }
        } 

        // devnote: This method should only be used in scenarios where we are automatically hooking up relationships for 
        // the user, and not in cases where they are manually setting relationships. 
        private static void UpdateRelatedEnd(RelatedEnd relatedEnd, IEntityWithRelationships relatedEntity, bool setIsLoaded, MergeOption mergeOption)
        { 
            AssociationEndMember endMember = (AssociationEndMember)(relatedEnd.ToEndMember);

            if ((endMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
                 endMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne)) 
            {
                if (setIsLoaded) 
                { 
                    relatedEnd.SetIsLoaded(true);
                } 
                // else we just want to leave IsLoaded alone, not set it to false

                // In NoTracking cases, we want to enable the EntityReference.EntityKey property, so we have to set the key
                if (mergeOption == MergeOption.NoTracking) 
                {
                    IEntityWithKey entityWithKey = relatedEntity as IEntityWithKey; 
                    if (entityWithKey != null) 
                    {
                        EntityKey targetKey = entityWithKey.EntityKey; 
                        EntityUtil.CheckEntityKeyNull(targetKey);

                        // since endMember is not Many, relatedEnd must be an EntityReference
                        ((EntityReference)relatedEnd).DetachedEntityKey = targetKey; 
                    }
#if DEBUG 
                    else 
                    {
                        // This should have been validated before we got here, so just Assert and don't validate again 
                        Debug.Fail("Relationships are not supported between NoTracking entities that do not support IEntityWithKey");
                    }
#endif
                } 
            }
        } 
 

        ///  
        /// Updates the relationships between a given source entity and a collection of target entities.
        /// Used for full span and related end Load methods, where the following may be true:
        /// (a) both sides of each relationship are always full entities and not stubs
        /// (b) there could be multiple entities to process at once 
        /// (c) NoTracking queries are possible.
        /// Not used for relationship span because although some of the logic is similar, the above are not true. 
        ///  
        /// ObjectContext to use to look up existing relationships. Using the context here instead of ObjectStateManager because for NoTracking queries
        /// we shouldn't even touch the state manager at all, so we don't want to access it until we know we are not using NoTracking. 
        /// MergeOption to use when updating existing relationships
        /// AssociationSet for the relationships
        /// Role of sourceEntity in associationSet
        /// EntityKey for sourceEntity 
        /// Source entity in the relationship
        /// Role of each targetEntity in associationSet 
        /// List of target entities to use to create relationships with sourceEntity 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds
        internal static int UpdateRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet, AssociationEndMember sourceMember, EntityKey sourceKey, IEntityWithRelationships sourceEntity, AssociationEndMember targetMember, IList targetEntities, bool setIsLoaded) 
        {
            int count = 0;

            if (targetEntities != null) 
            {
                if (mergeOption == MergeOption.NoTracking) 
                { 
                    RelatedEnd relatedEnd = (RelatedEnd)sourceEntity.RelationshipManager.GetRelatedEnd(sourceMember.DeclaringType.FullName, targetMember.Name);
                    if (!relatedEnd.IsEmpty()) 
                    {
                        // The RelatedEnd has already been filled as part of the query and adding additional
                        // entities would cause duplicate entries
                        throw EntityUtil.CannotFillTryDifferentMergeOption(relatedEnd.SourceRoleName, relatedEnd.RelationshipName); 
                    }
                } 
 
                foreach (IEntityWithRelationships targetEntity in targetEntities)
                { 
                    count++;

                    // If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one
                    EntityState newEntryState; 
                    if (mergeOption == MergeOption.NoTracking)
                    { 
                        // For NoTracking, we shouldn't touch the state manager, so no need to look for existing relationships to handle, just connect the two entities. 
                        // We don't care if the relationship already exists in the state manager or not, so just pass relationshipAlreadyExists=true so it won't look for it
                        AddEntityToCollectionOrReference( 
                            MergeOption.NoTracking,
                            sourceEntity,
                            sourceMember,
                            targetEntity, 
                            targetMember,
                            setIsLoaded, 
                            /*relationshipAlreadyExists*/ true, 
                            /*inKeyEntryPromotion*/ false);
                    } 
                    else
                    {
                        ObjectStateManager manager = context.ObjectStateManager;
                        EntityKey targetKey = manager.GetEntityKey(targetEntity); 
                        if (!TryUpdateExistingRelationships(context, mergeOption, associationSet, sourceMember, sourceKey, sourceEntity, targetMember, targetKey, setIsLoaded, out newEntryState))
                        { 
                            bool needNewRelationship = true; 
                            switch (sourceMember.RelationshipMultiplicity)
                            { 
                                case RelationshipMultiplicity.ZeroOrOne:
                                case RelationshipMultiplicity.One:
                                    // The other end of the relationship might already be related to something else, in which case we need to fix it up.
                                    // devnote1: In some cases we can let relationship span do this, but there are cases, like EntityCollection.Attach, where there is no query 
                                    //           and thus no relationship span to help us. So, for now, this is redundant because relationship span will make another pass over these
                                    //           entities, but unless I add a flag or something to indicate when I have to do it and when I don't, this is necessary. 
                                    // devnote2: The target and source arguments are intentionally reversed in the following call, because we already know there isn't a relationship 
                                    //           between the two entities we are current processing, but we want to see if there is one between the target and another source
                                    needNewRelationship = !TryUpdateExistingRelationships(context, mergeOption, associationSet, targetMember, 
                                        targetKey, targetEntity, sourceMember, sourceKey, setIsLoaded, out newEntryState);
                                    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 (newEntryState != EntityState.Deleted)
                                { 
                                    AddEntityToCollectionOrReference(
                                        mergeOption, 
                                        sourceEntity, 
                                        sourceMember,
                                        targetEntity, 
                                        targetMember,
                                        setIsLoaded,
                                        /*relationshipAlreadyExists*/ false,
                                        /*inKeyEntryPromotion*/ false); 
                                }
                                else 
                                { 
                                    // Add a Deleted relationship between the source entity and the target entity
                                    // No end fixup is necessary since the relationship is Deleted 
                                    RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
                                    manager.AddNewRelation(wrapper, EntityState.Deleted);
                                }
                            } 
                            // else there is nothing else for us to do, the relationship has been handled already
                        } 
                        // else there is nothing else for us to do, the relationship has been handled already 
                    }
                } 
            }
            return count;
            // devnote: Don't set IsLoaded on the target related end here -- the caller can do this more efficiently than we can here in some cases.
        } 

        ///  
        /// Removes relationships if necessary when a query determines that the source entity has no relationships on the server 
        /// 
        /// ObjectContext that contains the client relationships 
        /// MergeOption to use when updating existing relationships
        /// AssociationSet for the incoming relationship
        /// EntityKey of the source entity in the relationship
        /// Role of the source entity in the relationship 
        internal static void RemoveRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet,
            EntityKey sourceKey, AssociationEndMember sourceMember) 
        { 
            Debug.Assert(mergeOption == MergeOption.PreserveChanges || mergeOption == MergeOption.OverwriteChanges, "Unexpected MergeOption");
            List deletedRelationships = new List(); 

            // This entity has no related entities on the server for the given associationset and role. If it has related
            // entities on the client, we may need to update those relationships, depending on the MergeOption
            if (mergeOption == MergeOption.OverwriteChanges) 
            {
                foreach (ObjectStateEntry relationshipEntry in context.ObjectStateManager.FindRelationshipsByKey(sourceKey)) 
                { 
                    // We only care about the relationships that match the incoming associationset and role for the source entity
                    if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceKey, sourceMember)) 
                    {
                        deletedRelationships.Add(relationshipEntry);
                    }
                } 
            }
            else if (mergeOption == MergeOption.PreserveChanges) 
            { 
                // Leave any Added relationships for this entity, but remove Unchanged and Deleted ones
                foreach (ObjectStateEntry relationshipEntry in context.ObjectStateManager.FindRelationshipsByKey(sourceKey)) 
                {
                    // We only care about the relationships that match the incoming associationset and role for the source entity
                    if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceKey, sourceMember) &&
                        relationshipEntry.State != EntityState.Added) 
                    {
                        deletedRelationships.Add(relationshipEntry); 
                    } 
                }
            } 
            // else we do nothing. We never expect any other states here, and already Assert this condition at the top of the method

            foreach (ObjectStateEntry deletedEntry in deletedRelationships)
            { 
                ObjectStateManager.RemoveRelatedEndsAndDetachRelationship(deletedEntry, true);
            } 
        } 
        /// 
        /// Tries to updates one or more existing relationships for an entity, based on a given MergeOption and a target entity. 
        /// 
        /// ObjectContext to use to look up existing relationships for sourceEntity
        /// MergeOption to use when updating existing relationships
        /// AssociationSet for the relationship we are looking for 
        /// AssociationEndMember for the source role of the relationship
        /// EntityKey for the source entity in the relationship (passed here so we don't have to look it up again) 
        /// Source entity in the relationship 
        /// AssociationEndMember for the target role of the relationship
        /// EntityKey for the target entity in the relationship 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds
        /// [out] EntityState to be used for in scenarios where we need to add a new relationship after this method has returned
        /// 
        /// true if an existing relationship is found and updated, and no further action is needed 
        /// false if either no relationship was found, or if one was found and updated, but a new one still needs to be added
        ///  
        internal static bool TryUpdateExistingRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet, AssociationEndMember sourceMember, EntityKey sourceKey, IEntityWithRelationships sourceEntity, AssociationEndMember targetMember, EntityKey targetKey, bool setIsLoaded, out EntityState newEntryState) 
        {
            Debug.Assert(mergeOption != MergeOption.NoTracking, "Existing relationships should not be updated with NoTracking"); 

            // New relationships are always added as Unchanged except in specific scenarios. If there are multiple relationships being updated, and
            // at least one of those requests the new relationship to be Deleted, it should always be added as Deleted, even if there are other
            // relationships being updated that don't specify a state. Adding as Unchanged is just the default unless a scenario needs it to be Deleted to 
            // acheive a particular result.
            newEntryState = EntityState.Unchanged; 
 
            // Unless we find a case below where we explicitly do not want a new relationship, we should always add one to match the server.
            bool needNewRelationship = true; 

            ObjectStateManager manager = context.ObjectStateManager;
            List entriesToDetach = null;
            List entriesToUpdate = null; 
            foreach (ObjectStateEntry relationshipEntry in manager.FindRelationshipsByKey(sourceKey))
            { 
                // We only care about relationships for the same AssociationSet and where the source entity is in the same role as it is in the incoming relationship. 
                if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceKey, sourceMember))
                { 
                    // If the other end of this relationship matches our current target entity, this relationship entry matches the server
                    if (targetKey == relationshipEntry.Wrapper.GetOtherEntityKey(sourceKey))
                    {
                        if (entriesToUpdate == null) 
                        {
                            entriesToUpdate = new List(); 
                        } 
                        entriesToUpdate.Add(relationshipEntry);
                    } 
                    else
                    {
                        // We found an existing relationship where the reference side is different on the server than what the client has.
 
                        // This relationship is between the same source entity and a different target, so we may need to take steps to fix up the
                        // relationship to ensure that the client state is correct based on the requested MergeOption. 
                        // The only scenario we care about here is one where the target member has zero or one multiplicity (0..1 or 1..1), because those 
                        // are the only cases where it is meaningful to say that the relationship is different on the server and the client. In scenarios
                        // where the target member has a many (*) multiplicity, it is possible to have multiple relationships between the source key 
                        // and other entities, and we don't want to touch those here.
                        switch (targetMember.RelationshipMultiplicity)
                        {
                            case RelationshipMultiplicity.One: 
                            case RelationshipMultiplicity.ZeroOrOne:
                                switch (mergeOption) 
                                { 
                                    case MergeOption.AppendOnly:
                                        if (relationshipEntry.State != EntityState.Deleted) 
                                        {
                                            Debug.Assert(relationshipEntry.State == EntityState.Added || relationshipEntry.State == EntityState.Unchanged, "Unexpected relationshipEntry state");
                                            needNewRelationship = false; // adding a new relationship would conflict with the existing one
                                        } 
                                        break;
                                    case MergeOption.OverwriteChanges: 
                                        if (entriesToDetach == null) 
                                        {
                                            entriesToDetach = new List(); 
                                        }
                                        entriesToDetach.Add(relationshipEntry);
                                        break;
                                    case MergeOption.PreserveChanges: 
                                        switch (relationshipEntry.State)
                                        { 
                                            case EntityState.Added: 
                                                newEntryState = EntityState.Deleted;
                                                break; 
                                            case EntityState.Unchanged:
                                                if (entriesToDetach == null)
                                                {
                                                    entriesToDetach = new List(); 
                                                }
                                                entriesToDetach.Add(relationshipEntry); 
                                                break; 
                                            case EntityState.Deleted:
                                                newEntryState = EntityState.Deleted; 
                                                if (entriesToDetach == null)
                                                {
                                                    entriesToDetach = new List();
                                                } 
                                                entriesToDetach.Add(relationshipEntry);
                                                break; 
                                            default: 
                                                Debug.Assert(false, "Unexpected relationship entry state");
                                                break; 
                                        }
                                        break;
                                    default:
                                        Debug.Assert(false, "Unexpected MergeOption"); 
                                        break;
                                } 
                                break; 
                            case RelationshipMultiplicity.Many:
                                // do nothing because its okay for this source entity to have multiple different targets, so there is nothing for us to fixup 
                                break;
                            default:
                                Debug.Assert(false, "Unexpected targetMember.RelationshipMultiplicity");
                                break; 
                        }
                    } 
                } 
            }
 
            // Detach all of the entries that we have collected above
            if (entriesToDetach != null)
            {
                foreach (ObjectStateEntry entryToDetach in entriesToDetach) 
                {
                    // the entry may have already been detached by another operation. If not, detach it now. 
                    if (entryToDetach.State != EntityState.Detached) 
                    {
                        RemoveRelatedEndsAndDetachRelationship(entryToDetach, setIsLoaded); 
                    }
                }
            }
 
            // Update all of the matching entries that we have collectioned above
            if (entriesToUpdate != null) 
            { 
                foreach (ObjectStateEntry relationshipEntry in entriesToUpdate)
                { 
                    // Don't need new relationship to be added to match the server, since we already have a match
                    needNewRelationship = false;

                    // We have an existing relationship entry that matches exactly to the incoming relationship from the server, but 
                    // we may need to update it on the client based on the MergeOption and the state of the relationship entry.
                    switch (mergeOption) 
                    { 
                        case MergeOption.AppendOnly:
                            // AppendOnly and NoTracking shouldn't affect existing relationships, so do nothing 
                            break;
                        case MergeOption.OverwriteChanges:
                            if (relationshipEntry.State == EntityState.Added)
                            { 
                                relationshipEntry.AcceptChanges();
                            } 
                            else if (relationshipEntry.State == EntityState.Deleted) 
                            {
                                // targetEntry should always exist in this scenario because it would have 
                                // at least been created when the relationship entry was created
                                ObjectStateEntry targetEntry = manager.GetObjectStateEntry(targetKey);

                                // If the target entity is deleted, we don't want to bring the relationship entry back. 
                                if (targetEntry.State != EntityState.Deleted)
                                { 
                                    // If the targetEntry is a KeyEntry, there are no ends to fix up. 
                                    if (!targetEntry.IsKeyEntry)
                                    { 
                                        ObjectStateManager.AddEntityToCollectionOrReference(
                                            mergeOption,
                                            sourceEntity,
                                            sourceMember, 
                                            (IEntityWithRelationships)targetEntry.Entity,
                                            targetMember, 
                                            /*setIsLoaded*/ setIsLoaded, 
                                            /*relationshipAlreadyExists*/ true,
                                            /*inKeyEntryPromotion*/ false); 
                                    }
                                    relationshipEntry.RevertDelete();
                                }
                            } 
                            // else it's already Unchanged so we don't need to do anything
                            break; 
                        case MergeOption.PreserveChanges: 
                            if (relationshipEntry.State == EntityState.Added)
                            { 
                                // The client now matches the server, so just move the relationship to unchanged.
                                // If we don't do this and left the state Added, we will get a concurrency exception when trying to save
                                relationshipEntry.AcceptChanges();
                            } 
                            // else if it's already Unchanged we don't need to do anything
                            // else if it's Deleted we want to preserve that state so do nothing 
                            break; 
                        default:
                            Debug.Assert(false, "Unexpected MergeOption"); 
                            break;
                    }
                }
            } 

            return !needNewRelationship; 
        } 

        // Helper method to disconnect two related ends and detach their associated relationship entry 
        internal static void RemoveRelatedEndsAndDetachRelationship(ObjectStateEntry relationshipToRemove, bool setIsLoaded)
        {
            // If we are allowed to set the IsLoaded flag, then we can consider unloading these relationships
            if (setIsLoaded) 
            {
                // If the relationship needs to be deleted, then we should unload the related ends 
                UnloadReferenceRelatedEnds(relationshipToRemove); 
            }
 
            // Delete the relationship entry and disconnect the related ends
            if (relationshipToRemove.State != EntityState.Deleted)
            {
                relationshipToRemove.Delete(); 
            }
 
            // Detach the relationship entry 
            // Entries that were in the Added state prior to the Delete above will have already been Detached
            if (relationshipToRemove.State != EntityState.Detached) 
            {
                relationshipToRemove.AcceptChanges();
            }
        } 

        private static void UnloadReferenceRelatedEnds(ObjectStateEntry entry) 
        { 
            RelationshipEntry relationshipEntry = entry as RelationshipEntry;
            //Find two ends of the relationship 
            ObjectStateManager cache = relationshipEntry.ObjectStateManager;
            ReadOnlyMetadataCollection endMembers = relationshipEntry.Wrapper.AssociationEndMembers;

            UnloadReferenceRelatedEnds(cache, relationshipEntry, relationshipEntry.Wrapper.GetEntityKey(0), endMembers[1].Name); 
            UnloadReferenceRelatedEnds(cache, relationshipEntry, relationshipEntry.Wrapper.GetEntityKey(1), endMembers[0].Name);
        } 
 
        private static void UnloadReferenceRelatedEnds(ObjectStateManager cache, RelationshipEntry relationshipEntry, EntityKey sourceEntityKey, string targetRoleName)
        { 
            ObjectStateEntry entry = cache.GetObjectStateEntry(sourceEntityKey);
            IEntityWithRelationships entity = entry.Entity as IEntityWithRelationships;

            if (entity != null) 
            {
                EntityReference reference = entity.RelationshipManager.GetRelatedEnd(((AssociationSet)relationshipEntry.EntitySet).ElementType.FullName, targetRoleName) as EntityReference; 
                if (reference != null) 
                {
                    reference.SetIsLoaded(false); 
                }
            }
        }
 
        /// 
        /// Asserts that the EntityKey attached to the given entity 
        /// appropriately matches the given entity. 
        /// 
        /// the entity whose key must be verified 
        /// the entity set corresponding to the type of the given entity.
        [Conditional("DEBUG")]
        private void AssertKeyMatchesEntity(object entity, EntityKey entityKey, EntitySet entitySetForType)
        { 
#if DEBUG
            Debug.Assert(entity != null, "Cannot verify key for null entity."); 
 
            Debug.Assert((object)entityKey != null, "Cannot verify a null EntityKey.");
            Debug.Assert(!entityKey.IsTemporary, "Verifying a temporary EntityKey doesn't make sense because the key doesn't contain any values."); 
            Debug.Assert(entitySetForType != null, "Cannot verify against a null entity set.");

            EntitySet entitySetForKey = entityKey.GetEntitySet(this.MetadataWorkspace);
            Debug.Assert(entitySetForKey != null, "EntityKey.EntitySet should never be null."); 

            // Assert that the entity's key matches its type. 
            Debug.Assert(entitySetForType.Name == entitySetForKey.Name && 
                entitySetForType.EntityContainer.Name == entitySetForKey.EntityContainer.Name,
                "Entity's type indicates that belongs to a different the entity set than the one specified in its key."); 

            // Assert that the key values in the entity match the key values
            // within its EntityKey.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entity.GetType(), entitySetForType); 
            for (int i = 0; i < entitySetForKey.ElementType.KeyMembers.Count; ++i)
            { 
                EdmMember keyField = entitySetForKey.ElementType.KeyMembers[i]; 
                int ordinal = typeMetadata.GetOrdinalforCLayerMemberName(keyField.Name);
                Debug.Assert(ordinal >= 0, "Invalid ordinal returned for field '" + keyField.Name + "'"); 
                object entityValue = typeMetadata.Member(ordinal).GetValue(entity);
                object keyValue = entityKey.FindValueByName(keyField.Name);

                // Use object.Equals(), because key field values have to be simple types. 
                Debug.Assert(object.Equals(entityValue, keyValue),
                    "The entity cannot be added because the value of at least one key property does not match the corresponding value within its EntityKey."); 
            } 
#endif
        } 

        /// 
        /// Attach entity in unchanged state (skip Added state, don't create temp key)
        /// It is equal (but faster) to call AddEntry(); AcceptChanges(). 
        /// 
        ///  
        ///  
        /// 
        internal ObjectStateEntry AttachEntry(EntityKey entityKey, object dataObject, EntitySet entitySet, string argumentName) 
        {
            Debug.Assert(dataObject != null, "entity cannot be null.");
            Debug.Assert(entitySet != null, "entitySet must be non-null.");
            Debug.Assert(argumentName != null, "argumentName cannot be null."); 
            Debug.Assert(entityKey != null, "argumentName cannot be null.");
 
            // Get a StateManagerTypeMetadata for the entity type. 
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(dataObject.GetType(), entitySet);
 
            CheckKeyMatchesEntity(dataObject, entityKey, entitySet);

            // Create a cache entry.
            ObjectStateEntry newEntry = new EntityEntry(dataObject, entityKey, entitySet, this, typeMetadata, /*isAdded*/true); 

            // The property EntityKey on newEntry validates that the entry and the entity on the entry have the same key. 
            Debug.Assert(entityKey == newEntry.EntityKey, "newEntry.EntityKey should match entityKey"); 

            // In the constructor of Entry, state is set to Added, so set state manually to Unchanged. 
            newEntry.State = EntityState.Unchanged;

            // A entity is being attached.
            newEntry.AttachObjectStateManagerToEntity(); 
            AddEntityEntryToDictionary(newEntry, newEntry.State);
            // fire ColectionChanged event only when a new entity is added to cache 
            OnObjectStateManagerChanged(CollectionChangeAction.Add, newEntry.Entity); 

            return newEntry; 
        }

        /// 
        /// Checks that the EntityKey attached to the given entity 
        /// appropriately matches the given entity.
        /// NOTE: does the same as AssertKeyMatchesEntity(), but throws instead of calling Assert(). 
        ///  
        /// the entity whose key must be verified
        /// the entity set corresponding to the type of the given entity. 
        private void CheckKeyMatchesEntity(object entity, EntityKey entityKey, EntitySet entitySetForType)
        {
            Debug.Assert(entity != null, "Cannot verify key for null entity.");
 
            Debug.Assert((object)entityKey != null, "Cannot verify a null EntityKey.");
            Debug.Assert(!entityKey.IsTemporary, "Verifying a temporary EntityKey doesn't make sense because the key doesn't contain any values."); 
            Debug.Assert(entitySetForType != null, "Cannot verify against a null entity set."); 

            EntitySet entitySetForKey = entityKey.GetEntitySet(this.MetadataWorkspace); 
            if(entitySetForKey == null)
            {
                throw EntityUtil.InvalidKey();
            } 

            // Checks that the entity's key matches its type. 
            if (!(entitySetForType.Name == entitySetForKey.Name && 
                entitySetForType.EntityContainer.Name == entitySetForKey.EntityContainer.Name))
            { 
                throw EntityUtil.EntityTypeDoesntMatchEntitySetFromKey();
            }

            // Verify that the entity key contains the correct members for the entity set 
            entityKey.ValidateEntityKey(entitySetForKey);
 
            // Checks that the key values in the entity match the key values 
            // within its EntityKey.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entity.GetType(), entitySetForType); 
            for (int i = 0; i < entitySetForKey.ElementType.KeyMembers.Count; ++i)
            {
                EdmMember keyField = entitySetForKey.ElementType.KeyMembers[i];
                int ordinal = typeMetadata.GetOrdinalforCLayerMemberName(keyField.Name); 
                if (ordinal < 0)
                { 
                    throw EntityUtil.InvalidKey(); 
                }
 
                object entityValue = typeMetadata.Member(ordinal).GetValue(entity);
                object keyValue = entityKey.FindValueByName(keyField.Name);

                // Use object.Equals(), because key field values have to be simple types. 
                if (!object.Equals(entityValue, keyValue))
                { 
                    throw EntityUtil.KeyPropertyDoesntMatchValueInKey(); 
                }
            } 

            // NOTE: Called just to be sure that CheckKeyMatchesEntity() does the same as AssertKeyMatchesEntity().
            // AssertKeyMatchesEntity() is going to be removed in the future and CheckKeyMatchesEntity will be
            // used instead (in AddEntry() ). 
            AssertKeyMatchesEntity(entity, entityKey, entitySetForType);
        } 
 
        internal ObjectStateEntry AddNewRelation(RelationshipWrapper wrapper, EntityState desiredState)
        { 
            Debug.Assert(null == FindRelationship(wrapper), "relationship should not exist, caller verifies");

            RelationshipEntry entry = new RelationshipEntry(this, desiredState, wrapper);
            AddRelationshipEntryToDictionary(entry, desiredState); 
            AddRelationshipToLookup(entry);
            return entry; 
        } 

        internal ObjectStateEntry AddRelation(RelationshipWrapper wrapper, EntityState desiredState) 
        {
            Debug.Assert(EntityState.Added == desiredState ||        // result entry should be added or left alone
                         EntityState.Unchanged == desiredState ||    // result entry should be that state
                         EntityState.Deleted == desiredState,        // result entry should be in that state 
                         "unexpected state");
 
            ObjectStateEntry entry = FindRelationship(wrapper); 
            Debug.Assert(null == entry || (EntityState.Modified != entry.State), "relationship should never be modified");
 
            if (entry == null)
            {
                entry = AddNewRelation(wrapper, desiredState);
            } 
            else if (EntityState.Deleted != entry.State)
            { 
                // you can have a deleted and non-deleted relation between two entities 
                // SQL BU DT 449757: for now no-op in case if it exists. ideally need to throw
                if (EntityState.Unchanged == desiredState) 
                {
                    entry.AcceptChanges();
                }
                else if (EntityState.Deleted == desiredState) 
                {
                    entry.AcceptChanges(); 
                    entry.Delete(false); 
                }
                // else Added and leave entry alone 
            }
            else if (EntityState.Deleted != desiredState)
            {
                Debug.Assert(EntityState.Deleted == entry.State, "should be deleted state"); 
                entry.RevertDelete();
            } 
            // else entry already Deleted or if desired state is Added then left alone 

            Debug.Assert(desiredState == entry.State || 
                         EntityState.Added == desiredState,
                         "unexpected end state");
            return entry;
        } 

        ///  
        /// Adds the given relationship cache entry to the mapping from each of its endpoint keys. 
        /// 
        private void AddRelationshipToLookup(RelationshipEntry relationship) 
        {
            Debug.Assert(relationship != null, "relationship can't be null");
            Debug.Assert(relationship.IsRelationship, "The cache entry is expected to be a relationship cache entry.");
 
            AddRelationshipEndToLookup(relationship.Wrapper.Key0, relationship);
            if (!relationship.Wrapper.Key0.Equals(relationship.Wrapper.Key1)) 
            { 
                AddRelationshipEndToLookup(relationship.Wrapper.Key1, relationship);
            } 
        }

        /// 
        /// Adds the given relationship cache entry to the mapping from the given endpoint key. 
        /// 
        private void AddRelationshipEndToLookup(EntityKey key, RelationshipEntry relationship) 
        { 
            Debug.Assert(relationship.IsRelationship, "The cache entry is expected to be a relationship cache entry.");
            Debug.Assert(null != FindObjectStateEntry(key), "EntityEntry doesn't exist"); 

            EntityEntry entry = (EntityEntry)GetObjectStateEntry(key);
            Debug.Assert(key.Equals(entry.EntityKey), "EntityKey mismatch");
            entry.AddRelationshipEnd(relationship); 
        }
 
        ///  
        /// Deletes the given relationship cache entry from the mapping from each of its endpoint keys.
        ///  
        private void DeleteRelationshipFromLookup(RelationshipEntry relationship)
        {
            // The relationship is stored in the lookup indexed by both keys, so we need to remove it twice.
            DeleteRelationshipEndFromLookup(relationship.Wrapper.Key0, relationship); 
            if (!relationship.Wrapper.Key0.Equals(relationship.Wrapper.Key1))
            { 
                DeleteRelationshipEndFromLookup(relationship.Wrapper.Key1, relationship); 
            }
        } 

        /// 
        /// Deletes the given relationship cache entry from the mapping from the given endpoint key.
        ///  
        private void DeleteRelationshipEndFromLookup(EntityKey key, RelationshipEntry relationship)
        { 
            Debug.Assert(relationship.IsRelationship, "The cache entry is expected to be a relationship cache entry."); 
            Debug.Assert(relationship.State != EntityState.Detached, "Cannot remove a detached cache entry.");
            Debug.Assert(null != FindObjectStateEntry(key), "EntityEntry doesn't exist"); 

            EntityEntry entry = (EntityEntry)GetObjectStateEntry(key);
            Debug.Assert(key.Equals(entry.EntityKey), "EntityKey mismatch");
            entry.RemoveRelationshipEnd(relationship); 
        }
 
        internal ObjectStateEntry FindRelationship(RelationshipSet relationshipSet, 
                                                   KeyValuePair roleAndKey1,
                                                   KeyValuePair roleAndKey2) 
        {
            if ((null == (object)roleAndKey1.Value) || (null == (object)roleAndKey2.Value))
            {
                return null; 
            }
            return FindRelationship(new RelationshipWrapper((AssociationSet)relationshipSet, roleAndKey1, roleAndKey2)); 
        } 

        internal ObjectStateEntry FindRelationship(RelationshipWrapper relationshipWrapper) 
        {
            ObjectStateEntry entry = null;
            bool result = (((null != _unchangedRelationshipStore) && _unchangedRelationshipStore.TryGetValue(relationshipWrapper, out  entry)) ||
                           ((null != _deletedRelationshipStore) && _deletedRelationshipStore.TryGetValue(relationshipWrapper, out  entry)) || 
                           ((null != _addedRelationshipStore) && _addedRelationshipStore.TryGetValue(relationshipWrapper, out entry)));
            Debug.Assert(result == (null != entry), "found null entry"); 
            return entry; 
        }
 
        /// 
        /// DeleteRelationship
        /// 
        /// The deleted entry 
        internal ObjectStateEntry DeleteRelationship(RelationshipSet relationshipSet,
                                          KeyValuePair roleAndKey1, 
                                          KeyValuePair roleAndKey2) 
        {
            ObjectStateEntry entry = FindRelationship(relationshipSet, roleAndKey1, roleAndKey2); 
            if (entry != null)
            {
                entry.Delete(/*doFixup*/ false);
            } 
            return entry;
        } 
 

        ///  
        /// DeleteKeyEntry
        /// 
        internal void DeleteKeyEntry(ObjectStateEntry keyEntry)
        { 
            if (keyEntry != null && keyEntry.IsKeyEntry)
            { 
                ChangeState(keyEntry, keyEntry.State, EntityState.Detached); 
            }
        } 


        /// 
        /// Finds all relationships with the given key at one end. 
        /// 
        internal RelationshipEntry[] CopyOfRelationshipsByKey(EntityKey key) 
        { 
            return FindRelationshipsByKey(key).ToArray();
        } 

        /// 
        /// Finds all relationships with the given key at one end.
        /// Do not use the list to add elements 
        /// 
        internal EntityEntry.RelationshipEndEnumerable FindRelationshipsByKey(EntityKey key) 
        { 
            return new EntityEntry.RelationshipEndEnumerable((EntityEntry)FindObjectStateEntry(key));
        } 

        IEnumerable IEntityStateManager.FindRelationshipsByKey(EntityKey key)
        {
            return FindRelationshipsByKey(key); 
        }
 
        ///  
        /// Returns a non-null key for the given entity.
        /// Throws if the passed entity doesn't have a key attached. 
        /// Throws if the key is null.
        /// 
        /// An object with a key
        /// The key on the object 
        /// if key is null
        internal EntityKey GetEntityKey(object entity) 
        { 
            EntityKey key = null;
            if (!TryGetEntityKey(entity, out key)) //If key is null or missing 
            {
                throw EntityUtil.ObjectDoesNotHaveAKey(entity);
            }
            return key; 
        }
 
        ///  
        /// Retrieves an EntityKey from the given entity with relationships.
        /// If the entity implements, IEntity, the retrieve the key directly from the entity, 
        /// Else, if there's a context attached to the entity's relationship manager, retrieve
        /// the key from the contained ObjectStateManager
        ///
        /// Note: When this method is called with "owner" on a RelatedEnd. The RelatedEnd that has an owner will not 
        /// have a Context attached if the owner does not have a context attached. Hence, it's not necessary
        /// to check "this" to find a context. 
        ///  
        /// An object that implements the IEntityWithRelationships interface.
        ///  
        /// The EntityKey associated with the entity Returns null if the key doesn't exist.
        /// 
        internal static EntityKey FindKeyOnEntityWithRelationships(IEntityWithRelationships entity)
        { 
            IEntityWithKey entityWithKey = entity as IEntityWithKey;
            if (null != entityWithKey) 
            { 
                return entityWithKey.EntityKey;
            } 

            RelationshipManager relationshipManager = entity.RelationshipManager;
            if ((null != relationshipManager) && (null != relationshipManager.Context))
            { 
                return ObjectContext.FindEntityKey(entity, relationshipManager.Context);
            } 
            return null; 
        }
 
        //Verify that all entities in the _keylessEntityStore are also in the other dictionaries.
        //Verify that all the entries in the _keylessEntityStore don't implement IEntityWithKey.
        //Verify that there no entries in the other dictionaries that don't implement IEntityWithKey and aren't in _keylessEntityStore
        [ConditionalAttribute("DEBUG")] 
        private void ValidateKeylessEntityStore()
        { 
            // Future Enhancement : Check each entry in _keylessEntityStore to make sure it has a corresponding entry in one of the other stores. 
            if (null != _keylessEntityStore)
            { 
                foreach (ObjectStateEntry entry in _keylessEntityStore.Values)
                {
                    Debug.Assert(!(entry.Entity is IEntityWithKey), "_keylessEntityStore contains an entry that implement IEntityWithKey");
                    ObjectStateEntry entrya; 
                    bool result = false;
                    if (null != _addedEntityStore) 
                    { 
                        result = _addedEntityStore.TryGetValue(entry.EntityKey, out entrya);
                    } 
                    if (null != _modifiedEntityStore)
                    {
                        result |= _modifiedEntityStore.TryGetValue(entry.EntityKey, out entrya);
                    } 
                    if (null != _deletedEntityStore)
                    { 
                        result |= _deletedEntityStore.TryGetValue(entry.EntityKey, out entrya); 
                    }
                    if (null != _unchangedEntityStore) 
                    {
                        result |= _unchangedEntityStore.TryGetValue(entry.EntityKey, out entrya);
                    }
                    Debug.Assert(result, "entry in _keylessEntityStore doesn't exist in one of the other stores"); 
                }
            } 
 
            //Check each entry in the other stores to make sure that each non-IEntityWithKey entry is also in _keylessEntityStore
            Dictionary[] stores = { _unchangedEntityStore, _modifiedEntityStore, _addedEntityStore, _deletedEntityStore }; 
            foreach (Dictionary store in stores)
            {
                if (null != store)
                { 
                    foreach (ObjectStateEntry entry in store.Values)
                    { 
                        if (null != entry.Entity && //Skip span stub key entry 
                            !(entry.Entity is IEntityWithKey))
                        { 
                            ObjectStateEntry keylessEntry;
                            Debug.Assert(null != _keylessEntityStore, "There should be a store that keyless entries are in");
                            if (_keylessEntityStore.TryGetValue(entry.Entity, out keylessEntry))
                            { 
                                Debug.Assert(object.ReferenceEquals(entry, keylessEntry), "keylessEntry and entry from stores do not match");
                            } 
                            else 
                            {
                                Debug.Assert(false, "The entry containing an entity not implementing IEntityWithKey is not in the _keylessEntityStore"); 
                            }
                        }
                    }
                } 
            }
        } 
        ///  
        /// Find the ObjectStateEntry from _keylessEntityStore for an entity that doesn't implement IEntityWithKey.
        ///  
        /// 
        /// 
        private bool TryGetEntryFromKeylessStore(object entity, out ObjectStateEntry entryRef)
        { 
            Debug.Assert(!(entity is IEntityWithKey));
            EntityUtil.CheckArgumentNull(entity, "entity"); 
 
            ValidateKeylessEntityStore();
            entryRef = null; 
            if (null != _keylessEntityStore)
            {
                if (_keylessEntityStore.TryGetValue(entity, out entryRef))
                { 
                    return true;
                } 
            } 

            entryRef = null; 
            return false;
        }

        ///  
        /// Given an object, return the corresponding EntityKey.
        /// 
        ///  
        /// An object that is used by an entity.
        /// The key corresponding to the passed-in object. 
        /// 
        /// True if a key exists for the given object.
        /// False if key is null;
        /// False if the key does not exist for the entity (does not implement IEntity) 
        /// 
        /// entity is null 
        internal bool TryGetEntityKey(object entity, out EntityKey key) 
        {
            key = null; 
            EntityUtil.CheckArgumentNull(entity, "entity");

            IEntityWithKey entityWithKey = entity as IEntityWithKey;
            if (null != entityWithKey) //This entity implements IEntity. 
            {
                key = entityWithKey.EntityKey; 
            } 
            else //The passed entity does not implement IEntity.
            { 
                ObjectStateEntry entry;
                if (TryGetEntryFromKeylessStore(entity, out entry))
                {
                    key = entry.EntityKey; 
                }
            } 
            return (null != (object)key); 
        }
 
        /// 
        /// Returns all CacheEntries in the given state.
        /// 
        /// if EntityState.Detached flag is set in state 
        public IEnumerable GetObjectStateEntries(EntityState state)
        { 
            if ((EntityState.Detached & state) != 0) 
            {
                throw EntityUtil.DetachedObjectStateEntriesDoesNotExistInObjectStateManager(); 
            }
            return GetObjectStateEntriesInternal(state);
        }
 
        /// 
        /// Returns all CacheEntries in the given state. 
        ///  
        /// if EntityState.Detached flag is set in state
        IEnumerable IEntityStateManager.GetEntityStateEntries(EntityState state) 
        {
            Debug.Assert((EntityState.Detached & state) == 0, "Cannot get state entries for detached entities");
            foreach (ObjectStateEntry stateEntry in GetObjectStateEntriesInternal(state))
            { 
                yield return (IEntityStateEntry)stateEntry;
            } 
        } 

        internal int GetObjectStateEntriesCount(EntityState state) 
        {
            int size = 0;
            if ((EntityState.Added & state) != 0)
            { 
                size += ((null != _addedRelationshipStore) ? _addedRelationshipStore.Count : 0);
                size += ((null != _addedEntityStore) ? _addedEntityStore.Count : 0); 
            } 
            if ((EntityState.Modified & state) != 0)
            { 
                size += ((null != _modifiedEntityStore) ? _modifiedEntityStore.Count : 0);
            }
            if ((EntityState.Deleted & state) != 0)
            { 
                size += ((null != _deletedRelationshipStore) ? _deletedRelationshipStore.Count : 0);
                size += ((null != _deletedEntityStore) ? _deletedEntityStore.Count : 0); 
            } 
            if ((EntityState.Unchanged & state) != 0)
            { 
                size += ((null != _unchangedRelationshipStore) ? _unchangedRelationshipStore.Count : 0);
                size += ((null != _unchangedEntityStore) ? _unchangedEntityStore.Count : 0);
            }
            return size; 
        }
 
        private ObjectStateEntry[] GetObjectStateEntriesInternal(EntityState state) 
        {
            Debug.Assert((EntityState.Detached & state) == 0, "Cannot get state entries for detached entities"); 

            int size = GetObjectStateEntriesCount(state);
            ObjectStateEntry[] entries = new ObjectStateEntry[size];
 
            size = 0; // size is now used as an offset
            if (((EntityState.Added & state) != 0) && (null != _addedRelationshipStore)) 
            { 
                _addedRelationshipStore.Values.CopyTo(entries, size);
                size += _addedRelationshipStore.Count; 
            }
            if (((EntityState.Deleted & state) != 0) && (null != _deletedRelationshipStore))
            {
                _deletedRelationshipStore.Values.CopyTo(entries, size); 
                size += _deletedRelationshipStore.Count;
            } 
            if (((EntityState.Unchanged & state) != 0) && (null != _unchangedRelationshipStore)) 
            {
                _unchangedRelationshipStore.Values.CopyTo(entries, size); 
                size += _unchangedRelationshipStore.Count;
            }
            if (((EntityState.Added & state) != 0) && (null != _addedEntityStore))
            { 
                _addedEntityStore.Values.CopyTo(entries, size);
                size += _addedEntityStore.Count; 
            } 
            if (((EntityState.Modified & state) != 0) && (null != _modifiedEntityStore))
            { 
                _modifiedEntityStore.Values.CopyTo(entries, size);
                size += _modifiedEntityStore.Count;
            }
            if (((EntityState.Deleted & state) != 0) && (null != _deletedEntityStore)) 
            {
                _deletedEntityStore.Values.CopyTo(entries, size); 
                size += _deletedEntityStore.Count; 
            }
            if (((EntityState.Unchanged & state) != 0) && (null != _unchangedEntityStore)) 
            {
                _unchangedEntityStore.Values.CopyTo(entries, size);
                size += _unchangedEntityStore.Count;
            } 
            return entries;
        } 
 
        #region temporary (added state) to permanent (deleted, modified, unchanged state) EntityKey fixup
 
        /// 
        /// Performs key-fixup on the given entry, by creating a (permanent) EntityKey
        /// based on the current key values within the associated entity and fixing up
        /// all associated relationship entries. 
        /// 
        ///  
        /// Will promote EntityEntry.IsKeyEntry and leave in _unchangedStore 
        /// otherwise will move EntityEntry from _addedStore to _unchangedStore.
        ///  
        internal void FixupKey(EntityEntry entry)
        {
            Debug.Assert(entry != null, "entry should not be null.");
            Debug.Assert(entry.State == EntityState.Added, "Cannot do key fixup for an entry not in the Added state."); 
            Debug.Assert(entry.Entity != null, "must have entity, can't be entity stub in added state");
            Debug.Assert(!entry.IsRelationship, "Shouldn't pass relationship entries to FixupKey()"); 
 
            EntityKey oldKey = entry.EntityKey;
            Debug.Assert(entry == _addedEntityStore[oldKey], "not the same EntityEntry"); 
            Debug.Assert((object)oldKey != null, "Cannot fixup a cache entry with a null key.");
            Debug.Assert(oldKey.IsTemporary, "Cannot fixup an entry with a non-temporary key.");
            Debug.Assert(null != _addedEntityStore, "missing added store");
 
            // Construct an EntityKey based on the current, fixed-up values of the entry.
            EntityKey newKey = new EntityKey((EntitySet)entry.EntitySet, (IExtendedDataRecord)entry.CurrentValues); 
 
            ObjectStateEntry existingEntry = FindObjectStateEntry(newKey);
            if (existingEntry != null) 
            {
                if (!existingEntry.IsKeyEntry)
                {
                    // If the fixed-up key conflicts with an existing entry, we throw. 
                    throw EntityUtil.CannotFixUpKeyToExistingValues();
                } 
                newKey = existingEntry.EntityKey; // reuse existing reference 
            }
 
            // remove the relationships based on the temporary key
            RelationshipEntry[] relationshipEnds = entry.GetRelationshipEnds().ToArray();
            foreach (RelationshipEntry relationshipEntry in relationshipEnds)
            { 
                RemoveObjectStateEntryFromDictionary(relationshipEntry, relationshipEntry.State);
            } 
 
            // Remove ObjectStateEntry with old Key and add it back or promote with new key.
            RemoveObjectStateEntryFromDictionary(entry, EntityState.Added); 

            // This is the only scenario where we are allowed to set the EntityKey if it's already non-null
            // If entry.EntityKey is IEntityWithKey, user code will be called
            ResetEntityKey(entry, newKey); 

            // Fixup all relationships for which this key was a participant. 
            entry.UpdateRelationshipEnds(oldKey, (EntityEntry)existingEntry); 

            // add all the relationships back on the new entity key 
            foreach (RelationshipEntry relationshipEntry in relationshipEnds)
            {
                AddRelationshipEntryToDictionary(relationshipEntry, relationshipEntry.State);
            } 

            // Now promote the key entry to a full entry by adding entities to the related ends 
            if (existingEntry != null) 
            {
                // two ObjectStateEntry exist for same newKey, the entity stub must exist in unchanged state 
                Debug.Assert(existingEntry.State == EntityState.Unchanged, "entity stub must be in unchanged state");
                Debug.Assert(existingEntry.IsKeyEntry, "exising entry must be a key entry to promote");
                Debug.Assert(Object.ReferenceEquals(newKey, existingEntry.EntityKey), "should be same key reference");
                PromoteKeyEntry(existingEntry, entry.Entity, null, true, /*setIsLoaded*/ false, /*keyEntryInitialized*/ false, "AcceptChanges"); 

                // leave the entity stub in the unchanged state 
                // the existing entity stub wins 
            }
            else 
            {
                // change the state to "Unchanged"
                AddEntityEntryToDictionary(entry, EntityState.Unchanged);
            } 

            Debug.Assert((null == _addedEntityStore) || !_addedEntityStore.ContainsKey(oldKey), "EntityEntry exists with OldKey"); 
            Debug.Assert((null != _unchangedEntityStore) && _unchangedEntityStore.ContainsKey(newKey), "EntityEntry does not exist with NewKey"); 

            // FEATURE_CHANGE: once we support equality constraints (SQL PT DB 300002154), do recursive fixup. 
        }

        /// 
        /// Resets the EntityKey for this entry.  This method is called 
        /// as part of temporary key fixup. This method is necessary because it is the only
        /// scenario where we allow a new value to be set on a non-null EntityKey. This 
        /// is the only place where we should be setting and clearing _inRelationshipFixup. 
        /// 
        private void ResetEntityKey(ObjectStateEntry entry, EntityKey value) 
        {
            Debug.Assert((object)entry.EntityKey != null, "Cannot reset an entry's key if it hasn't been set in the first place.");
            Debug.Assert(!_inRelationshipFixup, "already _inRelationshipFixup");
            Debug.Assert(entry.EntityKey.IsTemporary, "Cannot reset an entry with a non-temporary key."); 
            Debug.Assert(!entry.EntityKey.Equals(value), "the keys should not be equal");
 
            IEntityWithKey entityWithKey = entry.Entity as IEntityWithKey; 
            if (null != entityWithKey)
            { 
                EntityKey entityKey = entityWithKey.EntityKey;
                if (entityKey == null || value.Equals(entityKey))
                {
                    throw EntityUtil.AcceptChangesEntityKeyIsNotValid(); 
                }
                try 
                { 
                    _inRelationshipFixup = true;
                    entityWithKey.EntityKey = value; // user will have control 
                    EntityUtil.CheckEntityKeysMatch(entityWithKey, value);
                }
                finally
                { 
                    _inRelationshipFixup = false;
                } 
            } 

            // Keeping the entity and entry keys in [....]. 
            entry.EntityKey = value;

            //Internally, entry.EntityKey asserts that entry._entityKey and entityWithKey.EntityKey are equal.
            Debug.Assert(value == entry.EntityKey, "The new key was not set onto the entry correctly"); 
        }
 
        #endregion 

        ///  
        /// Retrieve the corresponding IEntityStateEntry for the given EntityKey.
        /// 
        /// if key is null
        /// if key is not found 
        IEntityStateEntry IEntityStateManager.GetEntityStateEntry(EntityKey key)
        { 
            return (IEntityStateEntry)GetObjectStateEntry(key); 
        }
 
        /// 
        /// Retrieve the corresponding ObjectStateEntry for the given EntityKey.
        /// 
        /// if key is null 
        /// if key is not found
        public ObjectStateEntry GetObjectStateEntry(EntityKey key) 
        { 
            ObjectStateEntry entry;
            if (!TryGetObjectStateEntry(key, out entry)) 
            {
                throw EntityUtil.NoEntryExistForEntityKey();
            }
            return entry; 
        }
 
        ///  
        /// Given an entity, of type object, return the corresponding ObjectStateEntry.
        ///  
        /// 
        /// The corresonding ObjectStateEntry for this object.
        public ObjectStateEntry GetObjectStateEntry(object entity)
        { 
            ObjectStateEntry entry;
            if (!TryGetObjectStateEntry(entity, out entry)) 
            { 
                throw EntityUtil.NoEntryExistsForObject(entity);
            } 
            return entry;
        }

        ///  
        /// Retrieve the corresponding ObjectStateEntry for the given object.
        ///  
        ///  
        /// 
        /// true if the corresponding ObjectStateEntry was found 
        public bool TryGetObjectStateEntry(object entity, out ObjectStateEntry entry)
        {
            entry = null;
            EntityUtil.CheckArgumentNull(entity, "entity"); 
            entry = FindObjectStateEntry(entity);
 
            return entry != null; 
        }
 
        /// 
        /// Retrieve the corresponding IEntityStateEntry for the given EntityKey.
        /// 
        /// true if the corresponding IEntityStateEntry was found 
        /// if key is null
        bool IEntityStateManager.TryGetEntityStateEntry(EntityKey key, out IEntityStateEntry entry) 
        { 
            // Because the passed in IEntityStateEntry reference isn't necessarily an
            // ObjectStateEntry, we have to declare our own local copy, use it for the outparam of 
            // TryGetObjectStateEntry, and then set it onto our outparam if we successfully find
            // something (at that point we know we can cast to IEntityStateEntry), but we just can't
            // cast in the other direction.
            ObjectStateEntry objectStateEntry; 
            bool result = TryGetObjectStateEntry(key, out objectStateEntry);
            entry = (IEntityStateEntry)objectStateEntry; 
            return result; 
        }
 
        /// 
        /// Retrieve the corresponding ObjectStateEntry for the given EntityKey.
        /// 
        /// true if the corresponding ObjectStateEntry was found 
        /// if key is null
        public bool TryGetObjectStateEntry(EntityKey key, out ObjectStateEntry entry) 
        { 
            entry = null; // must set before checking for null key
            EntityUtil.CheckArgumentNull(key, "key"); 
            bool result;
            if (key.IsTemporary)
            {   // only temporary keys exist in the added state
                result = ((null != _addedEntityStore) && _addedEntityStore.TryGetValue(key, out entry)); 
            }
            else 
            {   // temporary keys do not exist in the unchanged, modified, deleted states. 
                result = (((null != _unchangedEntityStore) && _unchangedEntityStore.TryGetValue(key, out entry)) ||
                          ((null != _modifiedEntityStore) && _modifiedEntityStore.TryGetValue(key, out entry)) || 
                          ((null != _deletedEntityStore) && _deletedEntityStore.TryGetValue(key, out entry)));
            }
            Debug.Assert(result == (null != entry), "result and entry mismatch");
            return result; 
        }
 
        /// Retrieve the corresponding ObjectStateEntry for the given EntityKey. 
        internal ObjectStateEntry FindObjectStateEntry(EntityKey key)
        { 
            ObjectStateEntry entry = null;
            if (null != (object)key)
            {
                TryGetObjectStateEntry(key, out entry); 
            }
            return entry; 
        } 

        ///  
        /// Retrieve the corresponding ObjectStateEntry for the given entity.
        /// Returns null if key is unavailable or passed entity is null.
        /// 
        internal ObjectStateEntry FindObjectStateEntry(object entity) 
        {
            ObjectStateEntry entry = null; 
            IEntityWithKey entityWithKey = entity as IEntityWithKey; 

            if (entityWithKey != null) 
            {
                EntityKey entityEntityKey = entityWithKey.EntityKey;
                if (null != (object)entityEntityKey)
                { 
                    TryGetObjectStateEntry(entityEntityKey, out entry);
                } 
            } 
            else
            { 
                TryGetEntryFromKeylessStore(entity, out entry);
            }

            // If entity is detached, then entry.Entity won't have the same object reference. 
            // This can happen if the same entity is loaded with, then without, tracking
            // SQL BU Defect Tracking 520058 
            if (entry != null && !object.ReferenceEquals(entity, entry.Entity)) 
            {
                entry = null; 
            }

            return entry;
        } 

        internal void ChangeState(ObjectStateEntry entry, EntityState oldState, EntityState newState) 
        { 
            bool fireEvent = (!entry.IsRelationship && !entry.IsKeyEntry);  // do not fire event for relationship
            if (newState == EntityState.Detached) 
            {
                // If we're transitioning to detached, completely remove all traces
                // of the entry.
                if (entry.IsRelationship) 
                {
                    DeleteRelationshipFromLookup((RelationshipEntry)entry); 
                } 
                else
                { 
                    // SQLBU 508278: Object State Manager should not allow "dangling" relationships to stay in the state manager.
                    // Remove potential dangling relationships
                    Debug.Assert((object)entry.EntityKey != null, "attached entry must have a key");
                    foreach (ObjectStateEntry relationshipEntry in CopyOfRelationshipsByKey(entry.EntityKey)) 
                    {
                        ChangeState(relationshipEntry, relationshipEntry.State, EntityState.Detached); 
                    } 
                }
 
                // delay removal until RelationshipEnds is done
                RemoveObjectStateEntryFromDictionary(entry, oldState);

                object _entity = entry.Entity; // we have to cache the entity before detaching it totaly so we can fire event 
                entry.Reset();
                if (fireEvent && _entity != null) 
                { 
                    // first notify the view
                    OnEntityDeleted(CollectionChangeAction.Remove, _entity); 
                    OnObjectStateManagerChanged(CollectionChangeAction.Remove, _entity);
                }
            }
            else 
            {
                RemoveObjectStateEntryFromDictionary(entry, oldState); 
 
                if (entry.IsRelationship)
                { 
                    // If we're transitioning to something other than detached, add the
                    // entry to the appropriate dictionary.
                    AddRelationshipEntryToDictionary(entry, newState);
                } 
                else
                { 
                    // If we're transitioning to something other than detached, add the 
                    // entry to the appropriate dictionary.
                    AddEntityEntryToDictionary(entry, newState); 
                }
            }
            if (fireEvent && newState == EntityState.Deleted)
            { 
                // fire collectionChanged event only when an entity is being deleted (this includes deleting an added entity which becomes detached)
                OnEntityDeleted(CollectionChangeAction.Remove, entry.Entity); 
                OnObjectStateManagerChanged(CollectionChangeAction.Remove, entry.Entity); 
            }
        } 

        private void AddRelationshipEntryToDictionary(ObjectStateEntry entry, EntityState state)
        {
            Debug.Assert(entry.IsRelationship, "expecting IsRelationship"); 
            Debug.Assert(null != entry.Wrapper, "null RelationshipWrapper");
            Debug.Assert(entry is RelationshipEntry, "!RelationshipEntry"); 
 
            Dictionary dictionaryToAdd = null;
            switch (state) 
            {
                case EntityState.Unchanged:
                    if (null == _unchangedRelationshipStore)
                    { 
                        _unchangedRelationshipStore = new Dictionary();
                    } 
                    dictionaryToAdd = _unchangedRelationshipStore; 
                    break;
                case EntityState.Added: 
                    if (null == _addedRelationshipStore)
                    {
                        _addedRelationshipStore = new Dictionary();
                    } 
                    dictionaryToAdd = _addedRelationshipStore;
                    break; 
                case EntityState.Deleted: 
                    if (null == _deletedRelationshipStore)
                    { 
                        _deletedRelationshipStore = new Dictionary();
                    }
                    dictionaryToAdd = _deletedRelationshipStore;
                    break; 
                default:
                    Debug.Assert(false, "Invalid state."); 
                    break; 
            }
            Debug.Assert(dictionaryToAdd != null, "Couldn't find the correct relationship dictionary based on entity state."); 
            dictionaryToAdd.Add(entry.Wrapper, entry);
        }

        private void AddEntityEntryToDictionary(ObjectStateEntry entry, EntityState state) 
        {
            Debug.Assert(!entry.IsRelationship, "not expecting IsRelationship"); 
            Debug.Assert(null != (object)entry.EntityKey, "missing EntityKey"); 
            Debug.Assert(entry is EntityEntry, "!EntityEntry");
 
            Dictionary dictionaryToAdd = null;
            switch (state)
            {
                case EntityState.Unchanged: 
                    if (null == _unchangedEntityStore)
                    { 
                        _unchangedEntityStore = new Dictionary(); 
                    }
                    dictionaryToAdd = _unchangedEntityStore; 
                    Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Unchanged state");
                    break;
                case EntityState.Added:
                    if (null == _addedEntityStore) 
                    {
                        _addedEntityStore = new Dictionary(); 
                    } 
                    dictionaryToAdd = _addedEntityStore;
                    Debug.Assert(entry.EntityKey.IsTemporary, "adding non-temporary entity key into Added state"); 
                    break;
                case EntityState.Deleted:
                    if (null == _deletedEntityStore)
                    { 
                        _deletedEntityStore = new Dictionary();
                    } 
                    dictionaryToAdd = _deletedEntityStore; 
                    Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Deleted state");
                    break; 
                case EntityState.Modified:
                    if (null == _modifiedEntityStore)
                    {
                        _modifiedEntityStore = new Dictionary(); 
                    }
                    dictionaryToAdd = _modifiedEntityStore; 
                    Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Modified state"); 
                    break;
                default: 
                    Debug.Assert(false, "Invalid state.");
                    break;

 
            }
            Debug.Assert(dictionaryToAdd != null, "Couldn't find the correct entity dictionary based on entity state."); 
            dictionaryToAdd.Add(entry.EntityKey, entry); 
            AddEntryToKeylessStore(entry.Entity, entry);
 
        }

        private void AddEntryToKeylessStore(object entity, ObjectStateEntry entry)
        { 
            // Add an entry that doesn't implement IEntityWithKey to the keyless lookup.
            // It is used to lookup ObjectStateEntries when all we have is an entity reference. 
            if (null != entry.Entity && !(entry.Entity is IEntityWithKey)) 
            {
                if (null == _keylessEntityStore) 
                {
                    _keylessEntityStore = new Dictionary(new ObjectReferenceEqualityComparer());
                }
                if (!_keylessEntityStore.ContainsKey(entry.Entity)) 
                {
                    _keylessEntityStore.Add(entry.Entity, entry); 
                } 
            }
        } 

        /// 
        /// Removes the given cache entry from the appropriate dictionary, based on
        /// the given state and whether or not the entry represents a relationship. 
        /// 
        private void RemoveObjectStateEntryFromDictionary(ObjectStateEntry entry, EntityState state) 
        { 
            // Determine the appropriate dictionary from which to remove the entry.
            if (entry.IsRelationship) 
            {
                Dictionary dictionaryContainingEntry = null;
                switch (state)
                { 
                    case EntityState.Unchanged:
                        dictionaryContainingEntry = _unchangedRelationshipStore; 
                        break; 
                    case EntityState.Added:
                        dictionaryContainingEntry = _addedRelationshipStore; 
                        break;
                    case EntityState.Deleted:
                        dictionaryContainingEntry = _deletedRelationshipStore;
                        break; 
                    default:
                        Debug.Assert(false, "Invalid state."); 
                        break; 
                }
                Debug.Assert(dictionaryContainingEntry != null, "Couldn't find the correct relationship dictionary based on entity state."); 

                bool result = dictionaryContainingEntry.Remove(entry.Wrapper);
                Debug.Assert(result, "The correct relationship dictionary based on entity state doesn't contain the entry.");
 
                if (0 == dictionaryContainingEntry.Count)
                {   // reduce unused dictionary capacity 
                    switch (state) 
                    {
                        case EntityState.Unchanged: 
                            _unchangedRelationshipStore = null;
                            break;
                        case EntityState.Added:
                            _addedRelationshipStore = null; 
                            break;
                        case EntityState.Deleted: 
                            _deletedRelationshipStore = null; 
                            break;
                    } 
                }
            }
            else
            { 
                Dictionary dictionaryContainingEntry = null;
                switch (state) 
                { 
                    case EntityState.Unchanged:
                        dictionaryContainingEntry = _unchangedEntityStore; 
                        break;
                    case EntityState.Added:
                        dictionaryContainingEntry = _addedEntityStore;
                        break; 
                    case EntityState.Deleted:
                        dictionaryContainingEntry = _deletedEntityStore; 
                        break; 
                    case EntityState.Modified:
                        dictionaryContainingEntry = _modifiedEntityStore; 
                        break;
                    default:
                        Debug.Assert(false, "Invalid state.");
                        break; 
                }
                Debug.Assert(dictionaryContainingEntry != null, "Couldn't find the correct entity dictionary based on entity state."); 
 
                bool result = dictionaryContainingEntry.Remove(entry.EntityKey);
                Debug.Assert(result, "The correct entity dictionary based on entity state doesn't contain the entry."); 
                RemoveEntryFromKeylessStore(entry.Entity);

                if (0 == dictionaryContainingEntry.Count)
                {   // reduce unused dictionary capacity 
                    switch (state)
                    { 
                        case EntityState.Unchanged: 
                            _unchangedEntityStore = null;
                            break; 
                        case EntityState.Added:
                            _addedEntityStore = null;
                            break;
                        case EntityState.Deleted: 
                            _deletedEntityStore = null;
                            break; 
                        case EntityState.Modified: 
                            _modifiedEntityStore = null;
                            break; 
                    }
                }
            }
        } 

        internal void RemoveEntryFromKeylessStore(object entity) 
        { 
            // Remove and entry from the store containing entities not implementing IEntityWithKey
            if (null != entity && !(entity is IEntityWithKey)) 
            {
                bool keylessResult = _keylessEntityStore.Remove(entity);
                Debug.Assert(keylessResult, "The prescribed entry does not exist in _keylessEntityStore");
            } 
        }
 
 

        ///  
        /// If a corresponding StateManagerTypeMetadata exists, it is returned.
        /// Otherwise, a StateManagerTypeMetadata is created and cached.
        /// 
        internal StateManagerTypeMetadata GetOrAddStateManagerTypeMetadata(Type entityType, EntitySet entitySet) 
        {
            Debug.Assert(entityType != null, "entityType cannot be null."); 
            Debug.Assert(entitySet != null, "must have entitySet to correctly qualify Type"); 

            StateManagerTypeMetadata typeMetadata; 
            if (!_metadataMapping.TryGetValue(new EntitySetQualifiedType(entityType, entitySet), out typeMetadata))
            {
                // GetMap doesn't have a mechanism to qualify identity with EntityContainerName
                // This is unimportant until each EntityContainer can have its own ObjectTypeMapping. 
                typeMetadata = AddStateManagerTypeMetadata(entitySet, (ObjectTypeMapping)
                    MetadataWorkspace.GetMap(entityType.FullName, DataSpace.OSpace, DataSpace.OCSpace)); 
            } 
            return typeMetadata;
        } 

        /// 
        /// If a corresponding StateManagerTypeMetadata exists, it is returned.
        /// Otherwise, a StateManagerTypeMetadata is created and cached. 
        /// 
        internal StateManagerTypeMetadata GetOrAddStateManagerTypeMetadata(EdmType edmType) 
        { 
            Debug.Assert(edmType != null, "edmType cannot be null.");
            Debug.Assert(Helper.IsEntityType(edmType) || 
                         Helper.IsComplexType(edmType),
                         "only expecting ComplexType or EntityType");

            StateManagerTypeMetadata typeMetadata; 
            if (!_metadataStore.TryGetValue(edmType, out typeMetadata))
            { 
                typeMetadata = AddStateManagerTypeMetadata(edmType, (ObjectTypeMapping) 
                    MetadataWorkspace.GetMap(edmType, DataSpace.OCSpace));
            } 
            return typeMetadata;
        }

        ///  
        /// Creates an instance of StateManagerTypeMetadata from the given EdmType and ObjectMapping,
        /// and stores it in the metadata cache.  The new instance is returned. 
        ///  
        private StateManagerTypeMetadata AddStateManagerTypeMetadata(EntitySet entitySet, ObjectTypeMapping mapping)
        { 
            Debug.Assert(null != entitySet, "null entitySet");
            Debug.Assert(null != mapping, "null mapping");

            EdmType edmType = mapping.EdmType; 
            Debug.Assert(Helper.IsEntityType(edmType) ||
                         Helper.IsComplexType(edmType), 
                         "not Entity or complex type"); 

            StateManagerTypeMetadata typeMetadata; 
            if (!_metadataStore.TryGetValue(edmType, out typeMetadata))
            {
                typeMetadata = new StateManagerTypeMetadata(edmType, mapping);
                _metadataStore.Add(edmType, typeMetadata); 
            }
            _metadataMapping.Add(new EntitySetQualifiedType(mapping.ClrType.ClrType, entitySet), typeMetadata); 
            return typeMetadata; 
        }
 
        private StateManagerTypeMetadata AddStateManagerTypeMetadata(EdmType edmType, ObjectTypeMapping mapping)
        {
            Debug.Assert(null != edmType, "null EdmType");
            Debug.Assert(Helper.IsEntityType(edmType) || 
                         Helper.IsComplexType(edmType),
                         "not Entity or complex type"); 
 
            StateManagerTypeMetadata typeMetadata = new StateManagerTypeMetadata(edmType, mapping);
            _metadataStore.Add(edmType, typeMetadata); 
            return typeMetadata;
        }

        // Methods and properties used by recovery code in ObjectContext.AttachTo() 
        internal void BeginAttachTracking()
        { 
            Debug.Assert(this._promotedRelationships == null && this._promotedKeyEntries == null, 
                "recovery collections should be null");
 
            _promotedRelationships = new Dictionary>();
            _promotedKeyEntries = new Dictionary();
        }
 
        internal void EndAttachTracking()
        { 
            Debug.Assert(this._promotedRelationships != null && this._promotedKeyEntries != null, 
                "recovery collections should be not null");
 
            _promotedRelationships = null;
            _promotedKeyEntries = null;
        }
 
        internal bool IsAttachTracking
        { 
            get 
            {
                Debug.Assert((this._promotedRelationships == null) == (this._promotedKeyEntries == null), 
                    "state of both recovery collections should be the same");
                return this._promotedRelationships != null;
            }
        } 
    }
 
    internal sealed class ObjectReferenceEqualityComparer : IEqualityComparer 
    {
        bool IEqualityComparer.Equals(object x, object y) 
        {
            return (Object.ReferenceEquals(x,y));
        }
 
        int IEqualityComparer.GetHashCode(object obj)
        { 
            return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); 
        }
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 
//          DEV notes
//   1- I should never need to set/get any value in terms of clayer 
//
//
//
//--------------------------------------------------------------------- 
using System;
using System.Data; 
using System.Collections.Generic; 
using System.Collections;
using System.ComponentModel; 
using System.Data.Common;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.Data.Objects.DataClasses; 
using System.Diagnostics;
using System.Globalization; 
using System.Reflection; 
using System.Text;
using System.Data.Common.Utils; 
using System.Data.Objects.Internal;

namespace System.Data.Objects
{ 
    /// 
    /// implementation of ObjectStateManager class 
    ///  
    public class ObjectStateManager : IEntityStateManager
    { 
        // dictionaries (one for each entity state) that store cache entries that represent entities
        // these are only non-null when there is an entity in respective state, must always check for null before using
        private Dictionary _addedEntityStore;
        private Dictionary _modifiedEntityStore; 
        private Dictionary _deletedEntityStore;
        private Dictionary _unchangedEntityStore; 
        private Dictionary _keylessEntityStore; 

        // dictionaries (one for each entity state) that store cache entries that represent relationships 
        // these are only non-null when there is an relationship in respective state, must always check for null before using
        private Dictionary _addedRelationshipStore;
        private Dictionary _deletedRelationshipStore;
        private Dictionary _unchangedRelationshipStore; 

        // mapping from EdmType or EntitySetQualifiedType to StateManagerTypeMetadata 
        private readonly Dictionary _metadataStore; 
        private readonly Dictionary _metadataMapping;
 
        private readonly MetadataWorkspace _metadataWorkspace;

        // delegate for notifying changs in collection
        private CollectionChangeEventHandler onObjectStateManagerChangedDelegate; 
        private CollectionChangeEventHandler onEntityDeletedDelegate;
 
        // Flag to indicate if we are in the middle of relationship fixup. 
        // This is set and cleared only during ResetEntityKey, because only in that case
        // do we allow setting a value on a non-null EntityKey property 
        private bool _inRelationshipFixup;

        private ComplexTypeMaterializer _complexTypeMaterializer; // materializer instance that can be used to create complex types with just a metadata workspace
 
        // Dictionary used to recovery after exception in ObjectContext.AttachTo()
        private Dictionary> _promotedRelationships; 
        internal Dictionary> PromotedRelationships 
        {
            get { return _promotedRelationships; } 
        }

        // Dictionary used to recovery after exception in ObjectContext.AttachTo()
        private Dictionary _promotedKeyEntries; 
        internal Dictionary PromotedKeyEntries
        { 
            get { return _promotedKeyEntries; } 
        }
 
        #region Private Fields for ObjectStateEntry change tracking

        /// 
        /// The object on which the change was made, could be an entity or a complex object 
        /// Also see comments for _changingOldValue
        ///  
        private object _changingObject; 

        ///  
        /// _changingMember and _changingEntityMember should be the same for changes to
        /// top-level entity properties. They are only different for complex members.
        /// Also see comments for _changingOldValue.
        ///  
        private string _changingMember;
 
        ///  
        /// The top-level entity property that is changing. This could be the actual property
        /// that is changing, or the change could be occurring one or more levels down in the hierarchy 
        /// on a complex object.
        /// Also see comments for _changingOldValue
        /// 
        private string _changingEntityMember; 

        ///  
        /// EntityState of the entry during the changing notification. Used to detect 
        /// if the state has changed between the changing and changed notifications, which we do not allow.
        /// Also see comments for _changingOldValue 
        /// 
        private EntityState _changingState;

        ///  
        /// True if we are in a state where original values are to be stored during a changed notification
        /// This flags gets set during the changing notification and is used during changed to determine 
        /// if we need to save to original values or not. 
        /// Also see comments for _changingOldValue
        private bool _saveOriginalValues; 

        /// 
        /// Cache entity property/changing object/original value here between changing and changed events
        ///  
        /// 
        /// Only single threading is supported and changing/changed calls cannot be nested. Consecutive calls to Changing 
        /// overwrite previously cached values. Calls to Changed must have been preceded by a Changing call on the same property 
        ///
        /// a) user starts property value change with a call to 
        ///    IEntityChangeTracker.EntityMemberChanging or IEntityChangeTracker.EntityComplexMemberChanging
        /// b) The public interface methods call EntityValueChanging, which caches the original value
        /// c) new property value is stored on object
        /// d) users completes the property value change with a call to 
        ///    IEntityChangeTracker.EntityMemberChanged or IEntityChangeTracker.EntityComplexMemberChanged
        /// e} The public interface methods call EntityValueChanged, which saves the cached value in the original values record 
        ///  
        private object _changingOldValue;
 
        #endregion

        /// 
        /// ObjectStateManager ctor. 
        /// 
        ///  
        public ObjectStateManager(MetadataWorkspace metadataWorkspace) 
        {
            EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace"); 
            _metadataWorkspace = metadataWorkspace;

            _metadataStore = new Dictionary();
            _metadataMapping = new Dictionary(EntitySetQualifiedType.EqualityComparer); 
        }
 
        #region Internal Properties for ObjectStateEntry change tracking 

        internal object ChangingObject 
        {
            get { return _changingObject; }
            set { _changingObject = value; }
        } 

        internal string ChangingEntityMember 
        { 
            get { return _changingEntityMember; }
            set { _changingEntityMember = value; } 
        }

        internal string ChangingMember
        { 
            get { return _changingMember; }
            set { _changingMember = value; } 
        } 

        internal EntityState ChangingState 
        {
            get { return _changingState; }
            set { _changingState = value; }
        } 

        internal bool SaveOriginalValues 
        { 
            get { return _saveOriginalValues; }
            set { _saveOriginalValues = value; } 
        }

        internal object ChangingOldValue
        { 
            get { return _changingOldValue; }
            set { _changingOldValue = value; } 
        } 

        // Used by ObjectStateEntry to determine if it's safe to set a value 
        // on a non-null IEntity.EntityKey property
        internal bool InRelationshipFixup
        {
            get { return _inRelationshipFixup; } 
        }
 
        internal ComplexTypeMaterializer ComplexTypeMaterializer 
        {
            get 
            {
                if (_complexTypeMaterializer == null)
                {
                    _complexTypeMaterializer = new ComplexTypeMaterializer(this.MetadataWorkspace); 
                }
                return _complexTypeMaterializer; 
            } 
        }
 
        #endregion


        ///  
        /// MetadataWorkspace property
        ///  
        /// MetadataWorkspace 
        public MetadataWorkspace MetadataWorkspace
        { 
            get
            {
                return _metadataWorkspace;
            } 
        }
 
        #region events ObjectStateManagerChanged / EntityDeleted 
        /// 
        /// Event to notify changes in the collection. 
        /// 
        public event CollectionChangeEventHandler ObjectStateManagerChanged
        {
            add 
            {
                onObjectStateManagerChangedDelegate += value; 
            } 
            remove
            { 
                onObjectStateManagerChangedDelegate -= value;
            }
        }
 
        internal event CollectionChangeEventHandler EntityDeleted
        { 
            add 
            {
                onEntityDeletedDelegate += value; 
            }
            remove
            {
                onEntityDeletedDelegate -= value; 
            }
        } 
 
        internal void OnObjectStateManagerChanged(CollectionChangeAction action, object obj)
        { 
            if (onObjectStateManagerChangedDelegate != null)
            {
                onObjectStateManagerChangedDelegate(this, new CollectionChangeEventArgs(action, obj));
            } 
        }
 
        private void OnEntityDeleted(CollectionChangeAction action, object obj) 
        {
            if (onEntityDeletedDelegate != null) 
            {
                onEntityDeletedDelegate(this, new CollectionChangeEventArgs(action, obj));
            }
        } 
        #endregion
 
        ///  
        /// Adds an object stub to the cache.
        ///  
        /// the key of the object to add
        /// the entity set of the given object
        ///
        internal ObjectStateEntry AddKeyEntry(EntityKey entityKey, EntitySet entitySet) 
        {
            Debug.Assert((object)entityKey != null, "entityKey cannot be null."); 
            Debug.Assert(entitySet != null, "entitySet must be non-null."); 

            // We need to determine if an equivalent entry already exists; 
            // this is illegal in certain cases.
            ObjectStateEntry existingEntry = FindObjectStateEntry(entityKey);
            if (existingEntry != null)
            { 
                throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
            } 
 
            // Get a StateManagerTypeMetadata for the entity type.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entitySet.ElementType); 

            // Create a cache entry.
            ObjectStateEntry newEntry = new EntityEntry(entityKey, entitySet, this, typeMetadata);
 
            // A new entity is being added.
            AddEntityEntryToDictionary(newEntry, newEntry.State); 
 
            return newEntry;
        } 

        /// 
        /// Adds an object to the ObjectStateManager.
        ///  
        /// the object to add
        /// the entity set of the given object 
        /// Name of the argument passed to a public method, for use in exceptions. 
        /// Indicates whether the entity is added or unchanged.
        internal void AddEntry(object dataObject, EntityKey passedKey, EntitySet entitySet, string argumentName, bool isAdded) 
        {

            Debug.Assert(dataObject != null, "dataObject cannot be null.");
            Debug.Assert(entitySet != null, "entitySet must be non-null."); 
            // shadowValues is allowed to be null
            Debug.Assert(argumentName != null, "argumentName cannot be null."); 
 
            EntityKey entityKey = passedKey;
 
            // Get a StateManagerTypeMetadata for the entity type.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(dataObject.GetType(), entitySet);

            // dataObject's type should match to type that can be contained by the entityset 
            EdmType entityEdmType = typeMetadata.CdmMetadata.EdmType;
            //OC Mapping will make sure that non-abstract type in O space is always mapped to a non-abstract type in C space 
            Debug.Assert(!entityEdmType.Abstract, "non-abstract type in O space is mapped to abstract type in C space"); 
            if ((isAdded) && !entitySet.ElementType.IsAssignableFrom(entityEdmType))
            { 
                throw EntityUtil.EntityTypeDoesNotMatchEntitySet(dataObject.GetType().Name, TypeHelpers.GetFullName(entitySet), argumentName);
            }

            IEntityWithKey entityWithKey = dataObject as IEntityWithKey; 
            // The entityKey may be passed via the argument or via dataObject as IEntity.
            if (null != entityWithKey) 
            { 
                EntityKey dataObjectEntityKey = entityWithKey.EntityKey;
#if DEBUG 
                if ((object)dataObjectEntityKey != null && (object)entityKey != null)
                {
                    Debug.Assert(dataObjectEntityKey == entityKey, "The passed key and the key on dataObject must match.");
                } 
#endif
                if (null != (object)dataObjectEntityKey) 
                { 
                    entityKey = dataObjectEntityKey;
                    // These two checks verify that entityWithKey.EntityKey implemented by the user on a (I)POCO entity returns what it was given. 
                    EntityUtil.CheckEntityKeyNull(entityKey);
                    EntityUtil.CheckEntityKeysMatch(entityWithKey, entityKey);
                }
            } 

 
#if DEBUG 
            if ((object)entityKey != null && !entityKey.IsTemporary && !isAdded)
            { 
                // If the entity already has a permanent key, and we were invoked
                // from the materializer, assert that the key is correct.  We don't check
                // for temporary keys because temporary keys don't contain values.
                // Note that this method is only called in debug builds. 
                // entityKey is passed because the key has not yet been placed into the ObjectStateManager.
                AssertKeyMatchesEntity(dataObject, entityKey, entitySet); 
            } 
#endif
 
            if ((object)entityKey == null) // Neither entityWithKey.EntityKey nor the passed entityKey were non-null.
            {
                // If the entity does not have a key, create and add a temporary key.
                entityKey = new EntityKey(entitySet); 
                EntityUtil.SetKeyOntoEntity(entityWithKey, entityKey);
            } 
 
            // Create a cache entry.
            ObjectStateEntry newEntry = new EntityEntry(dataObject, entityKey, entitySet, this, typeMetadata, isAdded); 

            //Verify that the entityKey is set correctly--also checks entry.EK and entity.EK internally
            Debug.Assert(entityKey == newEntry.EntityKey, "The key on the new entry was not set correctly");
 
            // We need to determine if an equivalent entry already exists; this is illegal
            // in certain cases. 
            ObjectStateEntry existingEntry; 
            if ((isAdded) && (null != (existingEntry = FindObjectStateEntry(entityKey))))
            { 
                if (existingEntry.Entity != dataObject)
                {
                    throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
                } 
                // key does exist but entity is the same, it is being re-added ;
                // no-op when Add(entity) 
                // NOTE we don't want to re-add entities in other then Added state 
                if (existingEntry.State != EntityState.Added)  // (state == DataRowState.Unchanged && state == DataRowState.Modified)
                { 
                    throw EntityUtil.ObjectStateManagerDoesnotAllowToReAddUnchangedOrModifiedOrDeletedEntity(existingEntry.State);
                }
            }
            else 
            {
                // ObjectMaterializer will have already determined the existing entry doesn't exist 
                Debug.Assert(null == FindObjectStateEntry(entityKey), "should not have existing entry"); 

                if (newEntry.State == EntityState.Added && !entityKey.IsTemporary) 
                {
                    // If the entity doesn't already exist in the state manager
                    // and we intend to put the entity in the Added state (i.e.,
                    // AddEntry() was invoked from ObjectContext.AddObject()), 
                    // the entity's key must be temporary, because we require
                    // that all entries in the Added state have temporary keys. 
                    throw EntityUtil.CannotAddEntityWithKeyAttached(); 
                }
 
                // A new entity is being added.
                newEntry.AttachObjectStateManagerToEntity();
                AddEntityEntryToDictionary(newEntry, newEntry.State);
                // fire ColectionChanged event  only when a new entity is added to cache 
                OnObjectStateManagerChanged(CollectionChangeAction.Add, newEntry.Entity);
            } 
        } 

        // devnote: see comment to SQLBU 555615 in ObjectContext.AttachSingleObject() 
        internal void PromoteKeyEntryInitialization(ObjectStateEntry keyEntry,
            object entity,
            IExtendedDataRecord shadowValues,
            bool replacingEntry) 
        {
            Debug.Assert(keyEntry != null, "keyEntry must be non-null."); 
            Debug.Assert(entity != null, "entity cannot be null."); 
            // shadowValues is allowed to be null
 
            // Future Enhancement: Fixup already has this information, don't rediscover it
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entity.GetType(), (EntitySet)keyEntry.EntitySet);
            keyEntry.PromoteKeyEntry(entity, shadowValues, typeMetadata);
            AddEntryToKeylessStore(entity, keyEntry); 

            if (replacingEntry) 
            { 
                // if we are replacing an exising entry, then clean the entity's change tracker
                // so that it can be reset to this newly promoted entry 
                EntityUtil.SetChangeTrackerOntoEntity(entity, (IEntityChangeTracker)null);
            }
            // A new entity is being added.
            keyEntry.AttachObjectStateManagerToEntity(); 

            OnObjectStateManagerChanged(CollectionChangeAction.Add, keyEntry.Entity); 
        } 

        ///  
        /// Upgrades an entity key entry in the cache to a a regular entity
        /// 
        /// the key entry that exists in the state manager
        /// the object to add 
        /// a data record representation of the entity's values, including any values in shadow state
        /// True if this promoted key entry is replacing an existing detached entry 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds 
        /// Name of the argument passed to a public method, for use in exceptions.
        internal void PromoteKeyEntry(ObjectStateEntry keyEntry, 
            object entity,
            IExtendedDataRecord shadowValues,
            bool replacingEntry,
            bool setIsLoaded, 
            bool keyEntryInitialized,
            string argumentName) 
        { 
            Debug.Assert(keyEntry != null, "keyEntry must be non-null.");
            Debug.Assert(entity != null, "entity cannot be null."); 
            // shadowValues is allowed to be null
            Debug.Assert(argumentName != null, "argumentName cannot be null.");

            if (!keyEntryInitialized) 
            {
                this.PromoteKeyEntryInitialization(keyEntry, entity, shadowValues, replacingEntry); 
            } 

            bool doCleanup = true; 
            try
            {
                // We don't need to worry about the KeyEntry <-- Relationship --> KeyEntry because a key entry must
                // reference a non-key enrty. Fix up ther other side of the relationship. 
                // Get all the relationships that currently exist for this key entry
                foreach (ObjectStateEntry relationshipEntry in CopyOfRelationshipsByKey(keyEntry.EntityKey)) 
                { 
                    if (relationshipEntry.State != EntityState.Deleted)
                    { 
                        // Find the association ends that correspond to the source and target
                        AssociationEndMember sourceMember = keyEntry.GetAssociationEndMember(relationshipEntry);
                        AssociationEndMember targetMember = MetadataHelper.GetOtherAssociationEnd(sourceMember);
 
                        // Find the other end of the relationship
                        ObjectStateEntry targetEntry = keyEntry.GetOtherEndOfRelationship(relationshipEntry); 
 
                        // Here we are promoting based on a non-db retrieval so we use Append rules
                        AddEntityToCollectionOrReference( 
                            MergeOption.AppendOnly,
                            entity as IEntityWithRelationships,
                            sourceMember,
                            targetEntry.Entity as IEntityWithRelationships, 
                            targetMember,
                            /*setIsLoaded*/ setIsLoaded, 
                            /*relationshipAlreadyExists*/ true, 
                            /*inKeyEntryPromotion*/ true);
                    } 
                }
                doCleanup = false;
            }
            finally 
            {
                if (doCleanup) 
                { 
                    keyEntry.DetachObjectStateManagerFromEntity();
                    RemoveEntryFromKeylessStore(entity); 
                    keyEntry.DegradeEntry();
                }
            }
 
            if (this.IsAttachTracking)
            { 
                this.PromotedKeyEntries.Add(entity, keyEntry); 
            }
        } 

        internal void TrackPromotedRelationship(RelatedEnd relatedEnd, object entity)
        {
            Debug.Assert(relatedEnd != null); 
            Debug.Assert(entity != null);
            Debug.Assert(this.IsAttachTracking, "This method should be called only from ObjectContext.AttachTo (indirectly)"); 
 
            IList entities;
            if (!this.PromotedRelationships.TryGetValue(relatedEnd, out entities)) 
            {
                entities = new List();
                this.PromotedRelationships.Add(relatedEnd, entities);
            } 
            entities.Add(entity);
        } 
 
        internal void DegradePromotedRelationships()
        { 
            Debug.Assert(this.IsAttachTracking, "This method should be called only from the cleanup code");

            foreach (var pair in this.PromotedRelationships)
            { 
                foreach (object entity in pair.Value)
                { 
                    Debug.Assert((entity as IEntityWithRelationships) != null, 
                        "Entity which doesn't implement IEntityWithRelationships is in the recovery collection");
 
                    if (pair.Key.RemoveEntityFromLocallyCachedCollection(entity as IEntityWithRelationships, /*resetIsLoaded*/ false))
                    {
                        pair.Key.OnAssociationChanged(CollectionChangeAction.Remove, entity);
                    } 
                }
            } 
        } 

        ///  
        /// Performs non-generic collection or reference fixup between two entities
        /// This method should only be used in scenarios where we are automatically hooking up relationships for
        /// the user, and not in cases where they are manually setting relationships.
        ///  
        /// The MergeOption to use to decide how to resolve EntityReference conflicts
        /// The entity instance on the source side of the relationship 
        /// The AssociationEndMember that contains the metadata for the source entity 
        /// The entity instance on the source side of the relationship
        /// The AssociationEndMember that contains the metadata for the target entity 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds
        /// Whether or not the relationship entry already exists in the cache for these entities
        /// Whether this method is used in key entry promotion
        internal static void AddEntityToCollectionOrReference( 
            MergeOption mergeOption,
            IEntityWithRelationships sourceEntity, 
            AssociationEndMember sourceMember, 
            IEntityWithRelationships targetEntity,
            AssociationEndMember targetMember, 
            bool setIsLoaded,
            bool relationshipAlreadyExists,
            bool inKeyEntryPromotion)
        { 
            // Call GetRelatedEnd to retrieve the related end on the source entity that points to the target entity
            RelatedEnd relatedEnd = (RelatedEnd)sourceEntity.RelationshipManager.GetRelatedEnd(sourceMember.DeclaringType.FullName, targetMember.Name); 
 
            // EntityReference can only have one value
            if (targetMember.RelationshipMultiplicity != RelationshipMultiplicity.Many) 
            {
                switch (mergeOption)
                {
                    case MergeOption.NoTracking: 
                        // if using NoTracking, we have no way of determining identity resolution.
                        // Throw an exception saying the EntityReference is already populated and to try using 
                        // a different MergeOption 
                        if (!relatedEnd.IsEmpty())
                        { 
                            throw EntityUtil.CannotAddMoreThanOneEntityToEntityReferenceTryOtherMergeOption();
                        }
                        break;
                    case MergeOption.AppendOnly: 
                        // SQLBU 551031
                        // In key entry promotion case, detect that sourceEntity is already related to some entity in the context, 
                        // so it cannot be related to another entity being attached (relation 1-1). 
                        // Without this check we would throw exception from RelatedEnd.Add() but the exception message couldn't
                        // properly describe what has happened. 
                        if (inKeyEntryPromotion &&
                            !relatedEnd.IsEmpty())
                        {
                            throw EntityUtil.EntityConflictsWithKeyEntry(); 
                        }
                        break; 
 
                    case MergeOption.PreserveChanges:
                    case MergeOption.OverwriteChanges: 
                        // Retrieve the current target entity and the relationship
                        IEntityWithRelationships currentTargetEntity = null;
                        foreach (IEntityWithRelationships target in relatedEnd)
                        { 
                            Debug.Assert(currentTargetEntity == null, "Found more than one entry in an EntityReference");
                            currentTargetEntity = target; 
                        } 

                        if (currentTargetEntity != null) 
                        {
                            Debug.Assert(targetEntity != currentTargetEntity, "Source entity is already related to this target entity");

                            // The source entity is already related to a different target, so before we hook it up to the new target, 
                            // disconnect the existing related ends and detach the relationship entry
                            ObjectStateEntry relationshipEntry = relatedEnd.FindRelationshipEntryInObjectStateManager(currentTargetEntity); 
                            Debug.Assert(relationshipEntry != null, "Could not find relationship entry."); 

                            relatedEnd.RemoveAll(); 

                            // If the relationship was Added prior to the above RemoveAll, it will have already been detached
                            // If it was Unchanged, it is now Deleted and should be detached
                            // It should never have been Deleted before now, because we just got currentTargetEntity from the related end 
                            if (relationshipEntry.State == EntityState.Deleted)
                            { 
                                relationshipEntry.AcceptChanges(); 
                            }
 
                            Debug.Assert(relationshipEntry.State == EntityState.Detached, "relationshipEntry should be Detached");
                        }
                        break;
                } 
            }
 
            RelatedEnd targetRelatedEnd = null; 
            if (mergeOption == MergeOption.NoTracking)
            { 
                targetRelatedEnd = relatedEnd.GetOtherEndOfRelationship(targetEntity);
                if (targetRelatedEnd.IsLoaded)
                {
                    // The EntityCollection has already been loaded as part of the query and adding additional 
                    // entities would cause duplicate entries
                    throw EntityUtil.CannotFillTryDifferentMergeOption(targetRelatedEnd.SourceRoleName, targetRelatedEnd.RelationshipName); 
                } 
            }
 
            // Add the target entity
            relatedEnd.Add(targetEntity,
                true /*applyConstraints*/,
                true /*addRelationshipAsUnchanged*/, 
                relationshipAlreadyExists /*relationshipAlreadyExists*/);
 
            Debug.Assert(!(inKeyEntryPromotion && sourceEntity.RelationshipManager.Context == null), 
                "sourceEntity has been just attached to the context in PromoteKeyEntry, so Context shouldn't be null");
            Debug.Assert( 
                !(inKeyEntryPromotion &&
                sourceEntity.RelationshipManager.Context.ObjectStateManager.IsAttachTracking &&
                (setIsLoaded || mergeOption == MergeOption.NoTracking)),
                "This verifies that UpdateRelatedEnd is a no-op in a keyEntryPromotion case when the method is called indirectly from ObjectContext.AttachTo"); 

            // If either end is an EntityReference, we may need to set IsLoaded or the DetachedEntityKey 
            UpdateRelatedEnd(relatedEnd, targetEntity, setIsLoaded, mergeOption); 
            // we may have already retrieved the target end above, but if not, just get it now
            if (targetRelatedEnd == null) 
            {
                targetRelatedEnd = relatedEnd.GetOtherEndOfRelationship(targetEntity);
            }
            UpdateRelatedEnd(targetRelatedEnd, sourceEntity, setIsLoaded, mergeOption); 

            // In case the method was called from ObjectContext.AttachTo, we have to track relationships which were "promoted" 
            // Tracked relationships are used in recovery code of AttachTo. 
            if (inKeyEntryPromotion && sourceEntity.RelationshipManager.Context.ObjectStateManager.IsAttachTracking)
            { 
                sourceEntity.RelationshipManager.Context.ObjectStateManager.TrackPromotedRelationship(relatedEnd, targetEntity);
                sourceEntity.RelationshipManager.Context.ObjectStateManager.TrackPromotedRelationship(targetRelatedEnd, sourceEntity);
            }
        } 

        // devnote: This method should only be used in scenarios where we are automatically hooking up relationships for 
        // the user, and not in cases where they are manually setting relationships. 
        private static void UpdateRelatedEnd(RelatedEnd relatedEnd, IEntityWithRelationships relatedEntity, bool setIsLoaded, MergeOption mergeOption)
        { 
            AssociationEndMember endMember = (AssociationEndMember)(relatedEnd.ToEndMember);

            if ((endMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
                 endMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne)) 
            {
                if (setIsLoaded) 
                { 
                    relatedEnd.SetIsLoaded(true);
                } 
                // else we just want to leave IsLoaded alone, not set it to false

                // In NoTracking cases, we want to enable the EntityReference.EntityKey property, so we have to set the key
                if (mergeOption == MergeOption.NoTracking) 
                {
                    IEntityWithKey entityWithKey = relatedEntity as IEntityWithKey; 
                    if (entityWithKey != null) 
                    {
                        EntityKey targetKey = entityWithKey.EntityKey; 
                        EntityUtil.CheckEntityKeyNull(targetKey);

                        // since endMember is not Many, relatedEnd must be an EntityReference
                        ((EntityReference)relatedEnd).DetachedEntityKey = targetKey; 
                    }
#if DEBUG 
                    else 
                    {
                        // This should have been validated before we got here, so just Assert and don't validate again 
                        Debug.Fail("Relationships are not supported between NoTracking entities that do not support IEntityWithKey");
                    }
#endif
                } 
            }
        } 
 

        ///  
        /// Updates the relationships between a given source entity and a collection of target entities.
        /// Used for full span and related end Load methods, where the following may be true:
        /// (a) both sides of each relationship are always full entities and not stubs
        /// (b) there could be multiple entities to process at once 
        /// (c) NoTracking queries are possible.
        /// Not used for relationship span because although some of the logic is similar, the above are not true. 
        ///  
        /// ObjectContext to use to look up existing relationships. Using the context here instead of ObjectStateManager because for NoTracking queries
        /// we shouldn't even touch the state manager at all, so we don't want to access it until we know we are not using NoTracking. 
        /// MergeOption to use when updating existing relationships
        /// AssociationSet for the relationships
        /// Role of sourceEntity in associationSet
        /// EntityKey for sourceEntity 
        /// Source entity in the relationship
        /// Role of each targetEntity in associationSet 
        /// List of target entities to use to create relationships with sourceEntity 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds
        internal static int UpdateRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet, AssociationEndMember sourceMember, EntityKey sourceKey, IEntityWithRelationships sourceEntity, AssociationEndMember targetMember, IList targetEntities, bool setIsLoaded) 
        {
            int count = 0;

            if (targetEntities != null) 
            {
                if (mergeOption == MergeOption.NoTracking) 
                { 
                    RelatedEnd relatedEnd = (RelatedEnd)sourceEntity.RelationshipManager.GetRelatedEnd(sourceMember.DeclaringType.FullName, targetMember.Name);
                    if (!relatedEnd.IsEmpty()) 
                    {
                        // The RelatedEnd has already been filled as part of the query and adding additional
                        // entities would cause duplicate entries
                        throw EntityUtil.CannotFillTryDifferentMergeOption(relatedEnd.SourceRoleName, relatedEnd.RelationshipName); 
                    }
                } 
 
                foreach (IEntityWithRelationships targetEntity in targetEntities)
                { 
                    count++;

                    // If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one
                    EntityState newEntryState; 
                    if (mergeOption == MergeOption.NoTracking)
                    { 
                        // For NoTracking, we shouldn't touch the state manager, so no need to look for existing relationships to handle, just connect the two entities. 
                        // We don't care if the relationship already exists in the state manager or not, so just pass relationshipAlreadyExists=true so it won't look for it
                        AddEntityToCollectionOrReference( 
                            MergeOption.NoTracking,
                            sourceEntity,
                            sourceMember,
                            targetEntity, 
                            targetMember,
                            setIsLoaded, 
                            /*relationshipAlreadyExists*/ true, 
                            /*inKeyEntryPromotion*/ false);
                    } 
                    else
                    {
                        ObjectStateManager manager = context.ObjectStateManager;
                        EntityKey targetKey = manager.GetEntityKey(targetEntity); 
                        if (!TryUpdateExistingRelationships(context, mergeOption, associationSet, sourceMember, sourceKey, sourceEntity, targetMember, targetKey, setIsLoaded, out newEntryState))
                        { 
                            bool needNewRelationship = true; 
                            switch (sourceMember.RelationshipMultiplicity)
                            { 
                                case RelationshipMultiplicity.ZeroOrOne:
                                case RelationshipMultiplicity.One:
                                    // The other end of the relationship might already be related to something else, in which case we need to fix it up.
                                    // devnote1: In some cases we can let relationship span do this, but there are cases, like EntityCollection.Attach, where there is no query 
                                    //           and thus no relationship span to help us. So, for now, this is redundant because relationship span will make another pass over these
                                    //           entities, but unless I add a flag or something to indicate when I have to do it and when I don't, this is necessary. 
                                    // devnote2: The target and source arguments are intentionally reversed in the following call, because we already know there isn't a relationship 
                                    //           between the two entities we are current processing, but we want to see if there is one between the target and another source
                                    needNewRelationship = !TryUpdateExistingRelationships(context, mergeOption, associationSet, targetMember, 
                                        targetKey, targetEntity, sourceMember, sourceKey, setIsLoaded, out newEntryState);
                                    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 (newEntryState != EntityState.Deleted)
                                { 
                                    AddEntityToCollectionOrReference(
                                        mergeOption, 
                                        sourceEntity, 
                                        sourceMember,
                                        targetEntity, 
                                        targetMember,
                                        setIsLoaded,
                                        /*relationshipAlreadyExists*/ false,
                                        /*inKeyEntryPromotion*/ false); 
                                }
                                else 
                                { 
                                    // Add a Deleted relationship between the source entity and the target entity
                                    // No end fixup is necessary since the relationship is Deleted 
                                    RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
                                    manager.AddNewRelation(wrapper, EntityState.Deleted);
                                }
                            } 
                            // else there is nothing else for us to do, the relationship has been handled already
                        } 
                        // else there is nothing else for us to do, the relationship has been handled already 
                    }
                } 
            }
            return count;
            // devnote: Don't set IsLoaded on the target related end here -- the caller can do this more efficiently than we can here in some cases.
        } 

        ///  
        /// Removes relationships if necessary when a query determines that the source entity has no relationships on the server 
        /// 
        /// ObjectContext that contains the client relationships 
        /// MergeOption to use when updating existing relationships
        /// AssociationSet for the incoming relationship
        /// EntityKey of the source entity in the relationship
        /// Role of the source entity in the relationship 
        internal static void RemoveRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet,
            EntityKey sourceKey, AssociationEndMember sourceMember) 
        { 
            Debug.Assert(mergeOption == MergeOption.PreserveChanges || mergeOption == MergeOption.OverwriteChanges, "Unexpected MergeOption");
            List deletedRelationships = new List(); 

            // This entity has no related entities on the server for the given associationset and role. If it has related
            // entities on the client, we may need to update those relationships, depending on the MergeOption
            if (mergeOption == MergeOption.OverwriteChanges) 
            {
                foreach (ObjectStateEntry relationshipEntry in context.ObjectStateManager.FindRelationshipsByKey(sourceKey)) 
                { 
                    // We only care about the relationships that match the incoming associationset and role for the source entity
                    if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceKey, sourceMember)) 
                    {
                        deletedRelationships.Add(relationshipEntry);
                    }
                } 
            }
            else if (mergeOption == MergeOption.PreserveChanges) 
            { 
                // Leave any Added relationships for this entity, but remove Unchanged and Deleted ones
                foreach (ObjectStateEntry relationshipEntry in context.ObjectStateManager.FindRelationshipsByKey(sourceKey)) 
                {
                    // We only care about the relationships that match the incoming associationset and role for the source entity
                    if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceKey, sourceMember) &&
                        relationshipEntry.State != EntityState.Added) 
                    {
                        deletedRelationships.Add(relationshipEntry); 
                    } 
                }
            } 
            // else we do nothing. We never expect any other states here, and already Assert this condition at the top of the method

            foreach (ObjectStateEntry deletedEntry in deletedRelationships)
            { 
                ObjectStateManager.RemoveRelatedEndsAndDetachRelationship(deletedEntry, true);
            } 
        } 
        /// 
        /// Tries to updates one or more existing relationships for an entity, based on a given MergeOption and a target entity. 
        /// 
        /// ObjectContext to use to look up existing relationships for sourceEntity
        /// MergeOption to use when updating existing relationships
        /// AssociationSet for the relationship we are looking for 
        /// AssociationEndMember for the source role of the relationship
        /// EntityKey for the source entity in the relationship (passed here so we don't have to look it up again) 
        /// Source entity in the relationship 
        /// AssociationEndMember for the target role of the relationship
        /// EntityKey for the target entity in the relationship 
        /// Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds
        /// [out] EntityState to be used for in scenarios where we need to add a new relationship after this method has returned
        /// 
        /// true if an existing relationship is found and updated, and no further action is needed 
        /// false if either no relationship was found, or if one was found and updated, but a new one still needs to be added
        ///  
        internal static bool TryUpdateExistingRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet, AssociationEndMember sourceMember, EntityKey sourceKey, IEntityWithRelationships sourceEntity, AssociationEndMember targetMember, EntityKey targetKey, bool setIsLoaded, out EntityState newEntryState) 
        {
            Debug.Assert(mergeOption != MergeOption.NoTracking, "Existing relationships should not be updated with NoTracking"); 

            // New relationships are always added as Unchanged except in specific scenarios. If there are multiple relationships being updated, and
            // at least one of those requests the new relationship to be Deleted, it should always be added as Deleted, even if there are other
            // relationships being updated that don't specify a state. Adding as Unchanged is just the default unless a scenario needs it to be Deleted to 
            // acheive a particular result.
            newEntryState = EntityState.Unchanged; 
 
            // Unless we find a case below where we explicitly do not want a new relationship, we should always add one to match the server.
            bool needNewRelationship = true; 

            ObjectStateManager manager = context.ObjectStateManager;
            List entriesToDetach = null;
            List entriesToUpdate = null; 
            foreach (ObjectStateEntry relationshipEntry in manager.FindRelationshipsByKey(sourceKey))
            { 
                // We only care about relationships for the same AssociationSet and where the source entity is in the same role as it is in the incoming relationship. 
                if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceKey, sourceMember))
                { 
                    // If the other end of this relationship matches our current target entity, this relationship entry matches the server
                    if (targetKey == relationshipEntry.Wrapper.GetOtherEntityKey(sourceKey))
                    {
                        if (entriesToUpdate == null) 
                        {
                            entriesToUpdate = new List(); 
                        } 
                        entriesToUpdate.Add(relationshipEntry);
                    } 
                    else
                    {
                        // We found an existing relationship where the reference side is different on the server than what the client has.
 
                        // This relationship is between the same source entity and a different target, so we may need to take steps to fix up the
                        // relationship to ensure that the client state is correct based on the requested MergeOption. 
                        // The only scenario we care about here is one where the target member has zero or one multiplicity (0..1 or 1..1), because those 
                        // are the only cases where it is meaningful to say that the relationship is different on the server and the client. In scenarios
                        // where the target member has a many (*) multiplicity, it is possible to have multiple relationships between the source key 
                        // and other entities, and we don't want to touch those here.
                        switch (targetMember.RelationshipMultiplicity)
                        {
                            case RelationshipMultiplicity.One: 
                            case RelationshipMultiplicity.ZeroOrOne:
                                switch (mergeOption) 
                                { 
                                    case MergeOption.AppendOnly:
                                        if (relationshipEntry.State != EntityState.Deleted) 
                                        {
                                            Debug.Assert(relationshipEntry.State == EntityState.Added || relationshipEntry.State == EntityState.Unchanged, "Unexpected relationshipEntry state");
                                            needNewRelationship = false; // adding a new relationship would conflict with the existing one
                                        } 
                                        break;
                                    case MergeOption.OverwriteChanges: 
                                        if (entriesToDetach == null) 
                                        {
                                            entriesToDetach = new List(); 
                                        }
                                        entriesToDetach.Add(relationshipEntry);
                                        break;
                                    case MergeOption.PreserveChanges: 
                                        switch (relationshipEntry.State)
                                        { 
                                            case EntityState.Added: 
                                                newEntryState = EntityState.Deleted;
                                                break; 
                                            case EntityState.Unchanged:
                                                if (entriesToDetach == null)
                                                {
                                                    entriesToDetach = new List(); 
                                                }
                                                entriesToDetach.Add(relationshipEntry); 
                                                break; 
                                            case EntityState.Deleted:
                                                newEntryState = EntityState.Deleted; 
                                                if (entriesToDetach == null)
                                                {
                                                    entriesToDetach = new List();
                                                } 
                                                entriesToDetach.Add(relationshipEntry);
                                                break; 
                                            default: 
                                                Debug.Assert(false, "Unexpected relationship entry state");
                                                break; 
                                        }
                                        break;
                                    default:
                                        Debug.Assert(false, "Unexpected MergeOption"); 
                                        break;
                                } 
                                break; 
                            case RelationshipMultiplicity.Many:
                                // do nothing because its okay for this source entity to have multiple different targets, so there is nothing for us to fixup 
                                break;
                            default:
                                Debug.Assert(false, "Unexpected targetMember.RelationshipMultiplicity");
                                break; 
                        }
                    } 
                } 
            }
 
            // Detach all of the entries that we have collected above
            if (entriesToDetach != null)
            {
                foreach (ObjectStateEntry entryToDetach in entriesToDetach) 
                {
                    // the entry may have already been detached by another operation. If not, detach it now. 
                    if (entryToDetach.State != EntityState.Detached) 
                    {
                        RemoveRelatedEndsAndDetachRelationship(entryToDetach, setIsLoaded); 
                    }
                }
            }
 
            // Update all of the matching entries that we have collectioned above
            if (entriesToUpdate != null) 
            { 
                foreach (ObjectStateEntry relationshipEntry in entriesToUpdate)
                { 
                    // Don't need new relationship to be added to match the server, since we already have a match
                    needNewRelationship = false;

                    // We have an existing relationship entry that matches exactly to the incoming relationship from the server, but 
                    // we may need to update it on the client based on the MergeOption and the state of the relationship entry.
                    switch (mergeOption) 
                    { 
                        case MergeOption.AppendOnly:
                            // AppendOnly and NoTracking shouldn't affect existing relationships, so do nothing 
                            break;
                        case MergeOption.OverwriteChanges:
                            if (relationshipEntry.State == EntityState.Added)
                            { 
                                relationshipEntry.AcceptChanges();
                            } 
                            else if (relationshipEntry.State == EntityState.Deleted) 
                            {
                                // targetEntry should always exist in this scenario because it would have 
                                // at least been created when the relationship entry was created
                                ObjectStateEntry targetEntry = manager.GetObjectStateEntry(targetKey);

                                // If the target entity is deleted, we don't want to bring the relationship entry back. 
                                if (targetEntry.State != EntityState.Deleted)
                                { 
                                    // If the targetEntry is a KeyEntry, there are no ends to fix up. 
                                    if (!targetEntry.IsKeyEntry)
                                    { 
                                        ObjectStateManager.AddEntityToCollectionOrReference(
                                            mergeOption,
                                            sourceEntity,
                                            sourceMember, 
                                            (IEntityWithRelationships)targetEntry.Entity,
                                            targetMember, 
                                            /*setIsLoaded*/ setIsLoaded, 
                                            /*relationshipAlreadyExists*/ true,
                                            /*inKeyEntryPromotion*/ false); 
                                    }
                                    relationshipEntry.RevertDelete();
                                }
                            } 
                            // else it's already Unchanged so we don't need to do anything
                            break; 
                        case MergeOption.PreserveChanges: 
                            if (relationshipEntry.State == EntityState.Added)
                            { 
                                // The client now matches the server, so just move the relationship to unchanged.
                                // If we don't do this and left the state Added, we will get a concurrency exception when trying to save
                                relationshipEntry.AcceptChanges();
                            } 
                            // else if it's already Unchanged we don't need to do anything
                            // else if it's Deleted we want to preserve that state so do nothing 
                            break; 
                        default:
                            Debug.Assert(false, "Unexpected MergeOption"); 
                            break;
                    }
                }
            } 

            return !needNewRelationship; 
        } 

        // Helper method to disconnect two related ends and detach their associated relationship entry 
        internal static void RemoveRelatedEndsAndDetachRelationship(ObjectStateEntry relationshipToRemove, bool setIsLoaded)
        {
            // If we are allowed to set the IsLoaded flag, then we can consider unloading these relationships
            if (setIsLoaded) 
            {
                // If the relationship needs to be deleted, then we should unload the related ends 
                UnloadReferenceRelatedEnds(relationshipToRemove); 
            }
 
            // Delete the relationship entry and disconnect the related ends
            if (relationshipToRemove.State != EntityState.Deleted)
            {
                relationshipToRemove.Delete(); 
            }
 
            // Detach the relationship entry 
            // Entries that were in the Added state prior to the Delete above will have already been Detached
            if (relationshipToRemove.State != EntityState.Detached) 
            {
                relationshipToRemove.AcceptChanges();
            }
        } 

        private static void UnloadReferenceRelatedEnds(ObjectStateEntry entry) 
        { 
            RelationshipEntry relationshipEntry = entry as RelationshipEntry;
            //Find two ends of the relationship 
            ObjectStateManager cache = relationshipEntry.ObjectStateManager;
            ReadOnlyMetadataCollection endMembers = relationshipEntry.Wrapper.AssociationEndMembers;

            UnloadReferenceRelatedEnds(cache, relationshipEntry, relationshipEntry.Wrapper.GetEntityKey(0), endMembers[1].Name); 
            UnloadReferenceRelatedEnds(cache, relationshipEntry, relationshipEntry.Wrapper.GetEntityKey(1), endMembers[0].Name);
        } 
 
        private static void UnloadReferenceRelatedEnds(ObjectStateManager cache, RelationshipEntry relationshipEntry, EntityKey sourceEntityKey, string targetRoleName)
        { 
            ObjectStateEntry entry = cache.GetObjectStateEntry(sourceEntityKey);
            IEntityWithRelationships entity = entry.Entity as IEntityWithRelationships;

            if (entity != null) 
            {
                EntityReference reference = entity.RelationshipManager.GetRelatedEnd(((AssociationSet)relationshipEntry.EntitySet).ElementType.FullName, targetRoleName) as EntityReference; 
                if (reference != null) 
                {
                    reference.SetIsLoaded(false); 
                }
            }
        }
 
        /// 
        /// Asserts that the EntityKey attached to the given entity 
        /// appropriately matches the given entity. 
        /// 
        /// the entity whose key must be verified 
        /// the entity set corresponding to the type of the given entity.
        [Conditional("DEBUG")]
        private void AssertKeyMatchesEntity(object entity, EntityKey entityKey, EntitySet entitySetForType)
        { 
#if DEBUG
            Debug.Assert(entity != null, "Cannot verify key for null entity."); 
 
            Debug.Assert((object)entityKey != null, "Cannot verify a null EntityKey.");
            Debug.Assert(!entityKey.IsTemporary, "Verifying a temporary EntityKey doesn't make sense because the key doesn't contain any values."); 
            Debug.Assert(entitySetForType != null, "Cannot verify against a null entity set.");

            EntitySet entitySetForKey = entityKey.GetEntitySet(this.MetadataWorkspace);
            Debug.Assert(entitySetForKey != null, "EntityKey.EntitySet should never be null."); 

            // Assert that the entity's key matches its type. 
            Debug.Assert(entitySetForType.Name == entitySetForKey.Name && 
                entitySetForType.EntityContainer.Name == entitySetForKey.EntityContainer.Name,
                "Entity's type indicates that belongs to a different the entity set than the one specified in its key."); 

            // Assert that the key values in the entity match the key values
            // within its EntityKey.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entity.GetType(), entitySetForType); 
            for (int i = 0; i < entitySetForKey.ElementType.KeyMembers.Count; ++i)
            { 
                EdmMember keyField = entitySetForKey.ElementType.KeyMembers[i]; 
                int ordinal = typeMetadata.GetOrdinalforCLayerMemberName(keyField.Name);
                Debug.Assert(ordinal >= 0, "Invalid ordinal returned for field '" + keyField.Name + "'"); 
                object entityValue = typeMetadata.Member(ordinal).GetValue(entity);
                object keyValue = entityKey.FindValueByName(keyField.Name);

                // Use object.Equals(), because key field values have to be simple types. 
                Debug.Assert(object.Equals(entityValue, keyValue),
                    "The entity cannot be added because the value of at least one key property does not match the corresponding value within its EntityKey."); 
            } 
#endif
        } 

        /// 
        /// Attach entity in unchanged state (skip Added state, don't create temp key)
        /// It is equal (but faster) to call AddEntry(); AcceptChanges(). 
        /// 
        ///  
        ///  
        /// 
        internal ObjectStateEntry AttachEntry(EntityKey entityKey, object dataObject, EntitySet entitySet, string argumentName) 
        {
            Debug.Assert(dataObject != null, "entity cannot be null.");
            Debug.Assert(entitySet != null, "entitySet must be non-null.");
            Debug.Assert(argumentName != null, "argumentName cannot be null."); 
            Debug.Assert(entityKey != null, "argumentName cannot be null.");
 
            // Get a StateManagerTypeMetadata for the entity type. 
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(dataObject.GetType(), entitySet);
 
            CheckKeyMatchesEntity(dataObject, entityKey, entitySet);

            // Create a cache entry.
            ObjectStateEntry newEntry = new EntityEntry(dataObject, entityKey, entitySet, this, typeMetadata, /*isAdded*/true); 

            // The property EntityKey on newEntry validates that the entry and the entity on the entry have the same key. 
            Debug.Assert(entityKey == newEntry.EntityKey, "newEntry.EntityKey should match entityKey"); 

            // In the constructor of Entry, state is set to Added, so set state manually to Unchanged. 
            newEntry.State = EntityState.Unchanged;

            // A entity is being attached.
            newEntry.AttachObjectStateManagerToEntity(); 
            AddEntityEntryToDictionary(newEntry, newEntry.State);
            // fire ColectionChanged event only when a new entity is added to cache 
            OnObjectStateManagerChanged(CollectionChangeAction.Add, newEntry.Entity); 

            return newEntry; 
        }

        /// 
        /// Checks that the EntityKey attached to the given entity 
        /// appropriately matches the given entity.
        /// NOTE: does the same as AssertKeyMatchesEntity(), but throws instead of calling Assert(). 
        ///  
        /// the entity whose key must be verified
        /// the entity set corresponding to the type of the given entity. 
        private void CheckKeyMatchesEntity(object entity, EntityKey entityKey, EntitySet entitySetForType)
        {
            Debug.Assert(entity != null, "Cannot verify key for null entity.");
 
            Debug.Assert((object)entityKey != null, "Cannot verify a null EntityKey.");
            Debug.Assert(!entityKey.IsTemporary, "Verifying a temporary EntityKey doesn't make sense because the key doesn't contain any values."); 
            Debug.Assert(entitySetForType != null, "Cannot verify against a null entity set."); 

            EntitySet entitySetForKey = entityKey.GetEntitySet(this.MetadataWorkspace); 
            if(entitySetForKey == null)
            {
                throw EntityUtil.InvalidKey();
            } 

            // Checks that the entity's key matches its type. 
            if (!(entitySetForType.Name == entitySetForKey.Name && 
                entitySetForType.EntityContainer.Name == entitySetForKey.EntityContainer.Name))
            { 
                throw EntityUtil.EntityTypeDoesntMatchEntitySetFromKey();
            }

            // Verify that the entity key contains the correct members for the entity set 
            entityKey.ValidateEntityKey(entitySetForKey);
 
            // Checks that the key values in the entity match the key values 
            // within its EntityKey.
            StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entity.GetType(), entitySetForType); 
            for (int i = 0; i < entitySetForKey.ElementType.KeyMembers.Count; ++i)
            {
                EdmMember keyField = entitySetForKey.ElementType.KeyMembers[i];
                int ordinal = typeMetadata.GetOrdinalforCLayerMemberName(keyField.Name); 
                if (ordinal < 0)
                { 
                    throw EntityUtil.InvalidKey(); 
                }
 
                object entityValue = typeMetadata.Member(ordinal).GetValue(entity);
                object keyValue = entityKey.FindValueByName(keyField.Name);

                // Use object.Equals(), because key field values have to be simple types. 
                if (!object.Equals(entityValue, keyValue))
                { 
                    throw EntityUtil.KeyPropertyDoesntMatchValueInKey(); 
                }
            } 

            // NOTE: Called just to be sure that CheckKeyMatchesEntity() does the same as AssertKeyMatchesEntity().
            // AssertKeyMatchesEntity() is going to be removed in the future and CheckKeyMatchesEntity will be
            // used instead (in AddEntry() ). 
            AssertKeyMatchesEntity(entity, entityKey, entitySetForType);
        } 
 
        internal ObjectStateEntry AddNewRelation(RelationshipWrapper wrapper, EntityState desiredState)
        { 
            Debug.Assert(null == FindRelationship(wrapper), "relationship should not exist, caller verifies");

            RelationshipEntry entry = new RelationshipEntry(this, desiredState, wrapper);
            AddRelationshipEntryToDictionary(entry, desiredState); 
            AddRelationshipToLookup(entry);
            return entry; 
        } 

        internal ObjectStateEntry AddRelation(RelationshipWrapper wrapper, EntityState desiredState) 
        {
            Debug.Assert(EntityState.Added == desiredState ||        // result entry should be added or left alone
                         EntityState.Unchanged == desiredState ||    // result entry should be that state
                         EntityState.Deleted == desiredState,        // result entry should be in that state 
                         "unexpected state");
 
            ObjectStateEntry entry = FindRelationship(wrapper); 
            Debug.Assert(null == entry || (EntityState.Modified != entry.State), "relationship should never be modified");
 
            if (entry == null)
            {
                entry = AddNewRelation(wrapper, desiredState);
            } 
            else if (EntityState.Deleted != entry.State)
            { 
                // you can have a deleted and non-deleted relation between two entities 
                // SQL BU DT 449757: for now no-op in case if it exists. ideally need to throw
                if (EntityState.Unchanged == desiredState) 
                {
                    entry.AcceptChanges();
                }
                else if (EntityState.Deleted == desiredState) 
                {
                    entry.AcceptChanges(); 
                    entry.Delete(false); 
                }
                // else Added and leave entry alone 
            }
            else if (EntityState.Deleted != desiredState)
            {
                Debug.Assert(EntityState.Deleted == entry.State, "should be deleted state"); 
                entry.RevertDelete();
            } 
            // else entry already Deleted or if desired state is Added then left alone 

            Debug.Assert(desiredState == entry.State || 
                         EntityState.Added == desiredState,
                         "unexpected end state");
            return entry;
        } 

        ///  
        /// Adds the given relationship cache entry to the mapping from each of its endpoint keys. 
        /// 
        private void AddRelationshipToLookup(RelationshipEntry relationship) 
        {
            Debug.Assert(relationship != null, "relationship can't be null");
            Debug.Assert(relationship.IsRelationship, "The cache entry is expected to be a relationship cache entry.");
 
            AddRelationshipEndToLookup(relationship.Wrapper.Key0, relationship);
            if (!relationship.Wrapper.Key0.Equals(relationship.Wrapper.Key1)) 
            { 
                AddRelationshipEndToLookup(relationship.Wrapper.Key1, relationship);
            } 
        }

        /// 
        /// Adds the given relationship cache entry to the mapping from the given endpoint key. 
        /// 
        private void AddRelationshipEndToLookup(EntityKey key, RelationshipEntry relationship) 
        { 
            Debug.Assert(relationship.IsRelationship, "The cache entry is expected to be a relationship cache entry.");
            Debug.Assert(null != FindObjectStateEntry(key), "EntityEntry doesn't exist"); 

            EntityEntry entry = (EntityEntry)GetObjectStateEntry(key);
            Debug.Assert(key.Equals(entry.EntityKey), "EntityKey mismatch");
            entry.AddRelationshipEnd(relationship); 
        }
 
        ///  
        /// Deletes the given relationship cache entry from the mapping from each of its endpoint keys.
        ///  
        private void DeleteRelationshipFromLookup(RelationshipEntry relationship)
        {
            // The relationship is stored in the lookup indexed by both keys, so we need to remove it twice.
            DeleteRelationshipEndFromLookup(relationship.Wrapper.Key0, relationship); 
            if (!relationship.Wrapper.Key0.Equals(relationship.Wrapper.Key1))
            { 
                DeleteRelationshipEndFromLookup(relationship.Wrapper.Key1, relationship); 
            }
        } 

        /// 
        /// Deletes the given relationship cache entry from the mapping from the given endpoint key.
        ///  
        private void DeleteRelationshipEndFromLookup(EntityKey key, RelationshipEntry relationship)
        { 
            Debug.Assert(relationship.IsRelationship, "The cache entry is expected to be a relationship cache entry."); 
            Debug.Assert(relationship.State != EntityState.Detached, "Cannot remove a detached cache entry.");
            Debug.Assert(null != FindObjectStateEntry(key), "EntityEntry doesn't exist"); 

            EntityEntry entry = (EntityEntry)GetObjectStateEntry(key);
            Debug.Assert(key.Equals(entry.EntityKey), "EntityKey mismatch");
            entry.RemoveRelationshipEnd(relationship); 
        }
 
        internal ObjectStateEntry FindRelationship(RelationshipSet relationshipSet, 
                                                   KeyValuePair roleAndKey1,
                                                   KeyValuePair roleAndKey2) 
        {
            if ((null == (object)roleAndKey1.Value) || (null == (object)roleAndKey2.Value))
            {
                return null; 
            }
            return FindRelationship(new RelationshipWrapper((AssociationSet)relationshipSet, roleAndKey1, roleAndKey2)); 
        } 

        internal ObjectStateEntry FindRelationship(RelationshipWrapper relationshipWrapper) 
        {
            ObjectStateEntry entry = null;
            bool result = (((null != _unchangedRelationshipStore) && _unchangedRelationshipStore.TryGetValue(relationshipWrapper, out  entry)) ||
                           ((null != _deletedRelationshipStore) && _deletedRelationshipStore.TryGetValue(relationshipWrapper, out  entry)) || 
                           ((null != _addedRelationshipStore) && _addedRelationshipStore.TryGetValue(relationshipWrapper, out entry)));
            Debug.Assert(result == (null != entry), "found null entry"); 
            return entry; 
        }
 
        /// 
        /// DeleteRelationship
        /// 
        /// The deleted entry 
        internal ObjectStateEntry DeleteRelationship(RelationshipSet relationshipSet,
                                          KeyValuePair roleAndKey1, 
                                          KeyValuePair roleAndKey2) 
        {
            ObjectStateEntry entry = FindRelationship(relationshipSet, roleAndKey1, roleAndKey2); 
            if (entry != null)
            {
                entry.Delete(/*doFixup*/ false);
            } 
            return entry;
        } 
 

        ///  
        /// DeleteKeyEntry
        /// 
        internal void DeleteKeyEntry(ObjectStateEntry keyEntry)
        { 
            if (keyEntry != null && keyEntry.IsKeyEntry)
            { 
                ChangeState(keyEntry, keyEntry.State, EntityState.Detached); 
            }
        } 


        /// 
        /// Finds all relationships with the given key at one end. 
        /// 
        internal RelationshipEntry[] CopyOfRelationshipsByKey(EntityKey key) 
        { 
            return FindRelationshipsByKey(key).ToArray();
        } 

        /// 
        /// Finds all relationships with the given key at one end.
        /// Do not use the list to add elements 
        /// 
        internal EntityEntry.RelationshipEndEnumerable FindRelationshipsByKey(EntityKey key) 
        { 
            return new EntityEntry.RelationshipEndEnumerable((EntityEntry)FindObjectStateEntry(key));
        } 

        IEnumerable IEntityStateManager.FindRelationshipsByKey(EntityKey key)
        {
            return FindRelationshipsByKey(key); 
        }
 
        ///  
        /// Returns a non-null key for the given entity.
        /// Throws if the passed entity doesn't have a key attached. 
        /// Throws if the key is null.
        /// 
        /// An object with a key
        /// The key on the object 
        /// if key is null
        internal EntityKey GetEntityKey(object entity) 
        { 
            EntityKey key = null;
            if (!TryGetEntityKey(entity, out key)) //If key is null or missing 
            {
                throw EntityUtil.ObjectDoesNotHaveAKey(entity);
            }
            return key; 
        }
 
        ///  
        /// Retrieves an EntityKey from the given entity with relationships.
        /// If the entity implements, IEntity, the retrieve the key directly from the entity, 
        /// Else, if there's a context attached to the entity's relationship manager, retrieve
        /// the key from the contained ObjectStateManager
        ///
        /// Note: When this method is called with "owner" on a RelatedEnd. The RelatedEnd that has an owner will not 
        /// have a Context attached if the owner does not have a context attached. Hence, it's not necessary
        /// to check "this" to find a context. 
        ///  
        /// An object that implements the IEntityWithRelationships interface.
        ///  
        /// The EntityKey associated with the entity Returns null if the key doesn't exist.
        /// 
        internal static EntityKey FindKeyOnEntityWithRelationships(IEntityWithRelationships entity)
        { 
            IEntityWithKey entityWithKey = entity as IEntityWithKey;
            if (null != entityWithKey) 
            { 
                return entityWithKey.EntityKey;
            } 

            RelationshipManager relationshipManager = entity.RelationshipManager;
            if ((null != relationshipManager) && (null != relationshipManager.Context))
            { 
                return ObjectContext.FindEntityKey(entity, relationshipManager.Context);
            } 
            return null; 
        }
 
        //Verify that all entities in the _keylessEntityStore are also in the other dictionaries.
        //Verify that all the entries in the _keylessEntityStore don't implement IEntityWithKey.
        //Verify that there no entries in the other dictionaries that don't implement IEntityWithKey and aren't in _keylessEntityStore
        [ConditionalAttribute("DEBUG")] 
        private void ValidateKeylessEntityStore()
        { 
            // Future Enhancement : Check each entry in _keylessEntityStore to make sure it has a corresponding entry in one of the other stores. 
            if (null != _keylessEntityStore)
            { 
                foreach (ObjectStateEntry entry in _keylessEntityStore.Values)
                {
                    Debug.Assert(!(entry.Entity is IEntityWithKey), "_keylessEntityStore contains an entry that implement IEntityWithKey");
                    ObjectStateEntry entrya; 
                    bool result = false;
                    if (null != _addedEntityStore) 
                    { 
                        result = _addedEntityStore.TryGetValue(entry.EntityKey, out entrya);
                    } 
                    if (null != _modifiedEntityStore)
                    {
                        result |= _modifiedEntityStore.TryGetValue(entry.EntityKey, out entrya);
                    } 
                    if (null != _deletedEntityStore)
                    { 
                        result |= _deletedEntityStore.TryGetValue(entry.EntityKey, out entrya); 
                    }
                    if (null != _unchangedEntityStore) 
                    {
                        result |= _unchangedEntityStore.TryGetValue(entry.EntityKey, out entrya);
                    }
                    Debug.Assert(result, "entry in _keylessEntityStore doesn't exist in one of the other stores"); 
                }
            } 
 
            //Check each entry in the other stores to make sure that each non-IEntityWithKey entry is also in _keylessEntityStore
            Dictionary[] stores = { _unchangedEntityStore, _modifiedEntityStore, _addedEntityStore, _deletedEntityStore }; 
            foreach (Dictionary store in stores)
            {
                if (null != store)
                { 
                    foreach (ObjectStateEntry entry in store.Values)
                    { 
                        if (null != entry.Entity && //Skip span stub key entry 
                            !(entry.Entity is IEntityWithKey))
                        { 
                            ObjectStateEntry keylessEntry;
                            Debug.Assert(null != _keylessEntityStore, "There should be a store that keyless entries are in");
                            if (_keylessEntityStore.TryGetValue(entry.Entity, out keylessEntry))
                            { 
                                Debug.Assert(object.ReferenceEquals(entry, keylessEntry), "keylessEntry and entry from stores do not match");
                            } 
                            else 
                            {
                                Debug.Assert(false, "The entry containing an entity not implementing IEntityWithKey is not in the _keylessEntityStore"); 
                            }
                        }
                    }
                } 
            }
        } 
        ///  
        /// Find the ObjectStateEntry from _keylessEntityStore for an entity that doesn't implement IEntityWithKey.
        ///  
        /// 
        /// 
        private bool TryGetEntryFromKeylessStore(object entity, out ObjectStateEntry entryRef)
        { 
            Debug.Assert(!(entity is IEntityWithKey));
            EntityUtil.CheckArgumentNull(entity, "entity"); 
 
            ValidateKeylessEntityStore();
            entryRef = null; 
            if (null != _keylessEntityStore)
            {
                if (_keylessEntityStore.TryGetValue(entity, out entryRef))
                { 
                    return true;
                } 
            } 

            entryRef = null; 
            return false;
        }

        ///  
        /// Given an object, return the corresponding EntityKey.
        /// 
        ///  
        /// An object that is used by an entity.
        /// The key corresponding to the passed-in object. 
        /// 
        /// True if a key exists for the given object.
        /// False if key is null;
        /// False if the key does not exist for the entity (does not implement IEntity) 
        /// 
        /// entity is null 
        internal bool TryGetEntityKey(object entity, out EntityKey key) 
        {
            key = null; 
            EntityUtil.CheckArgumentNull(entity, "entity");

            IEntityWithKey entityWithKey = entity as IEntityWithKey;
            if (null != entityWithKey) //This entity implements IEntity. 
            {
                key = entityWithKey.EntityKey; 
            } 
            else //The passed entity does not implement IEntity.
            { 
                ObjectStateEntry entry;
                if (TryGetEntryFromKeylessStore(entity, out entry))
                {
                    key = entry.EntityKey; 
                }
            } 
            return (null != (object)key); 
        }
 
        /// 
        /// Returns all CacheEntries in the given state.
        /// 
        /// if EntityState.Detached flag is set in state 
        public IEnumerable GetObjectStateEntries(EntityState state)
        { 
            if ((EntityState.Detached & state) != 0) 
            {
                throw EntityUtil.DetachedObjectStateEntriesDoesNotExistInObjectStateManager(); 
            }
            return GetObjectStateEntriesInternal(state);
        }
 
        /// 
        /// Returns all CacheEntries in the given state. 
        ///  
        /// if EntityState.Detached flag is set in state
        IEnumerable IEntityStateManager.GetEntityStateEntries(EntityState state) 
        {
            Debug.Assert((EntityState.Detached & state) == 0, "Cannot get state entries for detached entities");
            foreach (ObjectStateEntry stateEntry in GetObjectStateEntriesInternal(state))
            { 
                yield return (IEntityStateEntry)stateEntry;
            } 
        } 

        internal int GetObjectStateEntriesCount(EntityState state) 
        {
            int size = 0;
            if ((EntityState.Added & state) != 0)
            { 
                size += ((null != _addedRelationshipStore) ? _addedRelationshipStore.Count : 0);
                size += ((null != _addedEntityStore) ? _addedEntityStore.Count : 0); 
            } 
            if ((EntityState.Modified & state) != 0)
            { 
                size += ((null != _modifiedEntityStore) ? _modifiedEntityStore.Count : 0);
            }
            if ((EntityState.Deleted & state) != 0)
            { 
                size += ((null != _deletedRelationshipStore) ? _deletedRelationshipStore.Count : 0);
                size += ((null != _deletedEntityStore) ? _deletedEntityStore.Count : 0); 
            } 
            if ((EntityState.Unchanged & state) != 0)
            { 
                size += ((null != _unchangedRelationshipStore) ? _unchangedRelationshipStore.Count : 0);
                size += ((null != _unchangedEntityStore) ? _unchangedEntityStore.Count : 0);
            }
            return size; 
        }
 
        private ObjectStateEntry[] GetObjectStateEntriesInternal(EntityState state) 
        {
            Debug.Assert((EntityState.Detached & state) == 0, "Cannot get state entries for detached entities"); 

            int size = GetObjectStateEntriesCount(state);
            ObjectStateEntry[] entries = new ObjectStateEntry[size];
 
            size = 0; // size is now used as an offset
            if (((EntityState.Added & state) != 0) && (null != _addedRelationshipStore)) 
            { 
                _addedRelationshipStore.Values.CopyTo(entries, size);
                size += _addedRelationshipStore.Count; 
            }
            if (((EntityState.Deleted & state) != 0) && (null != _deletedRelationshipStore))
            {
                _deletedRelationshipStore.Values.CopyTo(entries, size); 
                size += _deletedRelationshipStore.Count;
            } 
            if (((EntityState.Unchanged & state) != 0) && (null != _unchangedRelationshipStore)) 
            {
                _unchangedRelationshipStore.Values.CopyTo(entries, size); 
                size += _unchangedRelationshipStore.Count;
            }
            if (((EntityState.Added & state) != 0) && (null != _addedEntityStore))
            { 
                _addedEntityStore.Values.CopyTo(entries, size);
                size += _addedEntityStore.Count; 
            } 
            if (((EntityState.Modified & state) != 0) && (null != _modifiedEntityStore))
            { 
                _modifiedEntityStore.Values.CopyTo(entries, size);
                size += _modifiedEntityStore.Count;
            }
            if (((EntityState.Deleted & state) != 0) && (null != _deletedEntityStore)) 
            {
                _deletedEntityStore.Values.CopyTo(entries, size); 
                size += _deletedEntityStore.Count; 
            }
            if (((EntityState.Unchanged & state) != 0) && (null != _unchangedEntityStore)) 
            {
                _unchangedEntityStore.Values.CopyTo(entries, size);
                size += _unchangedEntityStore.Count;
            } 
            return entries;
        } 
 
        #region temporary (added state) to permanent (deleted, modified, unchanged state) EntityKey fixup
 
        /// 
        /// Performs key-fixup on the given entry, by creating a (permanent) EntityKey
        /// based on the current key values within the associated entity and fixing up
        /// all associated relationship entries. 
        /// 
        ///  
        /// Will promote EntityEntry.IsKeyEntry and leave in _unchangedStore 
        /// otherwise will move EntityEntry from _addedStore to _unchangedStore.
        ///  
        internal void FixupKey(EntityEntry entry)
        {
            Debug.Assert(entry != null, "entry should not be null.");
            Debug.Assert(entry.State == EntityState.Added, "Cannot do key fixup for an entry not in the Added state."); 
            Debug.Assert(entry.Entity != null, "must have entity, can't be entity stub in added state");
            Debug.Assert(!entry.IsRelationship, "Shouldn't pass relationship entries to FixupKey()"); 
 
            EntityKey oldKey = entry.EntityKey;
            Debug.Assert(entry == _addedEntityStore[oldKey], "not the same EntityEntry"); 
            Debug.Assert((object)oldKey != null, "Cannot fixup a cache entry with a null key.");
            Debug.Assert(oldKey.IsTemporary, "Cannot fixup an entry with a non-temporary key.");
            Debug.Assert(null != _addedEntityStore, "missing added store");
 
            // Construct an EntityKey based on the current, fixed-up values of the entry.
            EntityKey newKey = new EntityKey((EntitySet)entry.EntitySet, (IExtendedDataRecord)entry.CurrentValues); 
 
            ObjectStateEntry existingEntry = FindObjectStateEntry(newKey);
            if (existingEntry != null) 
            {
                if (!existingEntry.IsKeyEntry)
                {
                    // If the fixed-up key conflicts with an existing entry, we throw. 
                    throw EntityUtil.CannotFixUpKeyToExistingValues();
                } 
                newKey = existingEntry.EntityKey; // reuse existing reference 
            }
 
            // remove the relationships based on the temporary key
            RelationshipEntry[] relationshipEnds = entry.GetRelationshipEnds().ToArray();
            foreach (RelationshipEntry relationshipEntry in relationshipEnds)
            { 
                RemoveObjectStateEntryFromDictionary(relationshipEntry, relationshipEntry.State);
            } 
 
            // Remove ObjectStateEntry with old Key and add it back or promote with new key.
            RemoveObjectStateEntryFromDictionary(entry, EntityState.Added); 

            // This is the only scenario where we are allowed to set the EntityKey if it's already non-null
            // If entry.EntityKey is IEntityWithKey, user code will be called
            ResetEntityKey(entry, newKey); 

            // Fixup all relationships for which this key was a participant. 
            entry.UpdateRelationshipEnds(oldKey, (EntityEntry)existingEntry); 

            // add all the relationships back on the new entity key 
            foreach (RelationshipEntry relationshipEntry in relationshipEnds)
            {
                AddRelationshipEntryToDictionary(relationshipEntry, relationshipEntry.State);
            } 

            // Now promote the key entry to a full entry by adding entities to the related ends 
            if (existingEntry != null) 
            {
                // two ObjectStateEntry exist for same newKey, the entity stub must exist in unchanged state 
                Debug.Assert(existingEntry.State == EntityState.Unchanged, "entity stub must be in unchanged state");
                Debug.Assert(existingEntry.IsKeyEntry, "exising entry must be a key entry to promote");
                Debug.Assert(Object.ReferenceEquals(newKey, existingEntry.EntityKey), "should be same key reference");
                PromoteKeyEntry(existingEntry, entry.Entity, null, true, /*setIsLoaded*/ false, /*keyEntryInitialized*/ false, "AcceptChanges"); 

                // leave the entity stub in the unchanged state 
                // the existing entity stub wins 
            }
            else 
            {
                // change the state to "Unchanged"
                AddEntityEntryToDictionary(entry, EntityState.Unchanged);
            } 

            Debug.Assert((null == _addedEntityStore) || !_addedEntityStore.ContainsKey(oldKey), "EntityEntry exists with OldKey"); 
            Debug.Assert((null != _unchangedEntityStore) && _unchangedEntityStore.ContainsKey(newKey), "EntityEntry does not exist with NewKey"); 

            // FEATURE_CHANGE: once we support equality constraints (SQL PT DB 300002154), do recursive fixup. 
        }

        /// 
        /// Resets the EntityKey for this entry.  This method is called 
        /// as part of temporary key fixup. This method is necessary because it is the only
        /// scenario where we allow a new value to be set on a non-null EntityKey. This 
        /// is the only place where we should be setting and clearing _inRelationshipFixup. 
        /// 
        private void ResetEntityKey(ObjectStateEntry entry, EntityKey value) 
        {
            Debug.Assert((object)entry.EntityKey != null, "Cannot reset an entry's key if it hasn't been set in the first place.");
            Debug.Assert(!_inRelationshipFixup, "already _inRelationshipFixup");
            Debug.Assert(entry.EntityKey.IsTemporary, "Cannot reset an entry with a non-temporary key."); 
            Debug.Assert(!entry.EntityKey.Equals(value), "the keys should not be equal");
 
            IEntityWithKey entityWithKey = entry.Entity as IEntityWithKey; 
            if (null != entityWithKey)
            { 
                EntityKey entityKey = entityWithKey.EntityKey;
                if (entityKey == null || value.Equals(entityKey))
                {
                    throw EntityUtil.AcceptChangesEntityKeyIsNotValid(); 
                }
                try 
                { 
                    _inRelationshipFixup = true;
                    entityWithKey.EntityKey = value; // user will have control 
                    EntityUtil.CheckEntityKeysMatch(entityWithKey, value);
                }
                finally
                { 
                    _inRelationshipFixup = false;
                } 
            } 

            // Keeping the entity and entry keys in [....]. 
            entry.EntityKey = value;

            //Internally, entry.EntityKey asserts that entry._entityKey and entityWithKey.EntityKey are equal.
            Debug.Assert(value == entry.EntityKey, "The new key was not set onto the entry correctly"); 
        }
 
        #endregion 

        ///  
        /// Retrieve the corresponding IEntityStateEntry for the given EntityKey.
        /// 
        /// if key is null
        /// if key is not found 
        IEntityStateEntry IEntityStateManager.GetEntityStateEntry(EntityKey key)
        { 
            return (IEntityStateEntry)GetObjectStateEntry(key); 
        }
 
        /// 
        /// Retrieve the corresponding ObjectStateEntry for the given EntityKey.
        /// 
        /// if key is null 
        /// if key is not found
        public ObjectStateEntry GetObjectStateEntry(EntityKey key) 
        { 
            ObjectStateEntry entry;
            if (!TryGetObjectStateEntry(key, out entry)) 
            {
                throw EntityUtil.NoEntryExistForEntityKey();
            }
            return entry; 
        }
 
        ///  
        /// Given an entity, of type object, return the corresponding ObjectStateEntry.
        ///  
        /// 
        /// The corresonding ObjectStateEntry for this object.
        public ObjectStateEntry GetObjectStateEntry(object entity)
        { 
            ObjectStateEntry entry;
            if (!TryGetObjectStateEntry(entity, out entry)) 
            { 
                throw EntityUtil.NoEntryExistsForObject(entity);
            } 
            return entry;
        }

        ///  
        /// Retrieve the corresponding ObjectStateEntry for the given object.
        ///  
        ///  
        /// 
        /// true if the corresponding ObjectStateEntry was found 
        public bool TryGetObjectStateEntry(object entity, out ObjectStateEntry entry)
        {
            entry = null;
            EntityUtil.CheckArgumentNull(entity, "entity"); 
            entry = FindObjectStateEntry(entity);
 
            return entry != null; 
        }
 
        /// 
        /// Retrieve the corresponding IEntityStateEntry for the given EntityKey.
        /// 
        /// true if the corresponding IEntityStateEntry was found 
        /// if key is null
        bool IEntityStateManager.TryGetEntityStateEntry(EntityKey key, out IEntityStateEntry entry) 
        { 
            // Because the passed in IEntityStateEntry reference isn't necessarily an
            // ObjectStateEntry, we have to declare our own local copy, use it for the outparam of 
            // TryGetObjectStateEntry, and then set it onto our outparam if we successfully find
            // something (at that point we know we can cast to IEntityStateEntry), but we just can't
            // cast in the other direction.
            ObjectStateEntry objectStateEntry; 
            bool result = TryGetObjectStateEntry(key, out objectStateEntry);
            entry = (IEntityStateEntry)objectStateEntry; 
            return result; 
        }
 
        /// 
        /// Retrieve the corresponding ObjectStateEntry for the given EntityKey.
        /// 
        /// true if the corresponding ObjectStateEntry was found 
        /// if key is null
        public bool TryGetObjectStateEntry(EntityKey key, out ObjectStateEntry entry) 
        { 
            entry = null; // must set before checking for null key
            EntityUtil.CheckArgumentNull(key, "key"); 
            bool result;
            if (key.IsTemporary)
            {   // only temporary keys exist in the added state
                result = ((null != _addedEntityStore) && _addedEntityStore.TryGetValue(key, out entry)); 
            }
            else 
            {   // temporary keys do not exist in the unchanged, modified, deleted states. 
                result = (((null != _unchangedEntityStore) && _unchangedEntityStore.TryGetValue(key, out entry)) ||
                          ((null != _modifiedEntityStore) && _modifiedEntityStore.TryGetValue(key, out entry)) || 
                          ((null != _deletedEntityStore) && _deletedEntityStore.TryGetValue(key, out entry)));
            }
            Debug.Assert(result == (null != entry), "result and entry mismatch");
            return result; 
        }
 
        /// Retrieve the corresponding ObjectStateEntry for the given EntityKey. 
        internal ObjectStateEntry FindObjectStateEntry(EntityKey key)
        { 
            ObjectStateEntry entry = null;
            if (null != (object)key)
            {
                TryGetObjectStateEntry(key, out entry); 
            }
            return entry; 
        } 

        ///  
        /// Retrieve the corresponding ObjectStateEntry for the given entity.
        /// Returns null if key is unavailable or passed entity is null.
        /// 
        internal ObjectStateEntry FindObjectStateEntry(object entity) 
        {
            ObjectStateEntry entry = null; 
            IEntityWithKey entityWithKey = entity as IEntityWithKey; 

            if (entityWithKey != null) 
            {
                EntityKey entityEntityKey = entityWithKey.EntityKey;
                if (null != (object)entityEntityKey)
                { 
                    TryGetObjectStateEntry(entityEntityKey, out entry);
                } 
            } 
            else
            { 
                TryGetEntryFromKeylessStore(entity, out entry);
            }

            // If entity is detached, then entry.Entity won't have the same object reference. 
            // This can happen if the same entity is loaded with, then without, tracking
            // SQL BU Defect Tracking 520058 
            if (entry != null && !object.ReferenceEquals(entity, entry.Entity)) 
            {
                entry = null; 
            }

            return entry;
        } 

        internal void ChangeState(ObjectStateEntry entry, EntityState oldState, EntityState newState) 
        { 
            bool fireEvent = (!entry.IsRelationship && !entry.IsKeyEntry);  // do not fire event for relationship
            if (newState == EntityState.Detached) 
            {
                // If we're transitioning to detached, completely remove all traces
                // of the entry.
                if (entry.IsRelationship) 
                {
                    DeleteRelationshipFromLookup((RelationshipEntry)entry); 
                } 
                else
                { 
                    // SQLBU 508278: Object State Manager should not allow "dangling" relationships to stay in the state manager.
                    // Remove potential dangling relationships
                    Debug.Assert((object)entry.EntityKey != null, "attached entry must have a key");
                    foreach (ObjectStateEntry relationshipEntry in CopyOfRelationshipsByKey(entry.EntityKey)) 
                    {
                        ChangeState(relationshipEntry, relationshipEntry.State, EntityState.Detached); 
                    } 
                }
 
                // delay removal until RelationshipEnds is done
                RemoveObjectStateEntryFromDictionary(entry, oldState);

                object _entity = entry.Entity; // we have to cache the entity before detaching it totaly so we can fire event 
                entry.Reset();
                if (fireEvent && _entity != null) 
                { 
                    // first notify the view
                    OnEntityDeleted(CollectionChangeAction.Remove, _entity); 
                    OnObjectStateManagerChanged(CollectionChangeAction.Remove, _entity);
                }
            }
            else 
            {
                RemoveObjectStateEntryFromDictionary(entry, oldState); 
 
                if (entry.IsRelationship)
                { 
                    // If we're transitioning to something other than detached, add the
                    // entry to the appropriate dictionary.
                    AddRelationshipEntryToDictionary(entry, newState);
                } 
                else
                { 
                    // If we're transitioning to something other than detached, add the 
                    // entry to the appropriate dictionary.
                    AddEntityEntryToDictionary(entry, newState); 
                }
            }
            if (fireEvent && newState == EntityState.Deleted)
            { 
                // fire collectionChanged event only when an entity is being deleted (this includes deleting an added entity which becomes detached)
                OnEntityDeleted(CollectionChangeAction.Remove, entry.Entity); 
                OnObjectStateManagerChanged(CollectionChangeAction.Remove, entry.Entity); 
            }
        } 

        private void AddRelationshipEntryToDictionary(ObjectStateEntry entry, EntityState state)
        {
            Debug.Assert(entry.IsRelationship, "expecting IsRelationship"); 
            Debug.Assert(null != entry.Wrapper, "null RelationshipWrapper");
            Debug.Assert(entry is RelationshipEntry, "!RelationshipEntry"); 
 
            Dictionary dictionaryToAdd = null;
            switch (state) 
            {
                case EntityState.Unchanged:
                    if (null == _unchangedRelationshipStore)
                    { 
                        _unchangedRelationshipStore = new Dictionary();
                    } 
                    dictionaryToAdd = _unchangedRelationshipStore; 
                    break;
                case EntityState.Added: 
                    if (null == _addedRelationshipStore)
                    {
                        _addedRelationshipStore = new Dictionary();
                    } 
                    dictionaryToAdd = _addedRelationshipStore;
                    break; 
                case EntityState.Deleted: 
                    if (null == _deletedRelationshipStore)
                    { 
                        _deletedRelationshipStore = new Dictionary();
                    }
                    dictionaryToAdd = _deletedRelationshipStore;
                    break; 
                default:
                    Debug.Assert(false, "Invalid state."); 
                    break; 
            }
            Debug.Assert(dictionaryToAdd != null, "Couldn't find the correct relationship dictionary based on entity state."); 
            dictionaryToAdd.Add(entry.Wrapper, entry);
        }

        private void AddEntityEntryToDictionary(ObjectStateEntry entry, EntityState state) 
        {
            Debug.Assert(!entry.IsRelationship, "not expecting IsRelationship"); 
            Debug.Assert(null != (object)entry.EntityKey, "missing EntityKey"); 
            Debug.Assert(entry is EntityEntry, "!EntityEntry");
 
            Dictionary dictionaryToAdd = null;
            switch (state)
            {
                case EntityState.Unchanged: 
                    if (null == _unchangedEntityStore)
                    { 
                        _unchangedEntityStore = new Dictionary(); 
                    }
                    dictionaryToAdd = _unchangedEntityStore; 
                    Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Unchanged state");
                    break;
                case EntityState.Added:
                    if (null == _addedEntityStore) 
                    {
                        _addedEntityStore = new Dictionary(); 
                    } 
                    dictionaryToAdd = _addedEntityStore;
                    Debug.Assert(entry.EntityKey.IsTemporary, "adding non-temporary entity key into Added state"); 
                    break;
                case EntityState.Deleted:
                    if (null == _deletedEntityStore)
                    { 
                        _deletedEntityStore = new Dictionary();
                    } 
                    dictionaryToAdd = _deletedEntityStore; 
                    Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Deleted state");
                    break; 
                case EntityState.Modified:
                    if (null == _modifiedEntityStore)
                    {
                        _modifiedEntityStore = new Dictionary(); 
                    }
                    dictionaryToAdd = _modifiedEntityStore; 
                    Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Modified state"); 
                    break;
                default: 
                    Debug.Assert(false, "Invalid state.");
                    break;

 
            }
            Debug.Assert(dictionaryToAdd != null, "Couldn't find the correct entity dictionary based on entity state."); 
            dictionaryToAdd.Add(entry.EntityKey, entry); 
            AddEntryToKeylessStore(entry.Entity, entry);
 
        }

        private void AddEntryToKeylessStore(object entity, ObjectStateEntry entry)
        { 
            // Add an entry that doesn't implement IEntityWithKey to the keyless lookup.
            // It is used to lookup ObjectStateEntries when all we have is an entity reference. 
            if (null != entry.Entity && !(entry.Entity is IEntityWithKey)) 
            {
                if (null == _keylessEntityStore) 
                {
                    _keylessEntityStore = new Dictionary(new ObjectReferenceEqualityComparer());
                }
                if (!_keylessEntityStore.ContainsKey(entry.Entity)) 
                {
                    _keylessEntityStore.Add(entry.Entity, entry); 
                } 
            }
        } 

        /// 
        /// Removes the given cache entry from the appropriate dictionary, based on
        /// the given state and whether or not the entry represents a relationship. 
        /// 
        private void RemoveObjectStateEntryFromDictionary(ObjectStateEntry entry, EntityState state) 
        { 
            // Determine the appropriate dictionary from which to remove the entry.
            if (entry.IsRelationship) 
            {
                Dictionary dictionaryContainingEntry = null;
                switch (state)
                { 
                    case EntityState.Unchanged:
                        dictionaryContainingEntry = _unchangedRelationshipStore; 
                        break; 
                    case EntityState.Added:
                        dictionaryContainingEntry = _addedRelationshipStore; 
                        break;
                    case EntityState.Deleted:
                        dictionaryContainingEntry = _deletedRelationshipStore;
                        break; 
                    default:
                        Debug.Assert(false, "Invalid state."); 
                        break; 
                }
                Debug.Assert(dictionaryContainingEntry != null, "Couldn't find the correct relationship dictionary based on entity state."); 

                bool result = dictionaryContainingEntry.Remove(entry.Wrapper);
                Debug.Assert(result, "The correct relationship dictionary based on entity state doesn't contain the entry.");
 
                if (0 == dictionaryContainingEntry.Count)
                {   // reduce unused dictionary capacity 
                    switch (state) 
                    {
                        case EntityState.Unchanged: 
                            _unchangedRelationshipStore = null;
                            break;
                        case EntityState.Added:
                            _addedRelationshipStore = null; 
                            break;
                        case EntityState.Deleted: 
                            _deletedRelationshipStore = null; 
                            break;
                    } 
                }
            }
            else
            { 
                Dictionary dictionaryContainingEntry = null;
                switch (state) 
                { 
                    case EntityState.Unchanged:
                        dictionaryContainingEntry = _unchangedEntityStore; 
                        break;
                    case EntityState.Added:
                        dictionaryContainingEntry = _addedEntityStore;
                        break; 
                    case EntityState.Deleted:
                        dictionaryContainingEntry = _deletedEntityStore; 
                        break; 
                    case EntityState.Modified:
                        dictionaryContainingEntry = _modifiedEntityStore; 
                        break;
                    default:
                        Debug.Assert(false, "Invalid state.");
                        break; 
                }
                Debug.Assert(dictionaryContainingEntry != null, "Couldn't find the correct entity dictionary based on entity state."); 
 
                bool result = dictionaryContainingEntry.Remove(entry.EntityKey);
                Debug.Assert(result, "The correct entity dictionary based on entity state doesn't contain the entry."); 
                RemoveEntryFromKeylessStore(entry.Entity);

                if (0 == dictionaryContainingEntry.Count)
                {   // reduce unused dictionary capacity 
                    switch (state)
                    { 
                        case EntityState.Unchanged: 
                            _unchangedEntityStore = null;
                            break; 
                        case EntityState.Added:
                            _addedEntityStore = null;
                            break;
                        case EntityState.Deleted: 
                            _deletedEntityStore = null;
                            break; 
                        case EntityState.Modified: 
                            _modifiedEntityStore = null;
                            break; 
                    }
                }
            }
        } 

        internal void RemoveEntryFromKeylessStore(object entity) 
        { 
            // Remove and entry from the store containing entities not implementing IEntityWithKey
            if (null != entity && !(entity is IEntityWithKey)) 
            {
                bool keylessResult = _keylessEntityStore.Remove(entity);
                Debug.Assert(keylessResult, "The prescribed entry does not exist in _keylessEntityStore");
            } 
        }
 
 

        ///  
        /// If a corresponding StateManagerTypeMetadata exists, it is returned.
        /// Otherwise, a StateManagerTypeMetadata is created and cached.
        /// 
        internal StateManagerTypeMetadata GetOrAddStateManagerTypeMetadata(Type entityType, EntitySet entitySet) 
        {
            Debug.Assert(entityType != null, "entityType cannot be null."); 
            Debug.Assert(entitySet != null, "must have entitySet to correctly qualify Type"); 

            StateManagerTypeMetadata typeMetadata; 
            if (!_metadataMapping.TryGetValue(new EntitySetQualifiedType(entityType, entitySet), out typeMetadata))
            {
                // GetMap doesn't have a mechanism to qualify identity with EntityContainerName
                // This is unimportant until each EntityContainer can have its own ObjectTypeMapping. 
                typeMetadata = AddStateManagerTypeMetadata(entitySet, (ObjectTypeMapping)
                    MetadataWorkspace.GetMap(entityType.FullName, DataSpace.OSpace, DataSpace.OCSpace)); 
            } 
            return typeMetadata;
        } 

        /// 
        /// If a corresponding StateManagerTypeMetadata exists, it is returned.
        /// Otherwise, a StateManagerTypeMetadata is created and cached. 
        /// 
        internal StateManagerTypeMetadata GetOrAddStateManagerTypeMetadata(EdmType edmType) 
        { 
            Debug.Assert(edmType != null, "edmType cannot be null.");
            Debug.Assert(Helper.IsEntityType(edmType) || 
                         Helper.IsComplexType(edmType),
                         "only expecting ComplexType or EntityType");

            StateManagerTypeMetadata typeMetadata; 
            if (!_metadataStore.TryGetValue(edmType, out typeMetadata))
            { 
                typeMetadata = AddStateManagerTypeMetadata(edmType, (ObjectTypeMapping) 
                    MetadataWorkspace.GetMap(edmType, DataSpace.OCSpace));
            } 
            return typeMetadata;
        }

        ///  
        /// Creates an instance of StateManagerTypeMetadata from the given EdmType and ObjectMapping,
        /// and stores it in the metadata cache.  The new instance is returned. 
        ///  
        private StateManagerTypeMetadata AddStateManagerTypeMetadata(EntitySet entitySet, ObjectTypeMapping mapping)
        { 
            Debug.Assert(null != entitySet, "null entitySet");
            Debug.Assert(null != mapping, "null mapping");

            EdmType edmType = mapping.EdmType; 
            Debug.Assert(Helper.IsEntityType(edmType) ||
                         Helper.IsComplexType(edmType), 
                         "not Entity or complex type"); 

            StateManagerTypeMetadata typeMetadata; 
            if (!_metadataStore.TryGetValue(edmType, out typeMetadata))
            {
                typeMetadata = new StateManagerTypeMetadata(edmType, mapping);
                _metadataStore.Add(edmType, typeMetadata); 
            }
            _metadataMapping.Add(new EntitySetQualifiedType(mapping.ClrType.ClrType, entitySet), typeMetadata); 
            return typeMetadata; 
        }
 
        private StateManagerTypeMetadata AddStateManagerTypeMetadata(EdmType edmType, ObjectTypeMapping mapping)
        {
            Debug.Assert(null != edmType, "null EdmType");
            Debug.Assert(Helper.IsEntityType(edmType) || 
                         Helper.IsComplexType(edmType),
                         "not Entity or complex type"); 
 
            StateManagerTypeMetadata typeMetadata = new StateManagerTypeMetadata(edmType, mapping);
            _metadataStore.Add(edmType, typeMetadata); 
            return typeMetadata;
        }

        // Methods and properties used by recovery code in ObjectContext.AttachTo() 
        internal void BeginAttachTracking()
        { 
            Debug.Assert(this._promotedRelationships == null && this._promotedKeyEntries == null, 
                "recovery collections should be null");
 
            _promotedRelationships = new Dictionary>();
            _promotedKeyEntries = new Dictionary();
        }
 
        internal void EndAttachTracking()
        { 
            Debug.Assert(this._promotedRelationships != null && this._promotedKeyEntries != null, 
                "recovery collections should be not null");
 
            _promotedRelationships = null;
            _promotedKeyEntries = null;
        }
 
        internal bool IsAttachTracking
        { 
            get 
            {
                Debug.Assert((this._promotedRelationships == null) == (this._promotedKeyEntries == null), 
                    "state of both recovery collections should be the same");
                return this._promotedRelationships != null;
            }
        } 
    }
 
    internal sealed class ObjectReferenceEqualityComparer : IEqualityComparer 
    {
        bool IEqualityComparer.Equals(object x, object y) 
        {
            return (Object.ReferenceEquals(x,y));
        }
 
        int IEqualityComparer.GetHashCode(object obj)
        { 
            return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); 
        }
    } 
}

// 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