entityreference_tresulttype.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 / entityreference_tresulttype.cs / 2 / entityreference_tresulttype.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common; 
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm; 
using System.Diagnostics; 
using System.Runtime.Serialization;
 
namespace System.Data.Objects.DataClasses
{
    /// 
    /// Models a relationship end with multiplicity 1. 
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] 
    [DataContract] 
    [Serializable]
    public sealed class EntityReference : EntityReference 
        where TEntity : class, IEntityWithRelationships
    {
        // ------
        // 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 TEntity _cachedValue;

 
        // ------------
        // Constructors 
        // ------------ 

        ///  
        /// The default constructor is required for some serialization scenarios. It should not be used to
        /// create new EntityReferences. Use the GetRelatedReference or GetRelatedEnd methods on the RelationshipManager
        /// class instead.
        ///  
        public EntityReference()
        { 
        } 

        internal EntityReference(IEntityWithRelationships owner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer) 
            : base(owner, navigation, relationshipFixer)
        {
            this._cachedValue = null;
        } 

 
        // ---------- 
        // Properties
        // ---------- 

        /// 
        /// Stub only please replace with actual implementation
        ///  
        [System.Xml.Serialization.SoapIgnore]
        [System.Xml.Serialization.XmlIgnore] 
        public TEntity Value 
        {
            get 
            {
                CheckOwnerNull();
                return _cachedValue;
            } 
            set
            { 
                CheckOwnerNull(); 
                //setting to same value is a no-op (SQLBU DT # 446320)
                //setting to null is a special case because then we will also clear out any Added/Unchanged relationships with key entries, so we can't no-op if Value is null 
                if(value != null && value == _cachedValue)
                {
                    return;
                } 

                ValidateOwnerWithRIConstraints(); 
 
                if (null != value)
                { 
                    Add(value, /*applyConstraints*/false);
                }
                else
                { 
                    if (UsingNoTracking)
                    { 
                        if (_cachedValue != null) 
                        {
                            // The other end of relationship can be the EntityReference or EntityCollection 
                            // If the other end is EntityReference, its IsLoaded property should be set to FALSE
                            RelatedEnd relatedEnd = GetOtherEndOfRelationship(_cachedValue);
                            relatedEnd.OnRelatedEndClear();
                        } 

                        _isLoaded = false; 
                    } 
                    ClearCollectionOrRef(null, null, false);
                } 
            }
        }

        internal override object CachedValue 
        {
            get { return _cachedValue; } 
        } 

        internal override object ReferenceValue 
        {
            get
            {
                return Value; 
            }
            set 
            { 
                Value = (TEntity)value;
            } 
        }

        // -------
        // Methods 
        // -------
 
        ///  
        /// Loads the related entity or entities into the local related end using the supplied MergeOption.
        ///  
        public override void Load(MergeOption mergeOption)
        {
            CheckOwnerNull();
 
            // Validate that the Load is possible
            ObjectQuery sourceQuery = ValidateLoad(mergeOption, "EntityReference"); 
 
            _suppressEvents = true; // we do not want any event during the bulk operation
            try 
            {
                List refreshedValue = new List(GetResults(sourceQuery));

                if (refreshedValue.Count > 1) 
                {
                    throw EntityUtil.MoreThanExpectedRelatedEntitiesFound(); 
                } 

                if (refreshedValue.Count == 0) 
                {
                    if (ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One)
                    {
                        //query returned zero related end; one related end was expected. 
                        throw EntityUtil.LessThanExpectedRelatedEntitiesFound();
                    } 
                    else if (mergeOption == MergeOption.OverwriteChanges || mergeOption == MergeOption.PreserveChanges) 
                    {
                        // This entity is not related to anything in this AssociationSet and Role on the server. 
                        // If there is an existing _cachedValue, we may need to clear it out, based on the MergeOption
                        EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(Owner);
                        EntityUtil.CheckEntityKeyNull(sourceKey);
                        ObjectStateManager.RemoveRelationships(ObjectContext, mergeOption, (AssociationSet)RelationshipSet, sourceKey, (AssociationEndMember)FromEndProperty); 
                    }
                    // else this is NoTracking or AppendOnly, and no entity was retrieved by the Load, so there's nothing extra to do 
 
                    // Since we have no value and are not doing a merge, the last step is to set IsLoaded to true
                    _isLoaded = true; 
                }
                else
                {
                    Merge(refreshedValue, mergeOption, true /*setIsLoaded*/); 
                }
            } 
            finally 
            {
                _suppressEvents = false; 
            }
            // fire the AssociationChange with Refresh
            OnAssociationChanged(CollectionChangeAction.Refresh, null);
        } 

        ///  
        /// This operation is not allowed if the owner is null 
        /// 
        ///  
        internal override IEnumerator GetInternalEnumerator()
        {
            if(Value != null)
            { 
                yield return Value;
            } 
        } 

        ///  
        /// Attaches an entity to the EntityReference. The given
        /// entity is not assumed to be the complete set of related entities.
        ///
        /// Owner and all entities passed in must be in Unchanged or Modified state. 
        /// Deleted elements are allowed only when the state manager is already tracking the relationship
        /// instance. 
        ///  
        /// The entity to attach to the EntityCollection
        /// Thrown when  is null. 
        /// Thrown when the entity cannot be related via the current relationship end.
        public void Attach(TEntity entity)
        {
            CheckOwnerNull(); 
            ((IRelatedEnd)this).Attach(entity);
        } 
 
        internal override void Include(bool addRelationshipAsUnchanged, bool doAttach, HashSet promotedEntityKeyRefs)
        { 
            Debug.Assert(this.ObjectContext != null, "Should not be trying to add entities to state manager if context is null");

            // If we have an actual value or a key for this reference, add it to the context
            if (null != _cachedValue) 
            {
                IncludeEntity(_cachedValue, addRelationshipAsUnchanged, doAttach, promotedEntityKeyRefs); 
            } 
            else if (DetachedEntityKey != null)
            { 
                IncludeEntityKey(doAttach, promotedEntityKeyRefs);
            }
            // else there is nothing to add for this relationship
        } 

        private void IncludeEntityKey(bool doAttach, HashSet promotedEntityKeyRefs) 
        { 
            ObjectStateManager manager = this.ObjectContext.ObjectStateManager;
 
            bool addNewRelationship = false;
            bool addKeyEntry = false;
            ObjectStateEntry existingEntry = manager.FindObjectStateEntry(DetachedEntityKey);
            if (existingEntry == null) 
            {
                // add new key entry and create a relationship with it 
                addKeyEntry = true; 
                addNewRelationship = true;
            } 
            else
            {
                if (existingEntry.IsKeyEntry)
                { 
                    // We have an existing key entry, so just need to add a relationship with it
 
                    // We know the target end of this relationship is 1..1 or 0..1 since it is a reference, so if the source end is also not Many, we have a 1-to-1 
                    if (FromEndProperty.RelationshipMultiplicity != RelationshipMultiplicity.Many)
                    { 
                        // before we add a new relationship to this key entry, make sure it's not already related to something else
                        // We have to explicitly do this here because there are no other checks to make sure a key entry in a 1-to-1 doesn't end up in two of the same relationship
                        foreach (ObjectStateEntry relationshipEntry in this.ObjectContext.ObjectStateManager.FindRelationshipsByKey(DetachedEntityKey))
                        { 
                            // only care about relationships in the same AssociationSet and where the key is playing the same role that it plays in this EntityReference
                            if (relationshipEntry.IsSameAssociationSetAndRole((AssociationSet)RelationshipSet, DetachedEntityKey, (AssociationEndMember)ToEndMember) && 
                                relationshipEntry.State != EntityState.Deleted) 
                            {
                                throw EntityUtil.EntityConflictsWithKeyEntry(); 
                            }
                        }
                    }
 
                    addNewRelationship = true;
                } 
                else 
                {
                    // since we verified that the EntitySet is correct, the type of existingEntry.Entity must 
                    // be correct and therefore it has to be an IEntityWithRelationships, so just cast
                    IEntityWithRelationships targetEntity = (IEntityWithRelationships)(existingEntry.Entity);

                    // Verify that the target entity is in a valid state for adding a relationship 
                    if (existingEntry.State == EntityState.Deleted)
                    { 
                        throw EntityUtil.ObjectStateManagerDoesnotAllowToReAddUnchangedOrModifiedOrDeletedEntity(EntityState.Deleted); 
                    }
 
                    // We know the target end of this relationship is 1..1 or 0..1 since it is a reference, so if the source end is also not Many, we have a 1-to-1
                    RelatedEnd relatedEnd = (RelatedEnd)targetEntity.RelationshipManager.GetRelatedEnd(RelationshipName, RelationshipNavigation.From);
                    if (FromEndProperty.RelationshipMultiplicity != RelationshipMultiplicity.Many && !relatedEnd.IsEmpty())
                    { 
                        // Make sure the target entity is not already related to something else.
                        // devnote: The call to Add below does *not* do this check for the fixup case, so if it's not done here, no failure will occur 
                        //          and existing relationships may be deleted unexpectedly. RelatedEnd.Include should not remove existing relationships, only add new ones. 
                        throw EntityUtil.EntityConflictsWithKeyEntry();
                    } 

                    // We have an existing entity with the same key, just hook up the related ends
                    this.Add(targetEntity,
                        true /*applyConstraints*/, 
                        doAttach /*addRelationshipAsUnchanged*/,
                        false /*relationshipAlreadyExists*/); 
 
                    // add to the list of promoted key references so we can cleanup if a failure occurs later
                    promotedEntityKeyRefs.Add(this); 
                }
            }

            if (addNewRelationship) 
            {
                // devnote: If we add any validation here, it needs to go here before adding the key entry, 
                //          otherwise we have to clean up that entry if the validation fails 

                if (addKeyEntry) 
                {
                    EntitySet targetEntitySet = DetachedEntityKey.GetEntitySet(this.ObjectContext.MetadataWorkspace);
                    manager.AddKeyEntry(DetachedEntityKey, targetEntitySet);
                } 

                EntityKey ownerKey = ObjectContext.FindEntityKey(this.Owner, this.ObjectContext); 
                RelationshipWrapper wrapper = new RelationshipWrapper((AssociationSet)RelationshipSet, 
                    RelationshipNavigation.From, ownerKey, RelationshipNavigation.To, DetachedEntityKey);
                manager.AddNewRelation(wrapper, doAttach ? EntityState.Unchanged : EntityState.Added); 
            }
        }

        internal override void Exclude(HashSet promotedEntityKeyRefs) 
        {
            Debug.Assert(this.ObjectContext != null, "Should not be trying to remove entities from state manager if context is null"); 
 
            if (null != _cachedValue)
            { 
                // It is possible that _cachedValue was originally null in this graph, but was only set
                // while the graph was being added, if the DetachedEntityKey matched its key. In that case,
                // we only want to clear _cachedValue and delete the relationship entry, but not remove the entity
                // itself from the context. 
                if (promotedEntityKeyRefs.Contains(this))
                { 
                    // Retrieve the relationship entry before _cachedValue is set to null during Remove 
                    ObjectStateEntry relationshipEntry = FindRelationshipEntryInObjectStateManager(_cachedValue);
                    Debug.Assert(relationshipEntry != null, "Should have been able to find a valid relationship since _cachedValue is non-null"); 

                    // Remove the related ends and mark the relationship as deleted, but don't propagate the changes to the target entity itself
                    Remove(_cachedValue, /*doFixup*/ true, /*deleteEntity*/ false, /*deleteOwner*/ false, /*applyRefentialConstraints*/ false);
 
                    // The relationship will now either be detached (if it was previously in the Added state), or Deleted (if it was previously Unchanged)
                    // If it's Deleted, we need to AcceptChanges to get rid of it completely 
                    if (relationshipEntry.State != EntityState.Detached) 
                    {
                        relationshipEntry.AcceptChanges(); 
                    }

                    // Since this has been processed, remove it from the list
                    promotedEntityKeyRefs.Remove(this); 
                }
                else 
                { 
                    ExcludeEntity(_cachedValue, promotedEntityKeyRefs);
                } 
            }
            else if (DetachedEntityKey != null)
            {
                // there may still be relationship entries with stubs that need to be removed 
                // this works whether we just added the key entry along with the relationship or if it was already existing
                ExcludeEntityKey(); 
            } 
            // else there is nothing to remove for this relationship
        } 

        private void ExcludeEntityKey()
        {
            EntityKey ownerKey = ObjectContext.FindEntityKey(this.Owner, this.ObjectContext); 

            ObjectStateEntry relationshipEntry = this.ObjectContext.ObjectStateManager.FindRelationship(RelationshipSet, 
                new KeyValuePair(RelationshipNavigation.From, ownerKey), 
                new KeyValuePair(RelationshipNavigation.To, DetachedEntityKey));
 
            // we may have failed in adding the graph before we actually added this relationship, so make sure we actually found one
            if (relationshipEntry != null)
            {
                relationshipEntry.Delete(/*doFixup*/ false); 
                // If entry was Added before, it is now Detached, otherwise AcceptChanges to detach it
                if (relationshipEntry.State != EntityState.Detached) 
                { 
                    relationshipEntry.AcceptChanges();
                } 
            }
        }

        internal override void ClearCollectionOrRef(IEntityWithRelationships entity, RelationshipNavigation navigation, bool doCascadeDelete) 
        {
            if (null != _cachedValue) 
            { 
                // Following condition checks if we have already visited this graph node. If its true then
                // we should not do fixup because that would cause circular loop 
                if ((entity == _cachedValue) && (navigation.Equals(this.RelationshipNavigation)))
                {
                    Remove(_cachedValue, /*fixup*/false, /*deleteEntity*/false, /*deleteOwner*/false, /*applyReferentialConstraints*/false);
                } 
                else
                { 
                    Remove(_cachedValue, /*fixup*/true, doCascadeDelete, /*deleteOwner*/false, /*applyReferentialConstraints*/true); 
                }
            } 
            else
            {
                // this entity reference could be replacing a relationship that points to a key entry
                // we need to search relationships on the Owner entity to see if this is true, and if so remove the relationship entry 
                if (Owner != null && Owner.RelationshipManager.Context != null && !UsingNoTracking)
                { 
                    ObjectStateEntry ownerEntry = Owner.RelationshipManager.Context.ObjectStateManager.GetObjectStateEntry(Owner); 
                    ownerEntry.DeleteRelationshipsThatReferenceKeys(this.RelationshipSet, this.ToEndMember);
                } 
            }

            // If we have an Owner, clear the DetachedEntityKey.
            // If we do not have an owner, retain the key so that we can resolve the difference when the entity is attached to a context 
            if (this.Owner != null)
            { 
                // Clear the detachedEntityKey as well. In cases where we have to fix up the detachedEntityKey, we will not always be able to detect 
                // if we have *only* a Deleted relationship for a given entity/relationship/role, so clearing this here will ensure that
                // even if no other relationships are added, the key value will still be correct. 
                ((EntityReference)this).DetachedEntityKey = null;
            }
        }
 
        /// 
        /// 
        ///  
        /// 
        ///  
        /// True if the verify succeeded, False if the Add should no-op
        internal override bool VerifyEntityForAdd(IEntityWithRelationships entity, bool relationshipAlreadyExists)
        {
            if (!relationshipAlreadyExists && this.ContainsEntity(entity)) 
            {
                return false; 
            } 

            if (!(entity is TEntity)) 
            {
                throw EntityUtil.InvalidContainedTypeReference(entity.GetType().FullName, typeof(TEntity).FullName);
            }
            return true; 
        }
 
        //AddEntityToLocallyCachedCollection is used by both APIs a) IRelatedEnd.Add b) Value property setter. 
        // ApplyConstraints is true in case of IRelatedEnd.Add because one cannot add entity to ref it its already set
        // however applyConstraints is false in case of Value property setter because value can be set to a new value 
        // even if its non null.
        internal override void AddEntityToLocallyCachedCollection(IEntityWithRelationships entity, bool applyConstraints)
        {
            if (applyConstraints && null != _cachedValue) 
            {
                throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(); 
            } 
            ClearCollectionOrRef(null, null, false);
            this._cachedValue = (TEntity)entity; 
        }

        /// 
        /// Disconnected adds are not supported for an EntityReference so we should report this as an error. 
        /// 
        /// The entity to add to the related end in a disconnected state. 
        internal override void DisconnectedAdd(IEntityWithRelationships entity) 
        {
            CheckOwnerNull(); 
        }

        /// 
        /// Disconnected removes are not supported for an EntityReference so we should report this as an error. 
        /// 
        /// The entity to remove from the related end in a disconnected state. 
        internal override bool DisconnectedRemove(IEntityWithRelationships entity) 
        {
            CheckOwnerNull(); 
            return false;
        }

        internal override bool RemoveEntityFromLocallyCachedCollection(IEntityWithRelationships entity, bool resetIsLoaded) 
        {
            if (null != _cachedValue && entity != _cachedValue) 
            { 
                throw EntityUtil.EntityIsNotPartOfRelationship();
            } 
            this._cachedValue = null;

            if (resetIsLoaded)
                _isLoaded = false; 

            return true; 
        } 

        // Method used to retrieve properties from principal entities. 
        // NOTE: 'properties' list is modified in this method and may already contains some properties.
        internal override void RetrieveReferentialConstraintProperties(Dictionary> properties, HashSet visited)
        {
            Debug.Assert(properties != null); 

            if (this._cachedValue != null) 
            { 
                // Dictionary< propertyName, >
                Dictionary> retrievedProperties; 

                // PERFORMANCE: ReferentialConstraints collection in typical scenario is very small (1-3 elements)
                foreach (ReferentialConstraint constraint in ((AssociationType)this.RelationMetadata).ReferentialConstraints)
                { 
                    if (constraint.ToRole == FromEndProperty)
                    { 
                        this._cachedValue.RelationshipManager.RetrieveReferentialConstraintProperties(out retrievedProperties, true/*includeOwnValues*/, visited); 

                        Debug.Assert(retrievedProperties != null); 
                        Debug.Assert(constraint.FromProperties.Count == constraint.ToProperties.Count, "Referential constraints From/To properties list have different size");

                        // Following loop rewrites properties from "retrievedProperties" into "properties".
                        // At the same time, property's name is translated from name from principal end into name from dependent end: 
                        // Example: Client - Order
                        //          Client is principal end, Order is dependent end, Client.C_ID == Order.Client_ID 
                        // Input : retrievedProperties = { "C_ID" = 123 } 
                        // Output: properties = { "Client_ID" = 123 }
 
                        // NOTE order of properties in collections constraint.From/ToProperties is important
                        for (int i = 0; i < constraint.FromProperties.Count; ++i)
                        {
                            ObjectStateEntry.AddOrIncreaseCounter( 
                                    properties,
                                    constraint.ToProperties[i].Name, 
                                    retrievedProperties[constraint.FromProperties[i].Name].Key); 
                        }
                    } 
                }
            }
        }
 
        internal override bool IsEmpty()
        { 
            return _cachedValue == null; 
        }
 
        // Update IsLoaded flag if necessary
        // This method is called when Clear() was called on the other end of relationship (if the other end is EntityCollection)
        // or when Value property of the other end was set to null (if the other end is EntityReference).
        // This method is used only when NoTracking option was used. 
        internal override void OnRelatedEndClear()
        { 
            // If other end of relationship was loaded, it mean that this end was also cleared. 
            _isLoaded = false;
        } 

        internal override bool ContainsEntity(IEntityWithRelationships entity)
        {
            // Using operator 'as' instead of () allows calling ContainsEntity 
            // with entity of different type than TEntity.
            return ((null != _cachedValue) && (_cachedValue == (entity as TEntity))); 
        } 

        // Identical code is in EntityCollection, but this can't be moved to the base class because it relies on the 
        // knowledge of the generic type, and the base class isn't generic
        public ObjectQuery CreateSourceQuery()
        {
            CheckOwnerNull(); 
            return CreateSourceQuery(DefaultMergeOption);
        } 
 
        internal override IEnumerable CreateSourceQueryInternal()
        { 
            return CreateSourceQuery();
        }
        //End identical code
 
        /// 
        /// Take any values in the incoming RelatedEnd and sets them onto the values 
        /// that currently exist in this RelatedEnd 
        /// 
        ///  
        internal void InitializeWithValue(IRelatedEnd relatedEnd)
        {
            Debug.Assert(this._cachedValue == null, "The EntityReference already has a value.");
            EntityReference reference = relatedEnd as EntityReference; 
            if (reference != null && reference._cachedValue != null)
            { 
                this._cachedValue = reference._cachedValue; 
            }
        } 
    }
}


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common; 
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm; 
using System.Diagnostics; 
using System.Runtime.Serialization;
 
