RelationshipManager.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataEntity / System / Data / Objects / DataClasses / RelationshipManager.cs / 4 / RelationshipManager.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 
using System.Data.Common;
using System.Data.Common.Utils; 
using System.Collections.Generic;
using System.Diagnostics;
using System.Data.Mapping;
using System.Data.Metadata.Edm; 
using System.Reflection;
using System.Collections; 
using System.Runtime.Serialization; 
using System.ComponentModel;
 
namespace System.Data.Objects.DataClasses
{
    /// 
    /// Container for the lazily created relationship navigation 
    /// property objects (collections and refs).
    ///  
    [Serializable] 
    public class RelationshipManager
    { 
        // ------------
        // Constructors
        // ------------
 
        // This method is private in order to force all creation of this
        // object to occur through the public static Create method. 
        // See comments on that method for more details. 
        private RelationshipManager(IEntityWithRelationships owner)
        { 
            EntityUtil.CheckArgumentNull(owner, "owner");
            _owner = owner;
        }
 
        // ------
        // Fields 
        // ------ 

        // The following fields are serialized.  Adding or removing a serialized field is considered 
        // a breaking change.  This includes changing the field type or field name of existing
        // serialized fields. If you need to make this kind of change, it may be possible, but it
        // will require some custom serialization/deserialization code.
        private readonly IEntityWithRelationships _owner; 
        private List _relationships;
 
        [NonSerialized] 
        private ObjectContext _context;
        [NonSerialized] 
        private MergeOption _mergeOption;
        [NonSerialized]
        private bool _nodeVisited;
 

        // ---------- 
        // Properties 
        // ----------
        internal List Relationships 
        {
            get
            {
                if (null == _relationships) 
                {
                    _relationships = new List(); 
                } 
                return _relationships;
            } 
        }

        /// 
        /// Returns the current ObjectContext for this class. 
        /// 
        internal ObjectContext Context 
        { 
            get
            { 
                return _context;
            }
        }
 
        internal MergeOption MergeOption
        { 
            get 
            {
                return _mergeOption; 
            }
        }

        ///  
        /// this flag is used to keep track of nodes which have
        /// been visited. Currently used for Exclude operation. 
        ///  
        internal bool NodeVisited
        { 
            get
            {
                return _nodeVisited;
            } 
            set
            { 
                _nodeVisited = value; 
            }
        } 

        // -------
        // Methods
        // ------- 

        ///  
        /// Factory method to create a new RelationshipManager object. 
        ///
        /// Used by data classes that support relationships. If the change tracker 
        /// requests the RelationshipManager property and the data class does not
        /// already have a reference to one of these objects, it calls this method
        /// to create one, then saves a reference to that object. On subsequent accesses
        /// to that property, the data class should return the saved reference. 
        ///
        /// The reason for using a factory method instead of a public constructor is to 
        /// emphasize that this is not something you would normally call outside of a data class. 
        /// By requiring that these objects are created via this method, developers should
        /// give more thought to the operation, and will generally only use it when 
        /// they explicitly need to get an object of this type. It helps define the intended usage.
        /// 
        /// Reference to the entity that is calling this method
        ///  
        public static RelationshipManager Create(IEntityWithRelationships owner)
        { 
            return new RelationshipManager(owner); 
        }
 
        /// 
        /// Get the collection of entities related to the current entity using the specified
        /// combination of relationship name, source role name, and target role name
        /// This is private because it should only be called by the public GetRelatedEnd method. 
        /// 
        /// Type of the entity in the source role (same as the type of this) 
        /// Type of the entity in the target role 
        /// CSpace-qualified name of the relationship to navigate
        /// Name of the source role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship.
        /// Multiplicity of the source role. RelationshipMultiplicity.OneToOne and RelationshipMultiplicity.Zero are both
        /// accepted for a reference end, and RelationshipMultiplicity.Many is accepted for a collection
        /// Collection of related entities of type TTargetEntity 
        private EntityCollection GetRelatedCollection(string relationshipName, string sourceRoleName, string targetRoleName,
            RelationshipMultiplicity sourceRoleMultiplicity, RelatedEnd existingRelatedEnd) 
                where TSourceEntity : class, IEntityWithRelationships 
                where TTargetEntity : class, IEntityWithRelationships
        { 
            EntityCollection collection;
            RelatedEnd relatedEnd;
            TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd);
 
            if (existingRelatedEnd == null)
            { 
                if (relatedEnd != null) 
                {
                    collection = relatedEnd as EntityCollection; 
                    // Because this is a private method that will only be called for target roles that actually have a
                    // multiplicity that works with EntityReference, this should never be null. If the user requests
                    // a collection or reference and it doesn't match the target role multiplicity, it will be detected
                    // in the public GetRelatedCollection or GetRelatedReference 
                    Debug.Assert(collection != null, "should never receive anything but an EntityCollection here");
                    return collection; 
                } 
                else
                { 
                    RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName);
                    return CreateRelatedEnd(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection;
                }
            } 
            else
            { 
                // There is no need to supress events on the existingRelatedEnd because setting events on a disconnected 
                // EntityCollection is an InvalidOperation
                Debug.Assert(existingRelatedEnd._onAssociationChanged == null, "Disconnected RelatedEnd had events"); 

                if (relatedEnd != null)
                {
                    _relationships.Remove(relatedEnd); 
                }
 
                RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName); 
                collection = CreateRelatedEnd(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection;
 
                if (collection != null)
                {
                    bool doCleanup = true;
                    try 
                    {
                        RemergeCollections(relatedEnd as EntityCollection, collection); 
                        doCleanup = false; 
                    }
                    finally 
                    {
                        // An error occured so we need to put the previous relatedEnd back into the RelationshipManager
                        if (doCleanup && relatedEnd != null)
                        { 
                            _relationships.Remove(collection);
                            _relationships.Add(relatedEnd); 
                        } 
                    }
                } 
                return collection;
            }
        }
 
        /// 
        /// Re-merge items from collection so that relationship fixup is performed. 
        /// Ensure that any items in previous collection are excluded from the re-merge 
        /// 
        ///  
        /// The previous EntityCollection containing items that have already had fixup performed
        /// The new EntityCollection
        private void RemergeCollections(EntityCollection previousCollection,
            EntityCollection collection) 
                where TTargetEntity : class, IEntityWithRelationships
        { 
            Debug.Assert(collection != null, "collection is null"); 
            // If there is a previousCollection, we only need to merge the items that are
            // in the collection but not in the previousCollection 
            // Ensure that all of the items in the previousCollection are already in the new collection

            int relatedEntityCount = 0;
 
            // We will be modifing the collection's enumerator, so we need to make a copy of it
            List tempEntities = new List(collection.Count); 
            foreach (object entity in collection) 
            {
                tempEntities.Add((IEntityWithRelationships)entity); 
            }

            // Iterate through the entities that require merging
            // If the previousCollection already contained the entity, no additional work is needed 
            // If the previousCollection did not contain the entity,
            //   then remove it from the collection and re-add it to force relationship fixup 
            foreach (IEntityWithRelationships entity in tempEntities) 
            {
                bool requiresMerge = true; 
                if (previousCollection != null)
                {
                    // There is no need to merge and do fixup if the entity was already in the previousCollection because
                    // fixup would have already taken place when it was added to the previousCollection 
                    if (previousCollection.ContainsEntity(entity))
                    { 
                        relatedEntityCount++; 
                        requiresMerge = false;
                    } 
                }

                if (requiresMerge)
                { 
                    // Remove and re-add the item to the collections to force fixup
                    ((IRelatedEnd)collection).Remove(entity); 
                    ((IRelatedEnd)collection).Add(entity); 
                }
            } 

            // Ensure that all of the items in the previousCollection are already in the new collection
            if (previousCollection != null && relatedEntityCount != previousCollection.Count)
            { 
                throw EntityUtil.CannotRemergeCollections();
            } 
        } 

