File: System\Data\Objects\DataClasses\EntityReference_TResultType.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="EntityReference_TResultType.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Objects.DataClasses
{
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Data.Metadata.Edm;
    using System.Data.Objects.Internal;
    using System.Diagnostics;
    using System.Runtime.Serialization;
 
    /// <summary>
    /// Models a relationship end with multiplicity 1.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    [DataContract]
    [Serializable]
    public sealed class EntityReference<TEntity> : EntityReference
        where TEntity : class
    {
        // ------
        // 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.
        // Note that this field should no longer be used directly.  Instead, use the _wrappedCachedValue
        // field.  This field is retained only for compatibility with the serialization format introduced in v1.
        private TEntity _cachedValue;
 
        [NonSerialized]
        private IEntityWrapper _wrappedCachedValue;
 
        // ------------
        // Constructors
        // ------------
 
        /// <summary>
        /// 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.
        /// </summary>
        public EntityReference()
        {
            _wrappedCachedValue = EntityWrapperFactory.NullWrapper;
        }
 
        internal EntityReference(IEntityWrapper wrappedOwner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
            : base(wrappedOwner, navigation, relationshipFixer)
        {
            _wrappedCachedValue = EntityWrapperFactory.NullWrapper;
        }
 
        // ----------
        // Properties
        // ----------
 
        /// <summary>
        /// Stub only please replace with actual implementation
        /// </summary>
        [System.Xml.Serialization.SoapIgnore]
        [System.Xml.Serialization.XmlIgnore]
        public TEntity Value
        {
            get
            {
                DeferredLoad();
                return (TEntity)ReferenceValue.Entity;
            }
            set
            {
                ReferenceValue = EntityWrapperFactory.WrapEntityUsingContext(value, ObjectContext);
            }
        }
 
        internal override IEntityWrapper CachedValue
        {
            get { return _wrappedCachedValue; }
        }
 
        internal override IEntityWrapper ReferenceValue
        {
            get
            {
                CheckOwnerNull();
                return _wrappedCachedValue;
            }
            set
            {
                CheckOwnerNull();
                //setting to same value is a no-op (SQL BU 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.Entity != null && value.Entity == _wrappedCachedValue.Entity)
                {
                    return;
                }
 
                if (null != value.Entity)
                {
                    // Note that this is only done for the case where we are not setting the ref to null because
                    // clearing a ref is okay--it will cause the dependent to become deleted/detached.
                    ValidateOwnerWithRIConstraints(value, value == EntityWrapperFactory.NullWrapper ? null : value.EntityKey, checkBothEnds: true);
                    ObjectContext context = ObjectContext ?? value.Context;
                    if (context != null)
                    {
                        context.ObjectStateManager.TransactionManager.EntityBeingReparented = GetDependentEndOfReferentialConstraint(value.Entity);
                    }
                    try
                    {
                        Add(value, /*applyConstraints*/false);
                    }
                    finally
                    {
                        if (context != null)
                        {
                            context.ObjectStateManager.TransactionManager.EntityBeingReparented = null;
                        }
                    }
                }
                else
                {
                    if (UsingNoTracking)
                    {
                        if (_wrappedCachedValue.Entity != 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(_wrappedCachedValue);
                            relatedEnd.OnRelatedEndClear();
                        }
 
                        _isLoaded = false;
                    }
                    else
                    {
                        if (ObjectContext != null && ObjectContext.ContextOptions.UseConsistentNullReferenceBehavior)
                        {
                            AttemptToNullFKsOnRefOrKeySetToNull();
                        }
                    }
 
                    ClearCollectionOrRef(null, null, false);
                }
            }
        }
 
        // -------
        // Methods
        // -------
 
        /// <summary>
        /// Loads the related entity or entities into the local related end using the supplied MergeOption.        
        /// </summary>        
        public override void Load(MergeOption mergeOption)
        {
            CheckOwnerNull();
 
            // Validate that the Load is possible
            bool hasResults;
            ObjectQuery<TEntity> sourceQuery = ValidateLoad<TEntity>(mergeOption, "EntityReference", out hasResults);
 
            _suppressEvents = true; // we do not want any event during the bulk operation
            try
            {
                List<TEntity> refreshedValue = null;
                if (hasResults)
                {
                    // Only issue a query if we know it can produce results (in the case of FK, there may not be any 
                    // results).
                    refreshedValue = new List<TEntity>(GetResults<TEntity>(sourceQuery));
                }
                if (null == refreshedValue || refreshedValue.Count == 0)
                {
                    if (!((AssociationType)base.RelationMetadata).IsForeignKey && 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 = WrappedOwner.EntityKey;
                        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 if (refreshedValue.Count == 1)
                {
                    Merge<TEntity>(refreshedValue, mergeOption, true /*setIsLoaded*/);
                }
                else
                {
                    // More than 1 result, which is non-recoverable data inconsistency
                    throw EntityUtil.MoreThanExpectedRelatedEntitiesFound();
                }
            }
            finally
            {
                _suppressEvents = false;
            }
            // fire the AssociationChange with Refresh
            OnAssociationChanged(CollectionChangeAction.Refresh, null);
        }
 
        /// <summary>
        /// This operation is not allowed if the owner is null
        /// </summary>
        /// <returns></returns>
        internal override IEnumerable GetInternalEnumerable()
        {
            if (ReferenceValue.Entity != null)
            {
                yield return (TEntity)ReferenceValue.Entity;
            }
        }
 
        internal override IEnumerable<IEntityWrapper> GetWrappedEntities()
        {
            // 
            return _wrappedCachedValue.Entity == null ? new IEntityWrapper[0] : new IEntityWrapper[] { _wrappedCachedValue };
        }
 
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="entity">The entity to attach to the EntityCollection</param>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="entity"/> is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown when the entity cannot be related via the current relationship end.</exception>
        public void Attach(TEntity entity)
        {
            CheckOwnerNull();
            EntityUtil.CheckArgumentNull(entity, "entity");
            Attach(new IEntityWrapper[] { EntityWrapperFactory.WrapEntityUsingContext(entity, ObjectContext) }, false);
        }
 
        internal override void Include(bool addRelationshipAsUnchanged, bool doAttach)
        {
            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 != _wrappedCachedValue.Entity)
            {
                // Sometimes with mixed POCO and IPOCO, you can get different instances of IEntityWrappers stored in the IPOCO related ends
                // These should be replaced by the IEntityWrapper that is stored in the context
                IEntityWrapper identityWrapper = EntityWrapperFactory.WrapEntityUsingContext(_wrappedCachedValue.Entity, WrappedOwner.Context);
                if (identityWrapper != _wrappedCachedValue)
                {
                    _wrappedCachedValue = identityWrapper;
                }
                IncludeEntity(_wrappedCachedValue, addRelationshipAsUnchanged, doAttach);
            }
            else if (DetachedEntityKey != null)
            {
                IncludeEntityKey(doAttach);
            }
            // else there is nothing to add for this relationship
        }
 
        private void IncludeEntityKey(bool doAttach)
        {
            ObjectStateManager manager = this.ObjectContext.ObjectStateManager;
 
            bool addNewRelationship = false;
            bool addKeyEntry = false;
            EntityEntry existingEntry = manager.FindEntityEntry(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 (RelationshipEntry 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, (AssociationEndMember)ToEndMember, DetachedEntityKey) &&
                                relationshipEntry.State != EntityState.Deleted)
                            {
                                throw EntityUtil.EntityConflictsWithKeyEntry();
                            }
                        }
                    }
 
                    addNewRelationship = true;
                }
                else
                {
                    IEntityWrapper wrappedTarget = existingEntry.WrappedEntity;
 
                    // Verify that the target entity is in a valid state for adding a relationship
                    if (existingEntry.State == EntityState.Deleted)
                    {
                        throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
                    }
 
                    // 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 = wrappedTarget.RelationshipManager.GetRelatedEndInternal(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(wrappedTarget,
                        applyConstraints: true,
                        addRelationshipAsUnchanged: doAttach,
                        relationshipAlreadyExists: false,
                        allowModifyingOtherEndOfRelationship: true,
                        forceForeignKeyChanges: true);
 
                    // add to the list of promoted key references so we can cleanup if a failure occurs later
                    manager.TransactionManager.PopulatedEntityReferences.Add(this);
                }
            }
 
            // For FKs, don't create a key entry and don't create a relationship
            if (addNewRelationship && !IsForeignKey)
            {
                // 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 = WrappedOwner.EntityKey;
                EntityUtil.CheckEntityKeyNull(ownerKey);
                RelationshipWrapper wrapper = new RelationshipWrapper((AssociationSet)RelationshipSet,
                    RelationshipNavigation.From, ownerKey, RelationshipNavigation.To, DetachedEntityKey);
                manager.AddNewRelation(wrapper, doAttach ? EntityState.Unchanged : EntityState.Added);
            }
        }
 
        internal override void Exclude()
        {
            Debug.Assert(this.ObjectContext != null, "Should not be trying to remove entities from state manager if context is null");
 
            if (null != _wrappedCachedValue.Entity)
            {
                // 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.
                TransactionManager transManager = ObjectContext.ObjectStateManager.TransactionManager;
                bool doFullRemove = transManager.PopulatedEntityReferences.Contains(this);
                bool doRelatedEndRemove = transManager.AlignedEntityReferences.Contains(this);
                // For POCO, if the entity is undergoing snapshot for the first time, then in this step we actually
                // need to really exclude it rather than just disconnecting it.  If we don't, then it has the potential
                // to remain in the context at the end of the rollback process.
                if ((transManager.ProcessedEntities == null || !transManager.ProcessedEntities.Contains(_wrappedCachedValue)) &&
                    (doFullRemove || doRelatedEndRemove))
                {
                    // Retrieve the relationship entry before _cachedValue is set to null during Remove
                    RelationshipEntry relationshipEntry = IsForeignKey ? null : FindRelationshipEntryInObjectStateManager(_wrappedCachedValue);
                    Debug.Assert(IsForeignKey || 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(_wrappedCachedValue,
                            doFixup: doFullRemove,
                            deleteEntity: false,
                            deleteOwner: false,
                            applyReferentialConstraints: false,
                            preserveForeignKey: true);
 
                    // 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 != null && relationshipEntry.State != EntityState.Detached)
                    {
                        relationshipEntry.AcceptChanges();
                    }
 
                    // Since this has been processed, remove it from the list
                    if (doFullRemove)
                    {
                        transManager.PopulatedEntityReferences.Remove(this);
                    }
                    else
                    {
                        transManager.AlignedEntityReferences.Remove(this);
                    }
                }
                else
                {
                    ExcludeEntity(_wrappedCachedValue);
                }
            }
            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 = WrappedOwner.EntityKey;
 
            RelationshipEntry relationshipEntry = this.ObjectContext.ObjectStateManager.FindRelationship(RelationshipSet,
                new KeyValuePair<string, EntityKey>(RelationshipNavigation.From, ownerKey),
                new KeyValuePair<string, EntityKey>(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(IEntityWrapper wrappedEntity, RelationshipNavigation navigation, bool doCascadeDelete)
        {
            if (wrappedEntity == null)
            {
                wrappedEntity = EntityWrapperFactory.NullWrapper;
            }
            if (null != _wrappedCachedValue.Entity)
            {
                // 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 ((wrappedEntity.Entity == _wrappedCachedValue.Entity) && (navigation.Equals(this.RelationshipNavigation)))
                {
                    Remove(_wrappedCachedValue, /*fixup*/false, /*deleteEntity*/false, /*deleteOwner*/false, /*applyReferentialConstraints*/false, /*preserveForeignKey*/false);
                }
                else
                {
                    Remove(_wrappedCachedValue, /*fixup*/true, doCascadeDelete, /*deleteOwner*/false, /*applyReferentialConstraints*/true, /*preserveForeignKey*/false);
                }
            }
            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 (WrappedOwner.Entity != null && WrappedOwner.Context != null && !UsingNoTracking)
                {
                    EntityEntry ownerEntry = WrappedOwner.Context.ObjectStateManager.GetEntityEntry(WrappedOwner.Entity);
                    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.WrappedOwner.Entity != 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;
            }
        }
 
        internal override void ClearWrappedValues()
        {
            this._cachedValue = null;
            this._wrappedCachedValue = NullEntityWrapper.NullWrapper;
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="relationshipAlreadyExists"></param>
        /// <returns>True if the verify succeeded, False if the Add should no-op</returns>
        internal override bool VerifyEntityForAdd(IEntityWrapper wrappedEntity, bool relationshipAlreadyExists)
        {
            if (!relationshipAlreadyExists && this.ContainsEntity(wrappedEntity))
            {
                return false;
            }
 
            this.VerifyType(wrappedEntity);
 
            return true;
        }
 
        internal override bool CanSetEntityType(IEntityWrapper wrappedEntity)
        {
            return wrappedEntity.Entity is TEntity;
        }
 
        internal override void VerifyType(IEntityWrapper wrappedEntity)
        {
            if (!CanSetEntityType(wrappedEntity))
            {
                throw EntityUtil.InvalidContainedTypeReference(wrappedEntity.Entity.GetType().FullName, typeof(TEntity).FullName);
            }
        }
 
        /// <summary>
        /// Disconnected adds are not supported for an EntityReference so we should report this as an error.
        /// </summary>
        /// <param name="entity">The entity to add to the related end in a disconnected state.</param>
        internal override void DisconnectedAdd(IEntityWrapper wrappedEntity)
        {
            CheckOwnerNull();
        }
 
        /// <summary>
        /// Disconnected removes are not supported for an EntityReference so we should report this as an error.
        /// </summary>
        /// <param name="entity">The entity to remove from the related end in a disconnected state.</param>
        internal override bool DisconnectedRemove(IEntityWrapper wrappedEntity)
        {
            CheckOwnerNull();
            return false;
        }
 
        /// <summary>
        /// Remove from the RelatedEnd
        /// </summary>
        /// <param name="wrappedEntity"></param>
        /// <param name="resetIsLoaded"></param>
        /// <returns></returns>
        internal override bool RemoveFromLocalCache(IEntityWrapper wrappedEntity, bool resetIsLoaded, bool preserveForeignKey)
        {
            Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
            Debug.Assert(null == _wrappedCachedValue.Entity || wrappedEntity.Entity == _wrappedCachedValue.Entity, "The specified object is not a part of this relationship.");
 
            _wrappedCachedValue = EntityWrapperFactory.NullWrapper;
            _cachedValue = null;
 
            if (resetIsLoaded)
            {
                _isLoaded = false;
            }
 
            // This code sets nullable FK properties on a dependent end to null when a relationship has been nulled.
            if (ObjectContext != null && IsForeignKey && !preserveForeignKey)
            {
                NullAllForeignKeys();
            }
            return true;
        }
 
        /// <summary>
        /// Remove from the POCO collection
        /// </summary>
        /// <param name="wrappedEntity"></param>
        /// <returns></returns>
        internal override bool RemoveFromObjectCache(IEntityWrapper wrappedEntity)
        {
            Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
 
            // For POCO entities - clear the CLR reference
            if (this.TargetAccessor.HasProperty)
            {
                this.WrappedOwner.RemoveNavigationPropertyValue(this, (TEntity)wrappedEntity.Entity);
            }
 
            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<string, KeyValuePair<object, IntBox>> properties, HashSet<object> visited)
        {
            Debug.Assert(properties != null);
 
            if (this._wrappedCachedValue.Entity != null)
            {
                // Dictionary< propertyName, <propertyValue, counter>>
                Dictionary<string, KeyValuePair<object, IntBox>> 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)
                    {
                        // Detect circular references
                        if (visited.Contains(_wrappedCachedValue))
                        {
                            throw EntityUtil.CircularRelationshipsWithReferentialConstraints();
                        }
                        visited.Add(_wrappedCachedValue);
 
                        _wrappedCachedValue.RelationshipManager.RetrieveReferentialConstraintProperties(out retrievedProperties, visited, includeOwnValues: true);
 
                        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<C_ID> - Order<O_ID, Client_ID>   
                        //          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)
                        {
                            EntityEntry.AddOrIncreaseCounter(
                                    properties,
                                    constraint.ToProperties[i].Name,
                                    retrievedProperties[constraint.FromProperties[i].Name].Key);
                        }
                    }
                }
            }
        }
 
        internal override bool IsEmpty()
        {
            return _wrappedCachedValue.Entity == null;
        }
 
        internal override void VerifyMultiplicityConstraintsForAdd(bool applyConstraints)
        {
            if (applyConstraints && !this.IsEmpty())
            {
                throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(this.RelationshipNavigation.To, this.RelationshipNavigation.RelationshipName);
            }
        }
 
        // 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(IEntityWrapper wrappedEntity)
        {
            // Using operator 'as' instead of () allows calling ContainsEntity
            // with entity of different type than TEntity.
            return null != _wrappedCachedValue.Entity && _wrappedCachedValue.Entity == wrappedEntity.Entity;
        }
 
        // 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<TEntity> CreateSourceQuery()
        {
            CheckOwnerNull();
            bool hasResults;
            return CreateSourceQuery<TEntity>(DefaultMergeOption, out hasResults);
        }
 
        internal override IEnumerable CreateSourceQueryInternal()
        {
            return CreateSourceQuery();
        }
        //End identical code
 
        /// <summary>
        /// Take any values in the incoming RelatedEnd and sets them onto the values 
        /// that currently exist in this RelatedEnd
        /// </summary>
        /// <param name="rhs"></param>
        internal void InitializeWithValue(RelatedEnd relatedEnd)
        {
            Debug.Assert(this._wrappedCachedValue.Entity == null, "The EntityReference already has a value.");
            EntityReference<TEntity> reference = relatedEnd as EntityReference<TEntity>;
            if (reference != null && reference._wrappedCachedValue.Entity != null)
            {
                _wrappedCachedValue = reference._wrappedCachedValue;
                _cachedValue = (TEntity)_wrappedCachedValue.Entity;
            }
        }
 
        internal override bool CheckIfNavigationPropertyContainsEntity(IEntityWrapper wrapper)
        {
            Debug.Assert(this.RelationshipNavigation != null, "null RelationshipNavigation");
 
            // If the navigation property doesn't exist (e.g. unidirectional prop), then it can't contain the entity.
            if (!TargetAccessor.HasProperty)
            {
                return false;
            }
 
            object value = this.WrappedOwner.GetNavigationPropertyValue(this);
 
            return Object.Equals(value, wrapper.Entity);
        }
 
        internal override void VerifyNavigationPropertyForAdd(IEntityWrapper wrapper)
        {
            if (this.TargetAccessor.HasProperty)
            {
                object value = WrappedOwner.GetNavigationPropertyValue(this);
                if (!Object.ReferenceEquals(null, value) && !Object.Equals(value, wrapper.Entity))
                {
                    throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(
                        this.RelationshipNavigation.To, this.RelationshipNavigation.RelationshipName);
                }
            }
        }
 
        // This method is required to maintain compatibility with the v1 binary serialization format. 
        // In particular, it recreates a entity wrapper from the serialized cached value.
        // Note that this is only expected to work for non-POCO entities, since serialization of POCO
        // entities will not result in serialization of the RelationshipManager or its related objects.
        [OnDeserialized()]
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public void OnRefDeserialized(StreamingContext context)
        {
            _wrappedCachedValue = EntityWrapperFactory.WrapEntityUsingContext(_cachedValue, ObjectContext);
        }
 
        [OnSerializing()]
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public void OnSerializing(StreamingContext context)
        {
            if (!(WrappedOwner.Entity is IEntityWithRelationships))
            {
                throw new InvalidOperationException(System.Data.Entity.Strings.RelatedEnd_CannotSerialize("EntityReference"));
            }
        }
 
        #region Add
 
        /// <summary>
        /// AddToLocalEnd is used by both APIs a) RelatedEnd.Add b) Value property setter.
        /// ApplyConstraints is true in case of RelatedEnd.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.
        /// </summary>
        internal override void AddToLocalCache(IEntityWrapper wrappedEntity, bool applyConstraints)
        {
            if (wrappedEntity != _wrappedCachedValue)
            {
                TransactionManager tm = ObjectContext != null ? ObjectContext.ObjectStateManager.TransactionManager : null;
                if (applyConstraints && null != _wrappedCachedValue.Entity)
                {
                    // The idea here is that we want to throw for constraint violations in things that we are bringing in,
                    // but not when replacing references of things already in the context.  Therefore, if the the thing that
                    // we're replacing is in ProcessedEntities it means we're bringing it in and we should throw.
                    if (tm == null || tm.ProcessedEntities == null || tm.ProcessedEntities.Contains(_wrappedCachedValue))
                    {
                        throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(this.RelationshipNavigation.To, this.RelationshipNavigation.RelationshipName);
                    }
                }
                if (tm != null && wrappedEntity.Entity != null)
                {
                    // Setting this flag will prevent the FK from being temporarily set to null while changing
                    // it from one value to the next.
                    tm.BeginRelatedEndAdd();
                }
                try
                {
                    ClearCollectionOrRef(null, null, false);
                    _wrappedCachedValue = wrappedEntity;
                    _cachedValue = (TEntity)wrappedEntity.Entity;
                }
                finally
                {
                    if (tm != null && tm.IsRelatedEndAdd)
                    {
                        tm.EndRelatedEndAdd();
                    }
                }
            }
        }
 
        internal override void AddToObjectCache(IEntityWrapper wrappedEntity)
        {
            // For POCO entities - set the CLR reference
            if (this.TargetAccessor.HasProperty)
            {
                this.WrappedOwner.SetNavigationPropertyValue(this, wrappedEntity.Entity);
            }
        }
 
        #endregion
    }
}