namespace System.Data.Objects.DataClasses
{
    /// 
    /// Models a relationship end with multiplicity 1. 
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] 
    [DataContract] 
    [Serializable]
    public sealed class EntityReference : EntityReference 
        where TEntity : class, IEntityWithRelationships
    {
        // ------
        // 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 TEntity _cachedValue;

 
        // ------------
        // Constructors 
        // ------------ 

        ///  
        /// The default constructor is required for some serialization scenarios. It should not be used to
        /// create new EntityReferences. Use the GetRelatedReference or GetRelatedEnd methods on the RelationshipManager
        /// class instead.
        ///  
        public EntityReference()
        { 
        } 

        internal EntityReference(IEntityWithRelationships owner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer) 
            : base(owner, navigation, relationshipFixer)
        {
            this._cachedValue = null;
        } 

 
        // ---------- 
        // Properties
        // ---------- 

        /// 
        /// Stub only please replace with actual implementation
        ///  
        [System.Xml.Serialization.SoapIgnore]
        [System.Xml.Serialization.XmlIgnore] 
        public TEntity Value 
        {
            get 
            {
                CheckOwnerNull();
                return _cachedValue;
            } 
            set
            { 
                CheckOwnerNull(); 
                //setting to same value is a no-op (SQLBU DT # 446320)
                //setting to null is a special case because then we will also clear out any Added/Unchanged relationships with key entries, so we can't no-op if Value is null 
                if(value != null && value == _cachedValue)
                {
                    return;
                } 

                ValidateOwnerWithRIConstraints(); 
 
                if (null != value)
                { 
                    Add(value, /*applyConstraints*/false);
                }
                else
                { 
                    if (UsingNoTracking)
                    { 
                        if (_cachedValue != null) 
                        {
                            // The other end of relationship can be the EntityReference or EntityCollection 
                            // If the other end is EntityReference, its IsLoaded property should be set to FALSE
                            RelatedEnd relatedEnd = GetOtherEndOfRelationship(_cachedValue);
                            relatedEnd.OnRelatedEndClear();
                        } 

                        _isLoaded = false; 
                    } 
                    ClearCollectionOrRef(null, null, false);
                } 
            }
        }

        internal override object CachedValue 
        {
            get { return _cachedValue; } 
        } 

        internal override object ReferenceValue 
        {
            get
            {
                return Value; 
            }
            set 
            { 
                Value = (TEntity)value;
            } 
        }

        // -------
        // Methods 
        // -------
 
        ///  
        /// Loads the related entity or entities into the local related end using the supplied MergeOption.
        ///  
        public override void Load(MergeOption mergeOption)
        {
            CheckOwnerNull();
 
            // Validate that the Load is possible
            ObjectQuery sourceQuery = ValidateLoad(mergeOption, "EntityReference"); 
 
            _suppressEvents = true; // we do not want any event during the bulk operation
            try 
            {
                List refreshedValue = new List(GetResults(sourceQuery));

                if (refreshedValue.Count > 1) 
                {
                    throw EntityUtil.MoreThanExpectedRelatedEntitiesFound(); 
                } 

                if (refreshedValue.Count == 0) 
                {
                    if (ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One)
                    {
                        //query returned zero related end; one related end was expected. 
                        throw EntityUtil.LessThanExpectedRelatedEntitiesFound();
                    } 
                    else if (mergeOption == MergeOption.OverwriteChanges || mergeOption == MergeOption.PreserveChanges) 
                    {
                        // This entity is not related to anything in this AssociationSet and Role on the server. 
                        // If there is an existing _cachedValue, we may need to clear it out, based on the MergeOption
                        EntityKey sourceKey = ObjectStateManager.FindKeyOnEntityWithRelationships(Owner);
                        EntityUtil.CheckEntityKeyNull(sourceKey);
                        ObjectStateManager.RemoveRelationships(ObjectContext, mergeOption, (AssociationSet)RelationshipSet, sourceKey, (AssociationEndMember)FromEndProperty); 
                    }
                    // else this is NoTracking or AppendOnly, and no entity was retrieved by the Load, so there's nothing extra to do 
 
                    // Since we have no value and are not doing a merge, the last step is to set IsLoaded to true
                    _isLoaded = true; 
                }
                else
                {
                    Merge(refreshedValue, mergeOption, true /*setIsLoaded*/); 
                }
            } 
            finally 
            {
                _suppressEvents = false; 
            }
            // fire the AssociationChange with Refresh
            OnAssociationChanged(CollectionChangeAction.Refresh, null);
        } 

        ///  
        /// This operation is not allowed if the owner is null 
        /// 
        ///  
        internal override IEnumerator GetInternalEnumerator()
        {
            if(Value != null)
            { 
                yield return Value;
            } 
        } 

        ///  
        /// Attaches an entity to the EntityReference. The given
        /// entity is not assumed to be the complete set of related entities.
        ///
        /// Owner and all entities passed in must be in Unchanged or Modified state. 
        /// Deleted elements are allowed only when the state manager is already tracking the relationship
        /// instance. 
        ///  
        /// The entity to attach to the EntityCollection
        /// Thrown when  is null. 
        /// Thrown when the entity cannot be related via the current relationship end.
        public void Attach(TEntity entity)
        {
            CheckOwnerNull(); 
            ((IRelatedEnd)this).Attach(entity);
        } 
 
        internal override void Include(bool addRelationshipAsUnchanged, bool doAttach, HashSet promotedEntityKeyRefs)
        { 
            Debug.Assert(this.ObjectContext != null, "Should not be trying to add entities to state manager if context is null");

            // If we have an actual value or a key for this reference, add it to the context
            if (null != _cachedValue) 
            {
                IncludeEntity(_cachedValue, addRelationshipAsUnchanged, doAttach, promotedEntityKeyRefs); 
            } 
            else if (DetachedEntityKey != null)
            { 
                IncludeEntityKey(doAttach, promotedEntityKeyRefs);
            }
            // else there is nothing to add for this relationship
        } 

        private void IncludeEntityKey(bool doAttach, HashSet promotedEntityKeyRefs) 
        { 
            ObjectStateManager manager = this.ObjectContext.ObjectStateManager;
 
            bool addNewRelationship = false;
            bool addKeyEntry = false;
            ObjectStateEntry existingEntry = manager.FindObjectStateEntry(DetachedEntityKey);
            if (existingEntry == null) 
            {
                // add new key entry and create a relationship with it 
                addKeyEntry = true; 
                addNewRelationship = true;
            } 
            else
            {
                if (existingEntry.IsKeyEntry)
                { 
                    // We have an existing key entry, so just need to add a relationship with it
 
                    // We know the target end of this relationship is 1..1 or 0..1 since it is a reference, so if the source end is also not Many, we have a 1-to-1 
                    if (FromEndProperty.RelationshipMultiplicity != RelationshipMultiplicity.Many)
                    { 
                        // before we add a new relationship to this key entry, make sure it's not already related to something else
                        // We have to explicitly do this here because there are no other checks to make sure a key entry in a 1-to-1 doesn't end up in two of the same relationship
                        foreach (ObjectStateEntry relationshipEntry in this.ObjectContext.ObjectStateManager.FindRelationshipsByKey(DetachedEntityKey))
                        { 
                            // only care about relationships in the same AssociationSet and where the key is playing the same role that it plays in this EntityReference
                            if (relationshipEntry.IsSameAssociationSetAndRole((AssociationSet)RelationshipSet, DetachedEntityKey, (AssociationEndMember)ToEndMember) && 
                                relationshipEntry.State != EntityState.Deleted) 
                            {
                                throw EntityUtil.EntityConflictsWithKeyEntry(); 
                            }
                        }
                    }
 
                    addNewRelationship = true;
                } 
                else 
                {
                    // since we verified that the EntitySet is correct, the type of existingEntry.Entity must 
                    // be correct and therefore it has to be an IEntityWithRelationships, so just cast
                    IEntityWithRelationships targetEntity = (IEntityWithRelationships)(existingEntry.Entity);

                    // Verify that the target entity is in a valid state for adding a relationship 
                    if (existingEntry.State == EntityState.Deleted)
                    { 
                        throw EntityUtil.ObjectStateManagerDoesnotAllowToReAddUnchangedOrModifiedOrDeletedEntity(EntityState.Deleted); 
                    }
 
                    // We know the target end of this relationship is 1..1 or 0..1 since it is a reference, so if the source end is also not Many, we have a 1-to-1
                    RelatedEnd relatedEnd = (RelatedEnd)targetEntity.RelationshipManager.GetRelatedEnd(RelationshipName, RelationshipNavigation.From);
                    if (FromEndProperty.RelationshipMultiplicity != RelationshipMultiplicity.Many && !relatedEnd.IsEmpty())
                    { 
                        // Make sure the target entity is not already related to something else.
                        // devnote: The call to Add below does *not* do this check for the fixup case, so if it's not done here, no failure will occur 
                        //          and existing relationships may be deleted unexpectedly. RelatedEnd.Include should not remove existing relationships, only add new ones. 
                        throw EntityUtil.EntityConflictsWithKeyEntry();
                    } 

                    // We have an existing entity with the same key, just hook up the related ends
                    this.Add(targetEntity,
                        true /*applyConstraints*/, 
                        doAttach /*addRelationshipAsUnchanged*/,
                        false /*relationshipAlreadyExists*/); 
 
                    // add to the list of promoted key references so we can cleanup if a failure occurs later
                    promotedEntityKeyRefs.Add(this); 
                }
            }

            if (addNewRelationship) 
            {
                // devnote: If we add any validation here, it needs to go here before adding the key entry, 
                //          otherwise we have to clean up that entry if the validation fails 

                if (addKeyEntry) 
                {
                    EntitySet targetEntitySet = DetachedEntityKey.GetEntitySet(this.ObjectContext.MetadataWorkspace);
                    manager.AddKeyEntry(DetachedEntityKey, targetEntitySet);
                } 

                EntityKey ownerKey = ObjectContext.FindEntityKey(this.Owner, this.ObjectContext); 
                RelationshipWrapper wrapper = new RelationshipWrapper((AssociationSet)RelationshipSet, 
                    RelationshipNavigation.From, ownerKey, RelationshipNavigation.To, DetachedEntityKey);
                manager.AddNewRelation(wrapper, doAttach ? EntityState.Unchanged : EntityState.Added); 
            }
        }

        internal override void Exclude(HashSet promotedEntityKeyRefs) 
        {
            Debug.Assert(this.ObjectContext != null, "Should not be trying to remove entities from state manager if context is null"); 
 
            if (null != _cachedValue)
            { 
                // It is possible that _cachedValue was originally null in this graph, but was only set
                // while the graph was being added, if the DetachedEntityKey matched its key. In that case,
                // we only want to clear _cachedValue and delete the relationship entry, but not remove the entity
                // itself from the context. 
                if (promotedEntityKeyRefs.Contains(this))
                { 
                    // Retrieve the relationship entry before _cachedValue is set to null during Remove 
                    ObjectStateEntry relationshipEntry = FindRelationshipEntryInObjectStateManager(_cachedValue);
                    Debug.Assert(relationshipEntry != null, "Should have been able to find a valid relationship since _cachedValue is non-null"); 

                    // Remove the related ends and mark the relationship as deleted, but don't propagate the changes to the target entity itself
                    Remove(_cachedValue, /*doFixup*/ true, /*deleteEntity*/ false, /*deleteOwner*/ false, /*applyRefentialConstraints*/ false);
 
                    // The relationship will now either be detached (if it was previously in the Added state), or Deleted (if it was previously Unchanged)
                    // If it's Deleted, we need to AcceptChanges to get rid of it completely 
                    if (relationshipEntry.State != EntityState.Detached) 
                    {
                        relationshipEntry.AcceptChanges(); 
                    }

                    // Since this has been processed, remove it from the list
                    promotedEntityKeyRefs.Remove(this); 
                }
                else 
                { 
                    ExcludeEntity(_cachedValue, promotedEntityKeyRefs);
                } 
            }
            else if (DetachedEntityKey != null)
            {
                // there may still be relationship entries with stubs that need to be removed 
                // this works whether we just added the key entry along with the relationship or if it was already existing
                ExcludeEntityKey(); 
            } 
            // else there is nothing to remove for this relationship
        } 

        private void ExcludeEntityKey()
        {
            EntityKey ownerKey = ObjectContext.FindEntityKey(this.Owner, this.ObjectContext); 

            ObjectStateEntry relationshipEntry = this.ObjectContext.ObjectStateManager.FindRelationship(RelationshipSet, 
                new KeyValuePair(RelationshipNavigation.From, ownerKey), 
                new KeyValuePair(RelationshipNavigation.To, DetachedEntityKey));
 
            // we may have failed in adding the graph before we actually added this relationship, so make sure we actually found one
            if (relationshipEntry != null)
            {
                relationshipEntry.Delete(/*doFixup*/ false); 
                // If entry was Added before, it is now Detached, otherwise AcceptChanges to detach it
                if (relationshipEntry.State != EntityState.Detached) 
                { 
                    relationshipEntry.AcceptChanges();
                } 
            }
        }

        internal override void ClearCollectionOrRef(IEntityWithRelationships entity, RelationshipNavigation navigation, bool doCascadeDelete) 
        {
            if (null != _cachedValue) 
            { 
                // Following condition checks if we have already visited this graph node. If its true then
                // we should not do fixup because that would cause circular loop 
                if ((entity == _cachedValue) && (navigation.Equals(this.RelationshipNavigation)))
                {
                    Remove(_cachedValue, /*fixup*/false, /*deleteEntity*/false, /*deleteOwner*/false, /*applyReferentialConstraints*/false);
                } 
                else
                { 
                    Remove(_cachedValue, /*fixup*/true, doCascadeDelete, /*deleteOwner*/false, /*applyReferentialConstraints*/true); 
                }
            } 
            else
            {
                // this entity reference could be replacing a relationship that points to a key entry
                // we need to search relationships on the Owner entity to see if this is true, and if so remove the relationship entry 
                if (Owner != null && Owner.RelationshipManager.Context != null && !UsingNoTracking)
                { 
                    ObjectStateEntry ownerEntry = Owner.RelationshipManager.Context.ObjectStateManager.GetObjectStateEntry(Owner); 
                    ownerEntry.DeleteRelationshipsThatReferenceKeys(this.RelationshipSet, this.ToEndMember);
                } 
            }

            // If we have an Owner, clear the DetachedEntityKey.
            // If we do not have an owner, retain the key so that we can resolve the difference when the entity is attached to a context 
            if (this.Owner != null)
            { 
                // Clear the detachedEntityKey as well. In cases where we have to fix up the detachedEntityKey, we will not always be able to detect 
                // if we have *only* a Deleted relationship for a given entity/relationship/role, so clearing this here will ensure that
                // even if no other relationships are added, the key value will still be correct. 
                ((EntityReference)this).DetachedEntityKey = null;
            }
        }
 
        /// 
        /// 
        ///  
        /// 
        ///  
        /// True if the verify succeeded, False if the Add should no-op
        internal override bool VerifyEntityForAdd(IEntityWithRelationships entity, bool relationshipAlreadyExists)
        {
            if (!relationshipAlreadyExists && this.ContainsEntity(entity)) 
            {
                return false; 
            } 

            if (!(entity is TEntity)) 
            {
                throw EntityUtil.InvalidContainedTypeReference(entity.GetType().FullName, typeof(TEntity).FullName);
            }
            return true; 
        }
 
        //AddEntityToLocallyCachedCollection is used by both APIs a) IRelatedEnd.Add b) Value property setter. 
        // ApplyConstraints is true in case of IRelatedEnd.Add because one cannot add entity to ref it its already set
        // however applyConstraints is false in case of Value property setter because value can be set to a new value 
        // even if its non null.
        internal override void AddEntityToLocallyCachedCollection(IEntityWithRelationships entity, bool applyConstraints)
        {
            if (applyConstraints && null != _cachedValue) 
            {
                throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(); 
            } 
            ClearCollectionOrRef(null, null, false);
            this._cachedValue = (TEntity)entity; 
        }

        /// 
        /// Disconnected adds are not supported for an EntityReference so we should report this as an error. 
        /// 
        /// The entity to add to the related end in a disconnected state. 
        internal override void DisconnectedAdd(IEntityWithRelationships entity) 
        {
            CheckOwnerNull(); 
        }

        /// 
        /// Disconnected removes are not supported for an EntityReference so we should report this as an error. 
        /// 
        /// The entity to remove from the related end in a disconnected state. 
        internal override bool DisconnectedRemove(IEntityWithRelationships entity) 
        {
            CheckOwnerNull(); 
            return false;
        }

        internal override bool RemoveEntityFromLocallyCachedCollection(IEntityWithRelationships entity, bool resetIsLoaded) 
        {
            if (null != _cachedValue && entity != _cachedValue) 
            { 
                throw EntityUtil.EntityIsNotPartOfRelationship();
            } 
            this._cachedValue = null;

            if (resetIsLoaded)
                _isLoaded = false; 

            return true; 
        } 

        // Method used to retrieve properties from principal entities. 
        // NOTE: 'properties' list is modified in this method and may already contains some properties.
        internal override void RetrieveReferentialConstraintProperties(Dictionary> properties, HashSet visited)
        {
            Debug.Assert(properties != null); 

            if (this._cachedValue != null) 
            { 
                // Dictionary< propertyName, >
                Dictionary> retrievedProperties; 

                // PERFORMANCE: ReferentialConstraints collection in typical scenario is very small (1-3 elements)
                foreach (ReferentialConstraint constraint in ((AssociationType)this.RelationMetadata).ReferentialConstraints)
                { 
                    if (constraint.ToRole == FromEndProperty)
                    { 
                        this._cachedValue.RelationshipManager.RetrieveReferentialConstraintProperties(out retrievedProperties, true/*includeOwnValues*/, visited); 

                        Debug.Assert(retrievedProperties != null); 
                        Debug.Assert(constraint.FromProperties.Count == constraint.ToProperties.Count, "Referential constraints From/To properties list have different size");

                        // Following loop rewrites properties from "retrievedProperties" into "properties".
                        // At the same time, property's name is translated from name from principal end into name from dependent end: 
                        // Example: Client - Order
                        //          Client is principal end, Order is dependent end, Client.C_ID == Order.Client_ID 
                        // Input : retrievedProperties = { "C_ID" = 123 } 
                        // Output: properties = { "Client_ID" = 123 }
 
                        // NOTE order of properties in collections constraint.From/ToProperties is important
                        for (int i = 0; i < constraint.FromProperties.Count; ++i)
                        {
                            ObjectStateEntry.AddOrIncreaseCounter( 
                                    properties,
                                    constraint.ToProperties[i].Name, 
                                    retrievedProperties[constraint.FromProperties[i].Name].Key); 
                        }
                    } 
                }
            }
        }
 
        internal override bool IsEmpty()
        { 
            return _cachedValue == null; 
        }
 
        // Update IsLoaded flag if necessary
        // This method is called when Clear() was called on the other end of relationship (if the other end is EntityCollection)
        // or when Value property of the other end was set to null (if the other end is EntityReference).
        // This method is used only when NoTracking option was used. 
        internal override void OnRelatedEndClear()
        { 
            // If other end of relationship was loaded, it mean that this end was also cleared. 
            _isLoaded = false;
        } 

        internal override bool ContainsEntity(IEntityWithRelationships entity)
        {
            // Using operator 'as' instead of () allows calling ContainsEntity 
            // with entity of different type than TEntity.
            return ((null != _cachedValue) && (_cachedValue == (entity as TEntity))); 
        } 

        // Identical code is in EntityCollection, but this can't be moved to the base class because it relies on the 
        // knowledge of the generic type, and the base class isn't generic
        public ObjectQuery CreateSourceQuery()
        {
            CheckOwnerNull(); 
            return CreateSourceQuery(DefaultMergeOption);
        } 
 
        internal override IEnumerable CreateSourceQueryInternal()
        { 
            return CreateSourceQuery();
        }
        //End identical code
 
        /// 
        /// Take any values in the incoming RelatedEnd and sets them onto the values 
        /// that currently exist in this RelatedEnd 
        /// 
        ///  
        internal void InitializeWithValue(IRelatedEnd relatedEnd)
        {
            Debug.Assert(this._cachedValue == null, "The EntityReference already has a value.");
            EntityReference reference = relatedEnd as EntityReference; 
            if (reference != null && reference._cachedValue != null)
            { 
                this._cachedValue = reference._cachedValue; 
            }
        } 
    }
}


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