        ///  
        /// Get the entity reference of a related entity using the specified
        /// combination of relationship name, source role name, and target role name
        /// This is private because it should only be called by the public GetRelatedEnd method.
        ///  
        /// CSpace-qualified name of the relationship to navigate
        /// Name of the source role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Multiplicity of the source role. RelationshipMultiplicity.OneToOne and RelationshipMultiplicity.Zero are both
        /// accepted for a reference end, and RelationshipMultiplicity.Many is accepted for a collection 
        /// Reference for related entity of type TTargetEntity
        private EntityReference GetRelatedReference(string relationshipName, string sourceRoleName, string targetRoleName,
            RelationshipMultiplicity sourceRoleMultiplicity, RelatedEnd existingRelatedEnd)
                where TSourceEntity : class, IEntityWithRelationships 
                where TTargetEntity : class, IEntityWithRelationships
        { 
            EntityReference entityRef; 
            RelatedEnd relatedEnd;
 
            if (TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd))
            {
                entityRef = relatedEnd as EntityReference;
                // Because this is a private method that will only be called for target roles that actually have a 
                // multiplicity that works with EntityReference, this should never be null. If the user requests
                // a collection or reference and it doesn't match the target role multiplicity, it will be detected 
                // in the public GetRelatedCollection or GetRelatedReference 
                Debug.Assert(entityRef != null, "should never receive anything but an EntityReference here");
                return entityRef; 
            }
            else
            {
                RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName); 
                return CreateRelatedEnd(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.One, existingRelatedEnd) as EntityReference;
            } 
        } 

        ///  
        /// Returns either an EntityCollection or EntityReference of the correct type for the specified target role in a relationship
        /// This is intended to be used in scenarios where the user doesn't have full metadata, including the static type
        /// information for both ends of the relationship. This metadata is specified in the EdmRelationshipRoleAttribute
        /// on each entity type in the relationship, so the metadata system can retrieve it based on the supplied relationship 
        /// name and target role name.
        ///  
        /// Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not. 
        /// Target role to use to retrieve the other end of relationshipName
        /// IRelatedEnd representing the EntityCollection or EntityReference that was retrieved 
        public IRelatedEnd GetRelatedEnd(string relationshipName, string targetRoleName)
        {
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 

            // Get the AssociationType from metadata. This will contain all of the ospace metadata for this relationship 
            string fullSearchName; 
            AssociationType relationship = GetRelationshipType(_owner.GetType(), relationshipName, out fullSearchName);
 
            return GetRelatedEndInternal(relationshipName, targetRoleName, /*existingRelatedEnd*/ null, relationship);
        }

        internal bool TryGetRelatedEnd(string relationshipName, string targetRoleName, out IRelatedEnd relatedEnd) 
        {
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName"); 
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 
            relatedEnd = null;
 
            // Get the AssociationType from metadata. This will contain all of the ospace metadata for this relationship
            string fullSearchName;
            AssociationType relationship;
            if(TryGetRelationshipType(_owner.GetType(), relationshipName, out fullSearchName, out relationship)) 
            {
                relatedEnd = GetRelatedEndInternal(relationshipName, targetRoleName, /*existingRelatedEnd*/ null, relationship, false); 
                return relatedEnd != null; 
            }
            return false; 
        }

        private IRelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship)
        { 
            return GetRelatedEndInternal(relationshipName, targetRoleName, existingRelatedEnd, relationship, true);
        } 
 
        private IRelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship, bool throwOnError)
        { 
            Debug.Assert(relationshipName != null, "null relationshipNameFromUser");
            Debug.Assert(targetRoleName != null, "null targetRoleName");
            // existingRelatedEnd can be null if we are not trying to initialize an existing end
            Debug.Assert(relationship != null, "null relationshipType"); 

            AssociationEndMember sourceEnd; 
            AssociationEndMember targetEnd; 
            Debug.Assert(relationship.AssociationEndMembers.Count == 2, "Only 2-way relationships are currently supported");
 
            IRelatedEnd result = null;

            // There can only be two ends because we don't support n-way relationships -- figure out which end is the target and which is the source
            // If we want to support n-way relationships in the future, we will need a different overload of GetRelatedEnd that takes the source role name as well 
            if (relationship.AssociationEndMembers.TryGetValue(targetRoleName, false, out targetEnd))
            { 
                sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetEnd); 
            }
            else 
            {
                if (throwOnError)
                {
                    throw EntityUtil.InvalidTargetRole(relationshipName, targetRoleName, "targetRoleName"); 
                }
                else 
                { 
                    return result;
                } 
            }

            // Validate that the source type matches the type of the owner
            EntityType sourceEntityType = MetadataHelper.GetEntityTypeForEnd(sourceEnd); 
            Debug.Assert(sourceEntityType.DataSpace == DataSpace.OSpace && sourceEntityType.ClrType != null, "sourceEntityType must contain an ospace type");
            Type sourceType = sourceEntityType.ClrType; 
            if (!(sourceType.IsAssignableFrom(_owner.GetType()))) 
            {
                if (throwOnError) 
                {
                    throw EntityUtil.OwnerIsNotSourceType(_owner.GetType().FullName, sourceType.FullName, sourceEnd.Name, relationshipName);
                }
            } 
            else if (VerifyRelationship(relationship, sourceEnd.Name, throwOnError))
            { 
                // Call a dynamic method that will call either GetRelatedCollection or GetRelatedReference for this relationship 
                result = LightweightCodeGenerator.GetRelatedEnd(this, sourceEnd, targetEnd, existingRelatedEnd);
            } 
            return result;
        }

        ///  
        /// Takes an existing EntityReference that was created with the default constructor and initializes it using the provided relationship and target role names.
        /// This method is designed to be used during deserialization only, and will throw an exception if the provided EntityReference has already been initialized, 
        /// if the relationship manager already contains a relationship with this name and target role, or if the relationship manager is already attached to a ObjectContext. 
        /// 
        /// Type of the entity represented by targetRoleName 
        /// 
        /// 
        /// 
        [Browsable(false)] 
        public void InitializeRelatedReference(string relationshipName, string targetRoleName, EntityReference entityReference)
            where TTargetEntity : class, IEntityWithRelationships 
        { 
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 
            EntityUtil.CheckArgumentNull(entityReference, "entityReference");

            if (entityReference.Owner != null)
            { 
                throw EntityUtil.ReferenceAlreadyInitialized();
            } 
 
            if (this.Context != null && _mergeOption != MergeOption.NoTracking)
            { 
                throw EntityUtil.RelationshipManagerAttached();
            }

            // We need the CSpace-qualified name in order to determine if this relationship already exists, so look it up. 
            // If the relationship doesn't exist, we will use this type information to determine how to initialize the reference
            string fullRelationshipName; 
            AssociationType relationship = GetRelationshipType(_owner.GetType(), relationshipName, out fullRelationshipName); 

            RelatedEnd relatedEnd; 
            if (TryGetCachedRelatedEnd(fullRelationshipName, targetRoleName, out relatedEnd))
            {
                // For some serialization scenarios, we have to allow replacing a related end that we already know about, but in those scenarios
                // the end is always empty, so we can further restrict the user calling method method directly by doing this extra validation 
                if (!relatedEnd.IsEmpty())
                { 
                    entityReference.InitializeWithValue(relatedEnd); 
                }
                _relationships.Remove(relatedEnd); 
            }

            EntityReference reference = GetRelatedEndInternal(relationshipName, targetRoleName, entityReference, relationship) as EntityReference;
            if (reference == null) 
            {
                throw EntityUtil.ExpectedReferenceGotCollection(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            } 
        }
 
        /// 
        /// Takes an existing EntityCollection that was created with the default constructor and initializes it using the provided relationship and target role names.
        /// This method is designed to be used during deserialization only, and will throw an exception if the provided EntityCollection has already been initialized,
        /// or if the relationship manager is already attached to a ObjectContext. 
        /// 
        /// Type of the entity represented by targetRoleName 
        ///  
        /// 
        ///  
        [Browsable(false)]
        public void InitializeRelatedCollection(string relationshipName, string targetRoleName, EntityCollection entityCollection)
            where TTargetEntity : class, IEntityWithRelationships
        { 
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 
            EntityUtil.CheckArgumentNull(entityCollection, "entityCollection"); 

            if (entityCollection.Owner != null) 
            {
                throw EntityUtil.CollectionAlreadyInitialized();
            }
 
            if (this.Context != null && _mergeOption != MergeOption.NoTracking)
            { 
                throw EntityUtil.CollectionRelationshipManagerAttached(); 
            }
 
            // We need the CSpace-qualified name in order to determine if this relationship already exists, so look it up.
            // If the relationship doesn't exist, we will use this type information to determine how to initialize the reference
            string fullRelationshipName;
            AssociationType relationship = GetRelationshipType(_owner.GetType(), relationshipName, out fullRelationshipName); 

            EntityCollection collection = GetRelatedEndInternal(relationshipName, targetRoleName, entityCollection, relationship) as EntityCollection; 
            if (collection == null) 
            {
                throw EntityUtil.ExpectedCollectionGotReference(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            }
        }

        private bool TryGetRelationshipType(Type entityClrType, string relationshipName, out string fullSearchName, out AssociationType associationType) 
        {
            ObjectItemCollection objectItemCollection = null; 
            if (_context != null && _context.MetadataWorkspace != null) 
            {
                objectItemCollection = (ObjectItemCollection)_context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace); 
            }

            if (objectItemCollection != null)
            { 
                associationType = objectItemCollection.GetRelationshipType(entityClrType, relationshipName, out fullSearchName);
            } 
            else 
            {
                associationType = ObjectItemCollection.GetRelationshipTypeExpensiveWay(entityClrType, relationshipName, out fullSearchName); 
            }

            return (associationType != null);
        } 

        private AssociationType GetRelationshipType(Type entityClrType, string relationshipName, out string fullSearchName) 
        { 
            AssociationType associationType = null;
            if (!TryGetRelationshipType(entityClrType, relationshipName, out fullSearchName, out associationType)) 
            {
                throw EntityUtil.UnableToFindRelationshipTypeInMetadata(fullSearchName, "relationshipName");
            }
            return associationType; 
        }
 
        ///  
        /// Retrieves the AssociationEndMembers that corespond to the target end of a relationship
        /// given a specific CLR type that exists on the source end of a relationship 
        /// Note: this method can be very expensive if this RelationshipManager is not attached to an
        /// ObjectContext because no OSpace Metadata is available
        /// 
        /// A CLR type that is on the source role of the relationship 
        /// The OSpace EntityType that represents this CLR type
        private IEnumerable GetAllTargetEnds(Type entityClrType) 
        { 
            ObjectItemCollection objectItemCollection = null;
            if (_context != null && _context.MetadataWorkspace != null) 
            {
                objectItemCollection = (ObjectItemCollection)_context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace);
            }
 
            IEnumerable associations = null;
            if (objectItemCollection != null) 
            { 
                // Metadata is available
                associations = objectItemCollection.GetItems(); 
            }
            else
            {
                // No metadata is available, attempt to load the metadata on the fly to retrieve the AssociationTypes 
                associations = ObjectItemCollection.GetAllRelationshipTypesExpensiveWay(entityClrType.Assembly);
            } 
 
            foreach (AssociationType association in associations)
            { 
                // Check both ends for the presence of the source CLR type
                RefType referenceType = association.AssociationEndMembers[0].TypeUsage.EdmType as RefType;
                if (referenceType != null && referenceType.ElementType.ClrType.IsAssignableFrom(entityClrType))
                { 
                    // Return the target end
                    yield return association.AssociationEndMembers[1]; 
                } 

                referenceType = association.AssociationEndMembers[1].TypeUsage.EdmType as RefType; 
                if (referenceType != null && referenceType.ElementType.ClrType.IsAssignableFrom(entityClrType))
                {
                    // Return the target end
                    yield return association.AssociationEndMembers[0]; 
                }
            } 
            yield break; 
        }
 

        private bool VerifyRelationship(AssociationType relationship, string sourceEndName, bool throwOnError)
        {
            if (_context == null) 
            {
                return true;// if not added to cache, can not decide- for now 
            } 

            EntityKey ownerKey = null; 
            ownerKey = ObjectContext.FindEntityKey(_owner, _context);

            if (null == (object)ownerKey)
            { 
                return true; // if not added to cache, can not decide- for now
            } 
 
            TypeUsage associationTypeUsage;
            AssociationSet association = null; 
            bool isVerified = true;

            // First, get the CSpace association type from the relationship name, since the helper method looks up
            // association set in the CSpace, since there is no Entity Container in the OSpace 
            if (_context.Perspective.TryGetTypeByName(relationship.FullName, false/*ignoreCase*/, out associationTypeUsage))
            { 
                //Get the entity container first 
                EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
                    ownerKey.EntityContainerName, DataSpace.CSpace); 
                EntitySet entitySet;

                // Get the association set from the entity container, given the association type it refers to, and the entity set
                // name that the source end refers to 
                association = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, ownerKey.EntitySetName,
                    (AssociationType)associationTypeUsage.EdmType, sourceEndName, out entitySet); 
 
                if (association == null)
                { 
                    if (throwOnError)
                    {
                        string fullyQualifiedRelationshipName = relationship.FullName;
                        throw EntityUtil.NoRelationshipSetMatched(fullyQualifiedRelationshipName); 
                    }
                    else 
                    { 
                        isVerified = false;
                    } 
                }
                else
                {
                    Debug.Assert(association.AssociationSetEnds[sourceEndName].EntitySet == entitySet, "AssociationSetEnd does have the matching EntitySet"); 
                }
            } 
            return isVerified; 
        }
 
        /// 
        /// Get the collection of a related entity using the specified
        /// combination of relationship name, and target role name.
        /// Only supports 2-way relationships. 
        /// 
        /// Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Collection of entities of type TTargetEntity
        public EntityCollection GetRelatedCollection(string relationshipName, string targetRoleName) 
            where TTargetEntity : class, IEntityWithRelationships
        {
            EntityCollection collection = GetRelatedEnd(relationshipName, targetRoleName) as EntityCollection;
            if (collection == null) 
            {
                throw EntityUtil.ExpectedCollectionGotReference(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            } 
            return collection;
        } 

        /// 
        /// Get the entity reference of a related entity using the specified
        /// combination of relationship name, and target role name. 
        /// Only supports 2-way relationships.
        ///  
        /// Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship.
        /// Reference for related entity of type TTargetEntity 
        public EntityReference GetRelatedReference(string relationshipName, string targetRoleName)
            where TTargetEntity : class, IEntityWithRelationships
        {
            EntityReference reference = GetRelatedEnd(relationshipName, targetRoleName) as EntityReference; 
            if (reference == null)
            { 
                throw EntityUtil.ExpectedReferenceGotCollection(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            }
            return reference; 
        }

        /// 
        /// Gets collection or ref of related entity for a particular navigation. 
        /// 
        ///  
        /// Describes the relationship and navigation direction 
        /// 
        ///  
        /// Encapsulates information about the other end's type and cardinality,
        /// and knows how to create the other end
        /// 
        ///  
        internal IRelatedEnd GetRelatedEnd(RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
        { 
            RelatedEnd relatedEnd; 

            if (TryGetCachedRelatedEnd(navigation.RelationshipName, navigation.To, out relatedEnd)) 
            {
                return relatedEnd;
            }
            else 
            {
                relatedEnd = relationshipFixer.CreateSourceEnd(navigation, this); 
                Debug.Assert(null != relatedEnd, "CreateSourceEnd should always return a valid RelatedEnd"); 

                return relatedEnd; 
            }
        }

        ///  
        /// Factory method for creating new related ends
        ///  
        /// Type of the source end 
        /// Type of the target end
        /// RelationshipNavigation to be set on the new RelatedEnd 
        /// Multiplicity of the source role
        /// Multiplicity of the target role
        /// An existing related end to initialize instead of creating a new one
        /// new EntityCollection or EntityReference, depending on the specified target multiplicity 
        internal RelatedEnd CreateRelatedEnd(RelationshipNavigation navigation, RelationshipMultiplicity sourceRoleMultiplicity, RelationshipMultiplicity targetRoleMultiplicity, RelatedEnd existingRelatedEnd)
            where TSourceEntity : class, IEntityWithRelationships 
            where TTargetEntity : class, IEntityWithRelationships 
        {
            IRelationshipFixer relationshipFixer = new RelationshipFixer(sourceRoleMultiplicity, targetRoleMultiplicity); 
            RelatedEnd relatedEnd = null;
            switch (targetRoleMultiplicity)
            {
                case RelationshipMultiplicity.ZeroOrOne: 
                case RelationshipMultiplicity.One:
                    if (existingRelatedEnd != null) 
                    { 
                        Debug.Assert(_context == null || _mergeOption == MergeOption.NoTracking, "Expected null context when initializing an existing related end");
                        existingRelatedEnd.InitializeRelatedEnd(_owner, navigation, relationshipFixer); 
                        relatedEnd = existingRelatedEnd;
                    }
                    else
                    { 
                        relatedEnd = new EntityReference(_owner, navigation, relationshipFixer);
                    } 
                    break; 
                case RelationshipMultiplicity.Many:
                    if (existingRelatedEnd != null) 
                    {
                        Debug.Assert(_context == null || MergeOption == MergeOption.NoTracking, "Expected null context or NoTracking when initializing an existing related end");
                        existingRelatedEnd.InitializeRelatedEnd(_owner, navigation, relationshipFixer);
                        relatedEnd = existingRelatedEnd; 
                    }
                    else 
                    { 
                        relatedEnd = new EntityCollection(_owner, navigation, relationshipFixer);
                    } 
                    break;
                default:
                    throw EntityUtil.InvalidEnumerationValue(typeof(RelationshipMultiplicity), (int)targetRoleMultiplicity);
            } 

            // Verify that we can attach the context successfully before adding to our list of relationships 
            if (_context != null) 
            {
                relatedEnd.AttachContext(_context, _mergeOption); 
            }

            Relationships.Add(relatedEnd);
 
            return relatedEnd;
        } 
 
        /// 
        /// Returns an enumeration of all the related ends.  The enumeration 
        /// will be empty if the relationships have not been populated.
        /// 
        public IEnumerable GetAllRelatedEnds()
        { 
            if(_owner != null)
            { 
                foreach (AssociationEndMember endMember in GetAllTargetEnds(_owner.GetType())) 
                {
                    yield return GetRelatedEnd(endMember.DeclaringType.FullName, endMember.Name); 
                }
            }
            yield break;
        } 

        [OnSerializingAttribute] 
        public void OnSerializing(StreamingContext context) 
        {
            // If we are attached to a context we need to go fixup the detached entity key on any EntityReferences 
            if (_context != null && _mergeOption != MergeOption.NoTracking)
            {
                // For all of the Added or Unchanged relationships that this owner participates in, if the relationship is a reference
                // set the detached entity key to the EntityKey of the target member in the relationship 
                EntityKey ownerKey = _context.ObjectStateManager.GetEntityKey(_owner);
                foreach (ObjectStateEntry relationshipEntry in _context.ObjectStateManager.FindRelationshipsByKey(ownerKey)) 
                { 
                    if (relationshipEntry.State != EntityState.Deleted)
                    { 
                        EntityKey targetKey = relationshipEntry.Wrapper.GetOtherEntityKey(ownerKey);
                        EntityKey detachedEntityKey = targetKey;
                        if (!RelatedEnd.IsValidEntityKeyType(targetKey))
                        { 
                            detachedEntityKey = null;
                        } 
 
                        AssociationEndMember targetMember = relationshipEntry.Wrapper.GetAssociationEndMember(targetKey);
 
                        if (targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
                            targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne)
                        {
                            EntityReference entityRef = GetRelatedEnd(targetMember.DeclaringType.FullName, targetMember.Name) as EntityReference; 
                            Debug.Assert(entityRef != null, "Expected EntityReference since target end multiplicity is one");
                            entityRef.DetachedEntityKey = detachedEntityKey; 
                        } 
                    }
                } 
            }
            // else the detached entity key is already the correct value for detached entities
        }
 
        // ----------------
        // Internal Methods 
        // ---------------- 

        internal void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption) 
        {
            Debug.Assert(null != entitySet, "entitySet should not be null");
            Debug.Assert(null != context, "context");
            Debug.Assert(MergeOption.NoTracking == mergeOption || 
                         MergeOption.AppendOnly == mergeOption,
                         "mergeOption"); 
 
            _context = context;
            _mergeOption = mergeOption; 

            if (null != _relationships)
            {
                foreach(RelatedEnd relatedEnd in _relationships) 
                {
                    relatedEnd.AttachContext(context, entitySet, mergeOption); 
                } 
            }
        } 

        internal void ResetContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
        {
            Debug.Assert(null != entitySet, "entitySet should not be null"); 
            Debug.Assert(null != context, "context");
            Debug.Assert(MergeOption.NoTracking == mergeOption || 
                         MergeOption.AppendOnly == mergeOption, 
                         "mergeOption");
 
            if (!object.ReferenceEquals(_context, context))
            {
                _context = context;
                _mergeOption = mergeOption; 

                if (null != _relationships) 
                { 
                    foreach (RelatedEnd relatedEnd in _relationships)
                    { 
                        relatedEnd.AttachContext(context, entitySet, mergeOption);
                        foreach (object entity in relatedEnd)
                        {
                            IEntityWithRelationships entityWithRelationships = entity as IEntityWithRelationships; 
                            if (entityWithRelationships != null)
                            { 
                                entityWithRelationships.RelationshipManager.ResetContext(context, relatedEnd.GetTargetEntitySetFromRelationshipSet(), mergeOption); 
                            }
                        } 
                    }
                }
            }
        } 

        internal void DetachContext() 
        { 
            _context = null;
 
            if (null != _relationships)
            {
                foreach(RelatedEnd relatedEnd in _relationships)
                { 
                    relatedEnd.DetachContext();
                } 
            } 
        }
 
        /// 
        /// Add the rest of the graph, attached to this owner, to ObjectStateManager
        /// 
        /// if TRUE, the rest of the graph is attached directly as Unchanged 
        /// without calling AcceptChanges()
        internal void AddRelatedEntitiesToObjectStateManager(bool doAttach) 
        { 
            if (null != _relationships)
            { 
                HashSet promotedEntityKeyRefs = new HashSet();
                bool doCleanup = true;
                try
                { 
                    // Create a copy of this list because with self references, the set of relationships can change
                    List relatedEnds = new List(_relationships); 
                    foreach(RelatedEnd relatedEnd in relatedEnds) 
                    {
                        relatedEnd.Include(/*addRelationshipAsUnchanged*/false, doAttach, promotedEntityKeyRefs); 
                    }
                    doCleanup = false;
                }
                finally 
                {
                    // If error happens, while attaching entity graph to context, clean-up 
                    // is done on the Owner entity and all its relating entities. 
                    if(doCleanup)
                    { 
                        Debug.Assert(this.Context != null && this.Context.ObjectStateManager != null, "Null context or ObjectStateManager");

                        if (this.Context.ObjectStateManager.IsAttachTracking)
                        { 
                            // The graph being attached is connected to graph already existing in the OSM only through "promoted" relationships
                            // (relationships which originally existed only in OSM between key entries and entity entries but later were 
                            // "promoted" to normal relationships in EntityRef/Collection when the key entries were promoted). 
                            // The cleanup code traverse all the graph being added to the OSM, so we have to disconnect it from the graph already
                            // existing in the OSM by degrading promoted relationships. 
                            this.Context.ObjectStateManager.DegradePromotedRelationships();
                        }

                        NodeVisited = true; 
                        RemoveRelatedEntitiesFromObjectStateManager(_owner, promotedEntityKeyRefs);
                        Debug.Assert(promotedEntityKeyRefs.Count == 0, "Haven't cleaned up all of the promoted reference EntityKeys"); 
 
                        ObjectStateEntry entry;
 
                        Debug.Assert(doAttach == (this.Context.ObjectStateManager.IsAttachTracking), "In attach the recovery collection should be not null");

                        if (this.Context.ObjectStateManager.IsAttachTracking &&
                            this.Context.ObjectStateManager.PromotedKeyEntries.TryGetValue(_owner, out entry)) 
                        {
                            // This is executed only in the cleanup code from ObjectContext.AttachTo() 
                            // If the entry was promoted in AttachTo(), it has to be degraded now instead of being deleted. 
                            entry.DegradeEntry();
                        } 
                        else
                        {
                            RelatedEnd.RemoveEntityFromObjectStateManager(_owner);
                        } 
                    }
                } 
            } 
        }
 
        // Method is used to remove all entities and relationships, of a given entity
        // graph, from ObjectStateManager. This method is used when adding entity graph,
        // or a portion of it, raise exception.
        internal static void RemoveRelatedEntitiesFromObjectStateManager(IEntityWithRelationships entity, HashSet promotedEntityKeyRefs) 
        {
            foreach (RelatedEnd relatedEnd in entity.RelationshipManager.Relationships) 
            { 
                // only some of the related ends may have gotten attached, so just skip the ones that weren't
                if (relatedEnd.ObjectContext != null) 
                {
                    Debug.Assert(!relatedEnd.UsingNoTracking, "Shouldn't be touching the state manager with entities that were retrieved with NoTracking");
                    relatedEnd.Exclude(promotedEntityKeyRefs);
                    relatedEnd.DetachContext(); 
                }
            } 
        } 

        // Remove entity from its relationships and do cascade delete if required. 
        // All removed relationships are marked for deletion and all cascade deleted
        // entitites are also marked for deletion.
        internal void RemoveEntityFromRelationships()
        { 
            if (null != _relationships)
            { 
                foreach(RelatedEnd relatedEnd in _relationships) 
                {
                    relatedEnd.RemoveAll(); 
                }
            }
        }
 
        // Removes entity from its relationships.
        // Relationship entries are removed from ObjectStateManager if owner is in Added state 
        // or when owner is "many" end of the relationship 
        internal void DetachEntityFromRelationships(EntityState ownerEntityState)
        { 
            if (null != _relationships)
            {
                foreach(RelatedEnd relatedEnd in _relationships)
                { 
                    relatedEnd.DetachAll(ownerEntityState);
                } 
            } 
        }
 
        //For a given relationship removes passed in entity from owners relationship
        internal void RemoveEntity(RelationshipNavigation navigation, IEntityWithRelationships entity)
        {
            RelatedEnd relatedEnd; 
            if (TryGetCachedRelatedEnd(navigation.RelationshipName, navigation.To, out relatedEnd))
            { 
                ((IRelatedEnd)relatedEnd).Remove(entity); 
            }
        } 

        // Method used to retrieve properties from principal entities.
        // Parameter includeOwnValues means that values from current entity should be also added to "properties"
        // includeOwnValues is false only when this method is called from ObjectStateEntry.AcceptChanges() 
        // Parmeter "visited" is a set containig entities which were already visited during traversing the graph.
        // If _owner already exists in the set, it means that there is a cycle in the graph of relationships with RI Constraints. 
        internal void RetrieveReferentialConstraintProperties(out Dictionary> properties, bool includeOwnValues, HashSet visited) 
        {
            Debug.Assert(_owner != null); 
            Debug.Assert(visited != null);

            // Dictionary< propertyName, >
            properties = new Dictionary>(); 

            EntityKey ownerKey = _context.ObjectStateManager.GetEntityKey(_owner); 
            Debug.Assert((object)ownerKey != null); 

            // Detect circular references 
            if (visited.Contains(_owner))
            {
                throw EntityUtil.CircularRelationshipsWithReferentialConstraints();
            } 
            else
            { 
                visited.Add(_owner); 
            }
 
            // If the key is temporary, get values of referential constraint properties from principal entities
            if (ownerKey.IsTemporary)
            {
                // Find property names which should be retrieved 
                List propertiesToRetrieve;
                bool propertiesToPropagateExist; // not used 
 
                this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToPropagateExist);
 
                if (propertiesToRetrieve != null)
                {
                    // At first try to retrieve properties from entities which are in collections or references.
                    // This is the most common scenario. 
                    // Only if properties couldn't be retrieved this way, try to retrieve properties from related stubs.
 
                    if (_relationships != null) 
                    {
                        foreach(RelatedEnd relatedEnd in _relationships) 
                        {
                            // NOTE: If the following call throws UnableToRetrieveReferentialConstraintProperties,
                            //       it means that properties couldn't be found in indirectly related entities,
                            //       so it doesn't make sense to search for properties in directly related stubs, 
                            //       so exception is not being caught here.
                            relatedEnd.RetrieveReferentialConstraintProperties(properties, visited); 
                        } 
                    }
 
                    // Check if all properties were retrieved.
                    // There are 3 scenarios in which not every expected property can be retrieved:
                    // 1. There is no related entity from which the property is supposed to be retrieved.
                    // 2. Related entity which supposed to contains the property doesn't have fixed entity key. 
                    // 3. Property should be retrieved from related key entry
 
                    if (!CheckIfAllPropertiesWereRetrieved(properties, propertiesToRetrieve)) 
                    {
                        // Properties couldn't be found in entities in collections or refrences. 
                        // Try to find missing properties in related key entries.
                        // This process is slow but it is not a common case.
                        ObjectStateEntry entry = _context.ObjectStateManager.FindObjectStateEntry(ownerKey);
                        Debug.Assert(entry != null, "Owner entry not found in the object state manager"); 
                        entry.RetrieveReferentialConstraintPropertiesFromKeyEntries(properties);
 
                        // Check again if all properties were retrieved. 
                        if (!CheckIfAllPropertiesWereRetrieved(properties, propertiesToRetrieve))
                        { 
                            throw EntityUtil.UnableToRetrieveReferentialConstraintProperties();
                        }
                    }
                } 
            }
 
            // 1. If key is temporary, properties from principal entities were retrieved above. 
            //    The other key properties are properties which are not Dependent end of some Referential Constraint.
            // 2. If key is not temporary and this method was not called from AcceptChanges() - all key values 
            //    of the current entity are added to 'properties'.
            if (!ownerKey.IsTemporary || includeOwnValues) {
                // NOTE this part is never executed when the method is called from ObjectStateManager.AcceptChanges(),
                //      so we don't try to "retrieve" properties from the the same (callers) entity. 
                ObjectStateEntry entry = _context.ObjectStateManager.FindObjectStateEntry(ownerKey);
                Debug.Assert(entry != null, "Owner entry not found in the object state manager"); 
                entry.GetOtherKeyProperties(properties); 
            }
        } 

        // properties dictionary contains name of property, its value and coutner saying how many times this property was retrieved from principal entities
        private static bool CheckIfAllPropertiesWereRetrieved(Dictionary> properties, List propertiesToRetrieve)
        { 
            Debug.Assert(properties != null);
            Debug.Assert(propertiesToRetrieve != null); 
 
            bool isSuccess = true;
 
            List countersCopy = new List();
            ICollection> values = properties.Values;

            // Create copy of counters (needed in case of failure) 
            foreach(KeyValuePair valueCounterPair in values)
            { 
                countersCopy.Add(valueCounterPair.Value.Value); 
            }
 
            foreach(string name in propertiesToRetrieve)
            {
                if(!properties.ContainsKey(name))
                { 
                    isSuccess = false;
                    break; 
                } 

                KeyValuePair valueCounterPair = properties[name]; 
                valueCounterPair.Value.Value = valueCounterPair.Value.Value - 1;
                if (valueCounterPair.Value.Value < 0)
                {
                    isSuccess = false; 
                    break;
                } 
            } 

            // Check if all the coutners equal 0 
            if (isSuccess)
            {
                foreach (KeyValuePair valueCounterPair in values)
                { 
                    if (valueCounterPair.Value.Value != 0)
                    { 
                        isSuccess = false; 
                        break;
                    } 
                }
            }

            // Restore counters in case of failure 
            if (!isSuccess)
            { 
                IEnumerator enumerator = countersCopy.GetEnumerator(); 
                foreach(KeyValuePair valueCounterPair in values)
                { 
                    enumerator.MoveNext();
                    valueCounterPair.Value.Value = enumerator.Current;
                }
            } 

            return isSuccess; 
        } 

 
        // Check consistency between properties of current entity and Principal entities
        // If some of Principal entities don't exist or some property cannot be checked - this is violation of RI Constraints
        internal void CheckReferentialConstraintProperties(EntityKey ownerKey)
        { 
            Debug.Assert(ownerKey != null);
 
            List propertiesToRetrieve; // used to check if the owner is a dependent end of some RI Constraint 
            bool propertiesToPropagateExist;   // used to check if the owner is a principal end of some RI Constraint
            this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToPropagateExist); 

            if ((propertiesToRetrieve != null || propertiesToPropagateExist) &&
                _relationships != null)
            { 
                foreach (RelatedEnd relatedEnd in _relationships)
                { 
                    if (!relatedEnd.CheckReferentialConstraintProperties(ownerKey)) 
                    {
                        throw EntityUtil.InconsistentReferentialConstraintProperties(); 
                    }
                }
            }
        } 

        // ---------------- 
        // Private Methods 
        // ----------------
 
        /// 
        /// Searches the list of relationships for an entry with the specified relationship name and role names
        /// 
        /// CSpace-qualified name of the relationship 
        /// name of the target role
        /// the RelatedEnd if found, otherwise null 
        /// true if the entry found, false otherwise 
        private bool TryGetCachedRelatedEnd(string relationshipName, string targetRoleName, out RelatedEnd relatedEnd)
        { 
            relatedEnd = null;
            if (null != _relationships)
            {
                foreach (RelatedEnd end in _relationships) 
                {
                    RelationshipNavigation relNav = end.RelationshipNavigation; 
                    if (relNav.RelationshipName == relationshipName && relNav.To == targetRoleName) 
                    {
                        relatedEnd = end; 
                        return true;
                    }
                }
            } 
            return false;
        } 
 
        // Find properties which are Dependent/Principal ends of some referential constraint
        // Returned lists are never null. 
        // NOTE This method will be removed when bug 505935 is solved
        internal void FindNamesOfReferentialConstraintProperties(out List propertiesToRetrieve, out bool propertiesToPropagateExist)
        {
            Debug.Assert(_owner != null); 
            EntityKey ownerKey = ObjectStateManager.FindKeyOnEntityWithRelationships(_owner);
            EntityUtil.CheckEntityKeyNull(ownerKey); 
 
            propertiesToRetrieve = null;
            propertiesToPropagateExist = false; 

            ObjectContext context = this.Context;
            EntityUtil.CheckContextNull(context);
            EntitySet entitySet = ownerKey.GetEntitySet(context.MetadataWorkspace); 
            Debug.Assert(entitySet != null, "Unable to find entity set");
 
            // Get association types in which current entity's type is one of the ends. 
            List associations = MetadataHelper.GetAssociationsForEntitySet(entitySet);
 
            // Find key property names which are part of referential integrity constraints
            foreach (AssociationSet association in associations)
            {
                // NOTE ReferentialConstraints collection currently can contain 0 or 1 element 
                foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
                { 
                    if (constraint.ToRole.TypeUsage.EdmType == entitySet.ElementType.GetReferenceType()) 
                    {
                        // lazy creation of the list 
                        propertiesToRetrieve = propertiesToRetrieve ?? new List();
                        foreach(EdmProperty property in constraint.ToProperties)
                        {
                            propertiesToRetrieve.Add(property.Name); 
                        }
                    } 
                    // There are schemas, in which relationship has the same entitySet on both ends 
                    // that is why following 'if' statement is not inside of 'else' of previous 'if' statement
                    if (constraint.FromRole.TypeUsage.EdmType == entitySet.ElementType.GetReferenceType()) 
                    {
                        propertiesToPropagateExist = true;
                    }
                } 
            }
        } 
 
        /// 
        /// Helper method to validate consistency of RelationshipManager instances 
        /// 
        /// entity to compare against
        /// True if entity is the owner of this RelationshipManager, otherwise false
        internal bool IsOwner(IEntityWithRelationships entity) 
        {
            return Object.ReferenceEquals(entity, _owner); 
        } 

        // -------------------- 
        // Internal definitions
        // --------------------

        internal delegate IRelatedEnd GetRelatedEndMethod(RelationshipManager relationshipManager, RelatedEnd existingRelatedEnd); 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 
using System.Data.Common;
using System.Data.Common.Utils; 
using System.Collections.Generic;
using System.Diagnostics;
using System.Data.Mapping;
using System.Data.Metadata.Edm; 
using System.Reflection;
using System.Collections; 
using System.Runtime.Serialization; 
using System.ComponentModel;
 
namespace System.Data.Objects.DataClasses
{
    /// 
    /// Container for the lazily created relationship navigation 
    /// property objects (collections and refs).
    ///  
    [Serializable] 
    public class RelationshipManager
    { 
        // ------------
        // Constructors
        // ------------
 
        // This method is private in order to force all creation of this
        // object to occur through the public static Create method. 
        // See comments on that method for more details. 
        private RelationshipManager(IEntityWithRelationships owner)
        { 
            EntityUtil.CheckArgumentNull(owner, "owner");
            _owner = owner;
        }
 
        // ------
        // Fields 
        // ------ 

        // The following fields are serialized.  Adding or removing a serialized field is considered 
        // a breaking change.  This includes changing the field type or field name of existing
        // serialized fields. If you need to make this kind of change, it may be possible, but it
        // will require some custom serialization/deserialization code.
        private readonly IEntityWithRelationships _owner; 
        private List _relationships;
 
        [NonSerialized] 
        private ObjectContext _context;
        [NonSerialized] 
        private MergeOption _mergeOption;
        [NonSerialized]
        private bool _nodeVisited;
 

        // ---------- 
        // Properties 
        // ----------
        internal List Relationships 
        {
            get
            {
                if (null == _relationships) 
                {
                    _relationships = new List(); 
                } 
                return _relationships;
            } 
        }

        /// 
        /// Returns the current ObjectContext for this class. 
        /// 
        internal ObjectContext Context 
        { 
            get
            { 
                return _context;
            }
        }
 
        internal MergeOption MergeOption
        { 
            get 
            {
                return _mergeOption; 
            }
        }

        ///  
        /// this flag is used to keep track of nodes which have
        /// been visited. Currently used for Exclude operation. 
        ///  
        internal bool NodeVisited
        { 
            get
            {
                return _nodeVisited;
            } 
            set
            { 
                _nodeVisited = value; 
            }
        } 

        // -------
        // Methods
        // ------- 

        ///  
        /// Factory method to create a new RelationshipManager object. 
        ///
        /// Used by data classes that support relationships. If the change tracker 
        /// requests the RelationshipManager property and the data class does not
        /// already have a reference to one of these objects, it calls this method
        /// to create one, then saves a reference to that object. On subsequent accesses
        /// to that property, the data class should return the saved reference. 
        ///
        /// The reason for using a factory method instead of a public constructor is to 
        /// emphasize that this is not something you would normally call outside of a data class. 
        /// By requiring that these objects are created via this method, developers should
        /// give more thought to the operation, and will generally only use it when 
        /// they explicitly need to get an object of this type. It helps define the intended usage.
        /// 
        /// Reference to the entity that is calling this method
        ///  
        public static RelationshipManager Create(IEntityWithRelationships owner)
        { 
            return new RelationshipManager(owner); 
        }
 
        /// 
        /// Get the collection of entities related to the current entity using the specified
        /// combination of relationship name, source role name, and target role name
        /// This is private because it should only be called by the public GetRelatedEnd method. 
        /// 
        /// Type of the entity in the source role (same as the type of this) 
        /// Type of the entity in the target role 
        /// CSpace-qualified name of the relationship to navigate
        /// Name of the source role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship.
        /// Multiplicity of the source role. RelationshipMultiplicity.OneToOne and RelationshipMultiplicity.Zero are both
        /// accepted for a reference end, and RelationshipMultiplicity.Many is accepted for a collection
        /// Collection of related entities of type TTargetEntity 
        private EntityCollection GetRelatedCollection(string relationshipName, string sourceRoleName, string targetRoleName,
            RelationshipMultiplicity sourceRoleMultiplicity, RelatedEnd existingRelatedEnd) 
                where TSourceEntity : class, IEntityWithRelationships 
                where TTargetEntity : class, IEntityWithRelationships
        { 
            EntityCollection collection;
            RelatedEnd relatedEnd;
            TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd);
 
            if (existingRelatedEnd == null)
            { 
                if (relatedEnd != null) 
                {
                    collection = relatedEnd as EntityCollection; 
                    // Because this is a private method that will only be called for target roles that actually have a
                    // multiplicity that works with EntityReference, this should never be null. If the user requests
                    // a collection or reference and it doesn't match the target role multiplicity, it will be detected
                    // in the public GetRelatedCollection or GetRelatedReference 
                    Debug.Assert(collection != null, "should never receive anything but an EntityCollection here");
                    return collection; 
                } 
                else
                { 
                    RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName);
                    return CreateRelatedEnd(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection;
                }
            } 
            else
            { 
                // There is no need to supress events on the existingRelatedEnd because setting events on a disconnected 
                // EntityCollection is an InvalidOperation
                Debug.Assert(existingRelatedEnd._onAssociationChanged == null, "Disconnected RelatedEnd had events"); 

                if (relatedEnd != null)
                {
                    _relationships.Remove(relatedEnd); 
                }
 
                RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName); 
                collection = CreateRelatedEnd(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection;
 
                if (collection != null)
                {
                    bool doCleanup = true;
                    try 
                    {
                        RemergeCollections(relatedEnd as EntityCollection, collection); 
                        doCleanup = false; 
                    }
                    finally 
                    {
                        // An error occured so we need to put the previous relatedEnd back into the RelationshipManager
                        if (doCleanup && relatedEnd != null)
                        { 
                            _relationships.Remove(collection);
                            _relationships.Add(relatedEnd); 
                        } 
                    }
                } 
                return collection;
            }
        }
 
        /// 
        /// Re-merge items from collection so that relationship fixup is performed. 
        /// Ensure that any items in previous collection are excluded from the re-merge 
        /// 
        ///  
        /// The previous EntityCollection containing items that have already had fixup performed
        /// The new EntityCollection
        private void RemergeCollections(EntityCollection previousCollection,
            EntityCollection collection) 
                where TTargetEntity : class, IEntityWithRelationships
        { 
            Debug.Assert(collection != null, "collection is null"); 
            // If there is a previousCollection, we only need to merge the items that are
            // in the collection but not in the previousCollection 
            // Ensure that all of the items in the previousCollection are already in the new collection

            int relatedEntityCount = 0;
 
            // We will be modifing the collection's enumerator, so we need to make a copy of it
            List tempEntities = new List(collection.Count); 
            foreach (object entity in collection) 
            {
                tempEntities.Add((IEntityWithRelationships)entity); 
            }

            // Iterate through the entities that require merging
            // If the previousCollection already contained the entity, no additional work is needed 
            // If the previousCollection did not contain the entity,
            //   then remove it from the collection and re-add it to force relationship fixup 
            foreach (IEntityWithRelationships entity in tempEntities) 
            {
                bool requiresMerge = true; 
                if (previousCollection != null)
                {
                    // There is no need to merge and do fixup if the entity was already in the previousCollection because
                    // fixup would have already taken place when it was added to the previousCollection 
                    if (previousCollection.ContainsEntity(entity))
                    { 
                        relatedEntityCount++; 
                        requiresMerge = false;
                    } 
                }

                if (requiresMerge)
                { 
                    // Remove and re-add the item to the collections to force fixup
                    ((IRelatedEnd)collection).Remove(entity); 
                    ((IRelatedEnd)collection).Add(entity); 
                }
            } 

            // Ensure that all of the items in the previousCollection are already in the new collection
            if (previousCollection != null && relatedEntityCount != previousCollection.Count)
            { 
                throw EntityUtil.CannotRemergeCollections();
            } 
        } 

        ///  
        /// Get the entity reference of a related entity using the specified
        /// combination of relationship name, source role name, and target role name
        /// This is private because it should only be called by the public GetRelatedEnd method.
        ///  
        /// CSpace-qualified name of the relationship to navigate
        /// Name of the source role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Multiplicity of the source role. RelationshipMultiplicity.OneToOne and RelationshipMultiplicity.Zero are both
        /// accepted for a reference end, and RelationshipMultiplicity.Many is accepted for a collection 
        /// Reference for related entity of type TTargetEntity
        private EntityReference GetRelatedReference(string relationshipName, string sourceRoleName, string targetRoleName,
            RelationshipMultiplicity sourceRoleMultiplicity, RelatedEnd existingRelatedEnd)
                where TSourceEntity : class, IEntityWithRelationships 
                where TTargetEntity : class, IEntityWithRelationships
        { 
            EntityReference entityRef; 
            RelatedEnd relatedEnd;
 
            if (TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd))
            {
                entityRef = relatedEnd as EntityReference;
                // Because this is a private method that will only be called for target roles that actually have a 
                // multiplicity that works with EntityReference, this should never be null. If the user requests
                // a collection or reference and it doesn't match the target role multiplicity, it will be detected 
                // in the public GetRelatedCollection or GetRelatedReference 
                Debug.Assert(entityRef != null, "should never receive anything but an EntityReference here");
                return entityRef; 
            }
            else
            {
                RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName); 
                return CreateRelatedEnd(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.One, existingRelatedEnd) as EntityReference;
            } 
        } 

        ///  
        /// Returns either an EntityCollection or EntityReference of the correct type for the specified target role in a relationship
        /// This is intended to be used in scenarios where the user doesn't have full metadata, including the static type
        /// information for both ends of the relationship. This metadata is specified in the EdmRelationshipRoleAttribute
        /// on each entity type in the relationship, so the metadata system can retrieve it based on the supplied relationship 
        /// name and target role name.
        ///  
        /// Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not. 
        /// Target role to use to retrieve the other end of relationshipName
        /// IRelatedEnd representing the EntityCollection or EntityReference that was retrieved 
        public IRelatedEnd GetRelatedEnd(string relationshipName, string targetRoleName)
        {
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 

            // Get the AssociationType from metadata. This will contain all of the ospace metadata for this relationship 
            string fullSearchName; 
            AssociationType relationship = GetRelationshipType(_owner.GetType(), relationshipName, out fullSearchName);
 
            return GetRelatedEndInternal(relationshipName, targetRoleName, /*existingRelatedEnd*/ null, relationship);
        }

        internal bool TryGetRelatedEnd(string relationshipName, string targetRoleName, out IRelatedEnd relatedEnd) 
        {
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName"); 
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 
            relatedEnd = null;
 
            // Get the AssociationType from metadata. This will contain all of the ospace metadata for this relationship
            string fullSearchName;
            AssociationType relationship;
            if(TryGetRelationshipType(_owner.GetType(), relationshipName, out fullSearchName, out relationship)) 
            {
                relatedEnd = GetRelatedEndInternal(relationshipName, targetRoleName, /*existingRelatedEnd*/ null, relationship, false); 
                return relatedEnd != null; 
            }
            return false; 
        }

        private IRelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship)
        { 
            return GetRelatedEndInternal(relationshipName, targetRoleName, existingRelatedEnd, relationship, true);
        } 
 
        private IRelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship, bool throwOnError)
        { 
            Debug.Assert(relationshipName != null, "null relationshipNameFromUser");
            Debug.Assert(targetRoleName != null, "null targetRoleName");
            // existingRelatedEnd can be null if we are not trying to initialize an existing end
            Debug.Assert(relationship != null, "null relationshipType"); 

            AssociationEndMember sourceEnd; 
            AssociationEndMember targetEnd; 
            Debug.Assert(relationship.AssociationEndMembers.Count == 2, "Only 2-way relationships are currently supported");
 
            IRelatedEnd result = null;

            // There can only be two ends because we don't support n-way relationships -- figure out which end is the target and which is the source
            // If we want to support n-way relationships in the future, we will need a different overload of GetRelatedEnd that takes the source role name as well 
            if (relationship.AssociationEndMembers.TryGetValue(targetRoleName, false, out targetEnd))
            { 
                sourceEnd = MetadataHelper.GetOtherAssociationEnd(targetEnd); 
            }
            else 
            {
                if (throwOnError)
                {
                    throw EntityUtil.InvalidTargetRole(relationshipName, targetRoleName, "targetRoleName"); 
                }
                else 
                { 
                    return result;
                } 
            }

            // Validate that the source type matches the type of the owner
            EntityType sourceEntityType = MetadataHelper.GetEntityTypeForEnd(sourceEnd); 
            Debug.Assert(sourceEntityType.DataSpace == DataSpace.OSpace && sourceEntityType.ClrType != null, "sourceEntityType must contain an ospace type");
            Type sourceType = sourceEntityType.ClrType; 
            if (!(sourceType.IsAssignableFrom(_owner.GetType()))) 
            {
                if (throwOnError) 
                {
                    throw EntityUtil.OwnerIsNotSourceType(_owner.GetType().FullName, sourceType.FullName, sourceEnd.Name, relationshipName);
                }
            } 
            else if (VerifyRelationship(relationship, sourceEnd.Name, throwOnError))
            { 
                // Call a dynamic method that will call either GetRelatedCollection or GetRelatedReference for this relationship 
                result = LightweightCodeGenerator.GetRelatedEnd(this, sourceEnd, targetEnd, existingRelatedEnd);
            } 
            return result;
        }

        ///  
        /// Takes an existing EntityReference that was created with the default constructor and initializes it using the provided relationship and target role names.
        /// This method is designed to be used during deserialization only, and will throw an exception if the provided EntityReference has already been initialized, 
        /// if the relationship manager already contains a relationship with this name and target role, or if the relationship manager is already attached to a ObjectContext. 
        /// 
        /// Type of the entity represented by targetRoleName 
        /// 
        /// 
        /// 
        [Browsable(false)] 
        public void InitializeRelatedReference(string relationshipName, string targetRoleName, EntityReference entityReference)
            where TTargetEntity : class, IEntityWithRelationships 
        { 
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 
            EntityUtil.CheckArgumentNull(entityReference, "entityReference");

            if (entityReference.Owner != null)
            { 
                throw EntityUtil.ReferenceAlreadyInitialized();
            } 
 
            if (this.Context != null && _mergeOption != MergeOption.NoTracking)
            { 
                throw EntityUtil.RelationshipManagerAttached();
            }

            // We need the CSpace-qualified name in order to determine if this relationship already exists, so look it up. 
            // If the relationship doesn't exist, we will use this type information to determine how to initialize the reference
            string fullRelationshipName; 
            AssociationType relationship = GetRelationshipType(_owner.GetType(), relationshipName, out fullRelationshipName); 

            RelatedEnd relatedEnd; 
            if (TryGetCachedRelatedEnd(fullRelationshipName, targetRoleName, out relatedEnd))
            {
                // For some serialization scenarios, we have to allow replacing a related end that we already know about, but in those scenarios
                // the end is always empty, so we can further restrict the user calling method method directly by doing this extra validation 
                if (!relatedEnd.IsEmpty())
                { 
                    entityReference.InitializeWithValue(relatedEnd); 
                }
                _relationships.Remove(relatedEnd); 
            }

            EntityReference reference = GetRelatedEndInternal(relationshipName, targetRoleName, entityReference, relationship) as EntityReference;
            if (reference == null) 
            {
                throw EntityUtil.ExpectedReferenceGotCollection(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            } 
        }
 
        /// 
        /// Takes an existing EntityCollection that was created with the default constructor and initializes it using the provided relationship and target role names.
        /// This method is designed to be used during deserialization only, and will throw an exception if the provided EntityCollection has already been initialized,
        /// or if the relationship manager is already attached to a ObjectContext. 
        /// 
        /// Type of the entity represented by targetRoleName 
        ///  
        /// 
        ///  
        [Browsable(false)]
        public void InitializeRelatedCollection(string relationshipName, string targetRoleName, EntityCollection entityCollection)
            where TTargetEntity : class, IEntityWithRelationships
        { 
            EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
            EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName"); 
            EntityUtil.CheckArgumentNull(entityCollection, "entityCollection"); 

            if (entityCollection.Owner != null) 
            {
                throw EntityUtil.CollectionAlreadyInitialized();
            }
 
            if (this.Context != null && _mergeOption != MergeOption.NoTracking)
            { 
                throw EntityUtil.CollectionRelationshipManagerAttached(); 
            }
 
            // We need the CSpace-qualified name in order to determine if this relationship already exists, so look it up.
            // If the relationship doesn't exist, we will use this type information to determine how to initialize the reference
            string fullRelationshipName;
            AssociationType relationship = GetRelationshipType(_owner.GetType(), relationshipName, out fullRelationshipName); 

            EntityCollection collection = GetRelatedEndInternal(relationshipName, targetRoleName, entityCollection, relationship) as EntityCollection; 
            if (collection == null) 
            {
                throw EntityUtil.ExpectedCollectionGotReference(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            }
        }

        private bool TryGetRelationshipType(Type entityClrType, string relationshipName, out string fullSearchName, out AssociationType associationType) 
        {
            ObjectItemCollection objectItemCollection = null; 
            if (_context != null && _context.MetadataWorkspace != null) 
            {
                objectItemCollection = (ObjectItemCollection)_context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace); 
            }

            if (objectItemCollection != null)
            { 
                associationType = objectItemCollection.GetRelationshipType(entityClrType, relationshipName, out fullSearchName);
            } 
            else 
            {
                associationType = ObjectItemCollection.GetRelationshipTypeExpensiveWay(entityClrType, relationshipName, out fullSearchName); 
            }

            return (associationType != null);
        } 

        private AssociationType GetRelationshipType(Type entityClrType, string relationshipName, out string fullSearchName) 
        { 
            AssociationType associationType = null;
            if (!TryGetRelationshipType(entityClrType, relationshipName, out fullSearchName, out associationType)) 
            {
                throw EntityUtil.UnableToFindRelationshipTypeInMetadata(fullSearchName, "relationshipName");
            }
            return associationType; 
        }
 
        ///  
        /// Retrieves the AssociationEndMembers that corespond to the target end of a relationship
        /// given a specific CLR type that exists on the source end of a relationship 
        /// Note: this method can be very expensive if this RelationshipManager is not attached to an
        /// ObjectContext because no OSpace Metadata is available
        /// 
        /// A CLR type that is on the source role of the relationship 
        /// The OSpace EntityType that represents this CLR type
        private IEnumerable GetAllTargetEnds(Type entityClrType) 
        { 
            ObjectItemCollection objectItemCollection = null;
            if (_context != null && _context.MetadataWorkspace != null) 
            {
                objectItemCollection = (ObjectItemCollection)_context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace);
            }
 
            IEnumerable associations = null;
            if (objectItemCollection != null) 
            { 
                // Metadata is available
                associations = objectItemCollection.GetItems(); 
            }
            else
            {
                // No metadata is available, attempt to load the metadata on the fly to retrieve the AssociationTypes 
                associations = ObjectItemCollection.GetAllRelationshipTypesExpensiveWay(entityClrType.Assembly);
            } 
 
            foreach (AssociationType association in associations)
            { 
                // Check both ends for the presence of the source CLR type
                RefType referenceType = association.AssociationEndMembers[0].TypeUsage.EdmType as RefType;
                if (referenceType != null && referenceType.ElementType.ClrType.IsAssignableFrom(entityClrType))
                { 
                    // Return the target end
                    yield return association.AssociationEndMembers[1]; 
                } 

                referenceType = association.AssociationEndMembers[1].TypeUsage.EdmType as RefType; 
                if (referenceType != null && referenceType.ElementType.ClrType.IsAssignableFrom(entityClrType))
                {
                    // Return the target end
                    yield return association.AssociationEndMembers[0]; 
                }
            } 
            yield break; 
        }
 

        private bool VerifyRelationship(AssociationType relationship, string sourceEndName, bool throwOnError)
        {
            if (_context == null) 
            {
                return true;// if not added to cache, can not decide- for now 
            } 

            EntityKey ownerKey = null; 
            ownerKey = ObjectContext.FindEntityKey(_owner, _context);

            if (null == (object)ownerKey)
            { 
                return true; // if not added to cache, can not decide- for now
            } 
 
            TypeUsage associationTypeUsage;
            AssociationSet association = null; 
            bool isVerified = true;

            // First, get the CSpace association type from the relationship name, since the helper method looks up
            // association set in the CSpace, since there is no Entity Container in the OSpace 
            if (_context.Perspective.TryGetTypeByName(relationship.FullName, false/*ignoreCase*/, out associationTypeUsage))
            { 
                //Get the entity container first 
                EntityContainer entityContainer = this.Context.MetadataWorkspace.GetEntityContainer(
                    ownerKey.EntityContainerName, DataSpace.CSpace); 
                EntitySet entitySet;

                // Get the association set from the entity container, given the association type it refers to, and the entity set
                // name that the source end refers to 
                association = MetadataHelper.GetAssociationsForEntitySetAndAssociationType(entityContainer, ownerKey.EntitySetName,
                    (AssociationType)associationTypeUsage.EdmType, sourceEndName, out entitySet); 
 
                if (association == null)
                { 
                    if (throwOnError)
                    {
                        string fullyQualifiedRelationshipName = relationship.FullName;
                        throw EntityUtil.NoRelationshipSetMatched(fullyQualifiedRelationshipName); 
                    }
                    else 
                    { 
                        isVerified = false;
                    } 
                }
                else
                {
                    Debug.Assert(association.AssociationSetEnds[sourceEndName].EntitySet == entitySet, "AssociationSetEnd does have the matching EntitySet"); 
                }
            } 
            return isVerified; 
        }
 
        /// 
        /// Get the collection of a related entity using the specified
        /// combination of relationship name, and target role name.
        /// Only supports 2-way relationships. 
        /// 
        /// Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship. 
        /// Collection of entities of type TTargetEntity
        public EntityCollection GetRelatedCollection(string relationshipName, string targetRoleName) 
            where TTargetEntity : class, IEntityWithRelationships
        {
            EntityCollection collection = GetRelatedEnd(relationshipName, targetRoleName) as EntityCollection;
            if (collection == null) 
            {
                throw EntityUtil.ExpectedCollectionGotReference(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            } 
            return collection;
        } 

        /// 
        /// Get the entity reference of a related entity using the specified
        /// combination of relationship name, and target role name. 
        /// Only supports 2-way relationships.
        ///  
        /// Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not. 
        /// Name of the target role for the navigation. Indicates the direction of navigation across the relationship.
        /// Reference for related entity of type TTargetEntity 
        public EntityReference GetRelatedReference(string relationshipName, string targetRoleName)
            where TTargetEntity : class, IEntityWithRelationships
        {
            EntityReference reference = GetRelatedEnd(relationshipName, targetRoleName) as EntityReference; 
            if (reference == null)
            { 
                throw EntityUtil.ExpectedReferenceGotCollection(typeof(TTargetEntity).Name, targetRoleName, relationshipName); 
            }
            return reference; 
        }

        /// 
        /// Gets collection or ref of related entity for a particular navigation. 
        /// 
        ///  
        /// Describes the relationship and navigation direction 
        /// 
        ///  
        /// Encapsulates information about the other end's type and cardinality,
        /// and knows how to create the other end
        /// 
        ///  
        internal IRelatedEnd GetRelatedEnd(RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
        { 
            RelatedEnd relatedEnd; 

            if (TryGetCachedRelatedEnd(navigation.RelationshipName, navigation.To, out relatedEnd)) 
            {
                return relatedEnd;
            }
            else 
            {
                relatedEnd = relationshipFixer.CreateSourceEnd(navigation, this); 
                Debug.Assert(null != relatedEnd, "CreateSourceEnd should always return a valid RelatedEnd"); 

                return relatedEnd; 
            }
        }

        ///  
        /// Factory method for creating new related ends
        ///  
        /// Type of the source end 
        /// Type of the target end
        /// RelationshipNavigation to be set on the new RelatedEnd 
        /// Multiplicity of the source role
        /// Multiplicity of the target role
        /// An existing related end to initialize instead of creating a new one
        /// new EntityCollection or EntityReference, depending on the specified target multiplicity 
        internal RelatedEnd CreateRelatedEnd(RelationshipNavigation navigation, RelationshipMultiplicity sourceRoleMultiplicity, RelationshipMultiplicity targetRoleMultiplicity, RelatedEnd existingRelatedEnd)
            where TSourceEntity : class, IEntityWithRelationships 
            where TTargetEntity : class, IEntityWithRelationships 
        {
            IRelationshipFixer relationshipFixer = new RelationshipFixer(sourceRoleMultiplicity, targetRoleMultiplicity); 
            RelatedEnd relatedEnd = null;
            switch (targetRoleMultiplicity)
            {
                case RelationshipMultiplicity.ZeroOrOne: 
                case RelationshipMultiplicity.One:
                    if (existingRelatedEnd != null) 
                    { 
                        Debug.Assert(_context == null || _mergeOption == MergeOption.NoTracking, "Expected null context when initializing an existing related end");
                        existingRelatedEnd.InitializeRelatedEnd(_owner, navigation, relationshipFixer); 
                        relatedEnd = existingRelatedEnd;
                    }
                    else
                    { 
                        relatedEnd = new EntityReference(_owner, navigation, relationshipFixer);
                    } 
                    break; 
                case RelationshipMultiplicity.Many:
                    if (existingRelatedEnd != null) 
                    {
                        Debug.Assert(_context == null || MergeOption == MergeOption.NoTracking, "Expected null context or NoTracking when initializing an existing related end");
                        existingRelatedEnd.InitializeRelatedEnd(_owner, navigation, relationshipFixer);
                        relatedEnd = existingRelatedEnd; 
                    }
                    else 
                    { 
                        relatedEnd = new EntityCollection(_owner, navigation, relationshipFixer);
                    } 
                    break;
                default:
                    throw EntityUtil.InvalidEnumerationValue(typeof(RelationshipMultiplicity), (int)targetRoleMultiplicity);
            } 

            // Verify that we can attach the context successfully before adding to our list of relationships 
            if (_context != null) 
            {
                relatedEnd.AttachContext(_context, _mergeOption); 
            }

            Relationships.Add(relatedEnd);
 
            return relatedEnd;
        } 
 
        /// 
        /// Returns an enumeration of all the related ends.  The enumeration 
        /// will be empty if the relationships have not been populated.
        /// 
        public IEnumerable GetAllRelatedEnds()
        { 
            if(_owner != null)
            { 
                foreach (AssociationEndMember endMember in GetAllTargetEnds(_owner.GetType())) 
                {
                    yield return GetRelatedEnd(endMember.DeclaringType.FullName, endMember.Name); 
                }
            }
            yield break;
        } 

        [OnSerializingAttribute] 
        public void OnSerializing(StreamingContext context) 
        {
            // If we are attached to a context we need to go fixup the detached entity key on any EntityReferences 
            if (_context != null && _mergeOption != MergeOption.NoTracking)
            {
                // For all of the Added or Unchanged relationships that this owner participates in, if the relationship is a reference
                // set the detached entity key to the EntityKey of the target member in the relationship 
                EntityKey ownerKey = _context.ObjectStateManager.GetEntityKey(_owner);
                foreach (ObjectStateEntry relationshipEntry in _context.ObjectStateManager.FindRelationshipsByKey(ownerKey)) 
                { 
                    if (relationshipEntry.State != EntityState.Deleted)
                    { 
                        EntityKey targetKey = relationshipEntry.Wrapper.GetOtherEntityKey(ownerKey);
                        EntityKey detachedEntityKey = targetKey;
                        if (!RelatedEnd.IsValidEntityKeyType(targetKey))
                        { 
                            detachedEntityKey = null;
                        } 
 
                        AssociationEndMember targetMember = relationshipEntry.Wrapper.GetAssociationEndMember(targetKey);
 
                        if (targetMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
                            targetMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne)
                        {
                            EntityReference entityRef = GetRelatedEnd(targetMember.DeclaringType.FullName, targetMember.Name) as EntityReference; 
                            Debug.Assert(entityRef != null, "Expected EntityReference since target end multiplicity is one");
                            entityRef.DetachedEntityKey = detachedEntityKey; 
                        } 
                    }
                } 
            }
            // else the detached entity key is already the correct value for detached entities
        }
 
        // ----------------
        // Internal Methods 
        // ---------------- 

        internal void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption) 
        {
            Debug.Assert(null != entitySet, "entitySet should not be null");
            Debug.Assert(null != context, "context");
            Debug.Assert(MergeOption.NoTracking == mergeOption || 
                         MergeOption.AppendOnly == mergeOption,
                         "mergeOption"); 
 
            _context = context;
            _mergeOption = mergeOption; 

            if (null != _relationships)
            {
                foreach(RelatedEnd relatedEnd in _relationships) 
                {
                    relatedEnd.AttachContext(context, entitySet, mergeOption); 
                } 
            }
        } 

        internal void ResetContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
        {
            Debug.Assert(null != entitySet, "entitySet should not be null"); 
            Debug.Assert(null != context, "context");
            Debug.Assert(MergeOption.NoTracking == mergeOption || 
                         MergeOption.AppendOnly == mergeOption, 
                         "mergeOption");
 
            if (!object.ReferenceEquals(_context, context))
            {
                _context = context;
                _mergeOption = mergeOption; 

                if (null != _relationships) 
                { 
                    foreach (RelatedEnd relatedEnd in _relationships)
                    { 
                        relatedEnd.AttachContext(context, entitySet, mergeOption);
                        foreach (object entity in relatedEnd)
                        {
                            IEntityWithRelationships entityWithRelationships = entity as IEntityWithRelationships; 
                            if (entityWithRelationships != null)
                            { 
                                entityWithRelationships.RelationshipManager.ResetContext(context, relatedEnd.GetTargetEntitySetFromRelationshipSet(), mergeOption); 
                            }
                        } 
                    }
                }
            }
        } 

        internal void DetachContext() 
        { 
            _context = null;
 
            if (null != _relationships)
            {
                foreach(RelatedEnd relatedEnd in _relationships)
                { 
                    relatedEnd.DetachContext();
                } 
            } 
        }
 
        /// 
        /// Add the rest of the graph, attached to this owner, to ObjectStateManager
        /// 
        /// if TRUE, the rest of the graph is attached directly as Unchanged 
        /// without calling AcceptChanges()
        internal void AddRelatedEntitiesToObjectStateManager(bool doAttach) 
        { 
            if (null != _relationships)
            { 
                HashSet promotedEntityKeyRefs = new HashSet();
                bool doCleanup = true;
                try
                { 
                    // Create a copy of this list because with self references, the set of relationships can change
                    List relatedEnds = new List(_relationships); 
                    foreach(RelatedEnd relatedEnd in relatedEnds) 
                    {
                        relatedEnd.Include(/*addRelationshipAsUnchanged*/false, doAttach, promotedEntityKeyRefs); 
                    }
                    doCleanup = false;
                }
                finally 
                {
                    // If error happens, while attaching entity graph to context, clean-up 
                    // is done on the Owner entity and all its relating entities. 
                    if(doCleanup)
                    { 
                        Debug.Assert(this.Context != null && this.Context.ObjectStateManager != null, "Null context or ObjectStateManager");

                        if (this.Context.ObjectStateManager.IsAttachTracking)
                        { 
                            // The graph being attached is connected to graph already existing in the OSM only through "promoted" relationships
                            // (relationships which originally existed only in OSM between key entries and entity entries but later were 
                            // "promoted" to normal relationships in EntityRef/Collection when the key entries were promoted). 
                            // The cleanup code traverse all the graph being added to the OSM, so we have to disconnect it from the graph already
                            // existing in the OSM by degrading promoted relationships. 
                            this.Context.ObjectStateManager.DegradePromotedRelationships();
                        }

                        NodeVisited = true; 
                        RemoveRelatedEntitiesFromObjectStateManager(_owner, promotedEntityKeyRefs);
                        Debug.Assert(promotedEntityKeyRefs.Count == 0, "Haven't cleaned up all of the promoted reference EntityKeys"); 
 
                        ObjectStateEntry entry;
 
                        Debug.Assert(doAttach == (this.Context.ObjectStateManager.IsAttachTracking), "In attach the recovery collection should be not null");

                        if (this.Context.ObjectStateManager.IsAttachTracking &&
                            this.Context.ObjectStateManager.PromotedKeyEntries.TryGetValue(_owner, out entry)) 
                        {
                            // This is executed only in the cleanup code from ObjectContext.AttachTo() 
                            // If the entry was promoted in AttachTo(), it has to be degraded now instead of being deleted. 
                            entry.DegradeEntry();
                        } 
                        else
                        {
                            RelatedEnd.RemoveEntityFromObjectStateManager(_owner);
                        } 
                    }
                } 
            } 
        }
 
        // Method is used to remove all entities and relationships, of a given entity
        // graph, from ObjectStateManager. This method is used when adding entity graph,
        // or a portion of it, raise exception.
        internal static void RemoveRelatedEntitiesFromObjectStateManager(IEntityWithRelationships entity, HashSet promotedEntityKeyRefs) 
        {
            foreach (RelatedEnd relatedEnd in entity.RelationshipManager.Relationships) 
            { 
                // only some of the related ends may have gotten attached, so just skip the ones that weren't
                if (relatedEnd.ObjectContext != null) 
                {
                    Debug.Assert(!relatedEnd.UsingNoTracking, "Shouldn't be touching the state manager with entities that were retrieved with NoTracking");
                    relatedEnd.Exclude(promotedEntityKeyRefs);
                    relatedEnd.DetachContext(); 
                }
            } 
        } 

        // Remove entity from its relationships and do cascade delete if required. 
        // All removed relationships are marked for deletion and all cascade deleted
        // entitites are also marked for deletion.
        internal void RemoveEntityFromRelationships()
        { 
            if (null != _relationships)
            { 
                foreach(RelatedEnd relatedEnd in _relationships) 
                {
                    relatedEnd.RemoveAll(); 
                }
            }
        }
 
        // Removes entity from its relationships.
        // Relationship entries are removed from ObjectStateManager if owner is in Added state 
        // or when owner is "many" end of the relationship 
        internal void DetachEntityFromRelationships(EntityState ownerEntityState)
        { 
            if (null != _relationships)
            {
                foreach(RelatedEnd relatedEnd in _relationships)
                { 
                    relatedEnd.DetachAll(ownerEntityState);
                } 
            } 
        }
 
        //For a given relationship removes passed in entity from owners relationship
        internal void RemoveEntity(RelationshipNavigation navigation, IEntityWithRelationships entity)
        {
            RelatedEnd relatedEnd; 
            if (TryGetCachedRelatedEnd(navigation.RelationshipName, navigation.To, out relatedEnd))
            { 
                ((IRelatedEnd)relatedEnd).Remove(entity); 
            }
        } 

        // Method used to retrieve properties from principal entities.
        // Parameter includeOwnValues means that values from current entity should be also added to "properties"
        // includeOwnValues is false only when this method is called from ObjectStateEntry.AcceptChanges() 
        // Parmeter "visited" is a set containig entities which were already visited during traversing the graph.
        // If _owner already exists in the set, it means that there is a cycle in the graph of relationships with RI Constraints. 
        internal void RetrieveReferentialConstraintProperties(out Dictionary> properties, bool includeOwnValues, HashSet visited) 
        {
            Debug.Assert(_owner != null); 
            Debug.Assert(visited != null);

            // Dictionary< propertyName, >
            properties = new Dictionary>(); 

            EntityKey ownerKey = _context.ObjectStateManager.GetEntityKey(_owner); 
            Debug.Assert((object)ownerKey != null); 

            // Detect circular references 
            if (visited.Contains(_owner))
            {
                throw EntityUtil.CircularRelationshipsWithReferentialConstraints();
            } 
            else
            { 
                visited.Add(_owner); 
            }
 
            // If the key is temporary, get values of referential constraint properties from principal entities
            if (ownerKey.IsTemporary)
            {
                // Find property names which should be retrieved 
                List propertiesToRetrieve;
                bool propertiesToPropagateExist; // not used 
 
                this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToPropagateExist);
 
                if (propertiesToRetrieve != null)
                {
                    // At first try to retrieve properties from entities which are in collections or references.
                    // This is the most common scenario. 
                    // Only if properties couldn't be retrieved this way, try to retrieve properties from related stubs.
 
                    if (_relationships != null) 
                    {
                        foreach(RelatedEnd relatedEnd in _relationships) 
                        {
                            // NOTE: If the following call throws UnableToRetrieveReferentialConstraintProperties,
                            //       it means that properties couldn't be found in indirectly related entities,
                            //       so it doesn't make sense to search for properties in directly related stubs, 
                            //       so exception is not being caught here.
                            relatedEnd.RetrieveReferentialConstraintProperties(properties, visited); 
                        } 
                    }
 
                    // Check if all properties were retrieved.
                    // There are 3 scenarios in which not every expected property can be retrieved:
                    // 1. There is no related entity from which the property is supposed to be retrieved.
                    // 2. Related entity which supposed to contains the property doesn't have fixed entity key. 
                    // 3. Property should be retrieved from related key entry
 
                    if (!CheckIfAllPropertiesWereRetrieved(properties, propertiesToRetrieve)) 
                    {
                        // Properties couldn't be found in entities in collections or refrences. 
                        // Try to find missing properties in related key entries.
                        // This process is slow but it is not a common case.
                        ObjectStateEntry entry = _context.ObjectStateManager.FindObjectStateEntry(ownerKey);
                        Debug.Assert(entry != null, "Owner entry not found in the object state manager"); 
                        entry.RetrieveReferentialConstraintPropertiesFromKeyEntries(properties);
 
                        // Check again if all properties were retrieved. 
                        if (!CheckIfAllPropertiesWereRetrieved(properties, propertiesToRetrieve))
                        { 
                            throw EntityUtil.UnableToRetrieveReferentialConstraintProperties();
                        }
                    }
                } 
            }
 
            // 1. If key is temporary, properties from principal entities were retrieved above. 
            //    The other key properties are properties which are not Dependent end of some Referential Constraint.
            // 2. If key is not temporary and this method was not called from AcceptChanges() - all key values 
            //    of the current entity are added to 'properties'.
            if (!ownerKey.IsTemporary || includeOwnValues) {
                // NOTE this part is never executed when the method is called from ObjectStateManager.AcceptChanges(),
                //      so we don't try to "retrieve" properties from the the same (callers) entity. 
                ObjectStateEntry entry = _context.ObjectStateManager.FindObjectStateEntry(ownerKey);
                Debug.Assert(entry != null, "Owner entry not found in the object state manager"); 
                entry.GetOtherKeyProperties(properties); 
            }
        } 

        // properties dictionary contains name of property, its value and coutner saying how many times this property was retrieved from principal entities
        private static bool CheckIfAllPropertiesWereRetrieved(Dictionary> properties, List propertiesToRetrieve)
        { 
            Debug.Assert(properties != null);
            Debug.Assert(propertiesToRetrieve != null); 
 
            bool isSuccess = true;
 
            List countersCopy = new List();
            ICollection> values = properties.Values;

            // Create copy of counters (needed in case of failure) 
            foreach(KeyValuePair valueCounterPair in values)
            { 
                countersCopy.Add(valueCounterPair.Value.Value); 
            }
 
            foreach(string name in propertiesToRetrieve)
            {
                if(!properties.ContainsKey(name))
                { 
                    isSuccess = false;
                    break; 
                } 

                KeyValuePair valueCounterPair = properties[name]; 
                valueCounterPair.Value.Value = valueCounterPair.Value.Value - 1;
                if (valueCounterPair.Value.Value < 0)
                {
                    isSuccess = false; 
                    break;
                } 
            } 

            // Check if all the coutners equal 0 
            if (isSuccess)
            {
                foreach (KeyValuePair valueCounterPair in values)
                { 
                    if (valueCounterPair.Value.Value != 0)
                    { 
                        isSuccess = false; 
                        break;
                    } 
                }
            }

            // Restore counters in case of failure 
            if (!isSuccess)
            { 
                IEnumerator enumerator = countersCopy.GetEnumerator(); 
                foreach(KeyValuePair valueCounterPair in values)
                { 
                    enumerator.MoveNext();
                    valueCounterPair.Value.Value = enumerator.Current;
                }
            } 

            return isSuccess; 
        } 

 
        // Check consistency between properties of current entity and Principal entities
        // If some of Principal entities don't exist or some property cannot be checked - this is violation of RI Constraints
        internal void CheckReferentialConstraintProperties(EntityKey ownerKey)
        { 
            Debug.Assert(ownerKey != null);
 
            List propertiesToRetrieve; // used to check if the owner is a dependent end of some RI Constraint 
            bool propertiesToPropagateExist;   // used to check if the owner is a principal end of some RI Constraint
            this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToPropagateExist); 

            if ((propertiesToRetrieve != null || propertiesToPropagateExist) &&
                _relationships != null)
            { 
                foreach (RelatedEnd relatedEnd in _relationships)
                { 
                    if (!relatedEnd.CheckReferentialConstraintProperties(ownerKey)) 
                    {
                        throw EntityUtil.InconsistentReferentialConstraintProperties(); 
                    }
                }
            }
        } 

        // ---------------- 
        // Private Methods 
        // ----------------
 
        /// 
        /// Searches the list of relationships for an entry with the specified relationship name and role names
        /// 
        /// CSpace-qualified name of the relationship 
        /// name of the target role
        /// the RelatedEnd if found, otherwise null 
        /// true if the entry found, false otherwise 
        private bool TryGetCachedRelatedEnd(string relationshipName, string targetRoleName, out RelatedEnd relatedEnd)
        { 
            relatedEnd = null;
            if (null != _relationships)
            {
                foreach (RelatedEnd end in _relationships) 
                {
                    RelationshipNavigation relNav = end.RelationshipNavigation; 
                    if (relNav.RelationshipName == relationshipName && relNav.To == targetRoleName) 
                    {
                        relatedEnd = end; 
                        return true;
                    }
                }
            } 
            return false;
        } 
 
        // Find properties which are Dependent/Principal ends of some referential constraint
        // Returned lists are never null. 
        // NOTE This method will be removed when bug 505935 is solved
        internal void FindNamesOfReferentialConstraintProperties(out List propertiesToRetrieve, out bool propertiesToPropagateExist)
        {
            Debug.Assert(_owner != null); 
            EntityKey ownerKey = ObjectStateManager.FindKeyOnEntityWithRelationships(_owner);
            EntityUtil.CheckEntityKeyNull(ownerKey); 
 
            propertiesToRetrieve = null;
            propertiesToPropagateExist = false; 

            ObjectContext context = this.Context;
            EntityUtil.CheckContextNull(context);
            EntitySet entitySet = ownerKey.GetEntitySet(context.MetadataWorkspace); 
            Debug.Assert(entitySet != null, "Unable to find entity set");
 
            // Get association types in which current entity's type is one of the ends. 
            List associations = MetadataHelper.GetAssociationsForEntitySet(entitySet);
 
            // Find key property names which are part of referential integrity constraints
            foreach (AssociationSet association in associations)
            {
                // NOTE ReferentialConstraints collection currently can contain 0 or 1 element 
                foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
                { 
                    if (constraint.ToRole.TypeUsage.EdmType == entitySet.ElementType.GetReferenceType()) 
                    {
                        // lazy creation of the list 
                        propertiesToRetrieve = propertiesToRetrieve ?? new List();
                        foreach(EdmProperty property in constraint.ToProperties)
                        {
                            propertiesToRetrieve.Add(property.Name); 
                        }
                    } 
                    // There are schemas, in which relationship has the same entitySet on both ends 
                    // that is why following 'if' statement is not inside of 'else' of previous 'if' statement
                    if (constraint.FromRole.TypeUsage.EdmType == entitySet.ElementType.GetReferenceType()) 
                    {
                        propertiesToPropagateExist = true;
                    }
                } 
            }
        } 
 
        /// 
        /// Helper method to validate consistency of RelationshipManager instances 
        /// 
        /// entity to compare against
        /// True if entity is the owner of this RelationshipManager, otherwise false
        internal bool IsOwner(IEntityWithRelationships entity) 
        {
            return Object.ReferenceEquals(entity, _owner); 
        } 

        // -------------------- 
        // Internal definitions
        // --------------------

        internal delegate IRelatedEnd GetRelatedEndMethod(RelationshipManager relationshipManager, RelatedEnd existingRelatedEnd); 
    }
} 

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