1 //---------------------------------------------------------------------
2 // <copyright file="EntityReference_TResultType.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 namespace System.Data.Objects.DataClasses
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.ComponentModel;
16 using System.Data.Metadata.Edm;
17 using System.Data.Objects.Internal;
18 using System.Diagnostics;
19 using System.Runtime.Serialization;
22 /// Models a relationship end with multiplicity 1.
24 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
27 public sealed class EntityReference<TEntity> : EntityReference
34 // The following fields are serialized. Adding or removing a serialized field is considered
35 // a breaking change. This includes changing the field type or field name of existing
36 // serialized fields. If you need to make this kind of change, it may be possible, but it
37 // will require some custom serialization/deserialization code.
38 // Note that this field should no longer be used directly. Instead, use the _wrappedCachedValue
39 // field. This field is retained only for compatibility with the serialization format introduced in v1.
40 private TEntity _cachedValue;
43 private IEntityWrapper _wrappedCachedValue;
50 /// The default constructor is required for some serialization scenarios. It should not be used to
51 /// create new EntityReferences. Use the GetRelatedReference or GetRelatedEnd methods on the RelationshipManager
54 public EntityReference()
56 _wrappedCachedValue = EntityWrapperFactory.NullWrapper;
59 internal EntityReference(IEntityWrapper wrappedOwner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
60 : base(wrappedOwner, navigation, relationshipFixer)
62 _wrappedCachedValue = EntityWrapperFactory.NullWrapper;
70 /// Stub only please replace with actual implementation
72 [System.Xml.Serialization.SoapIgnore]
73 [System.Xml.Serialization.XmlIgnore]
79 return (TEntity)ReferenceValue.Entity;
83 ReferenceValue = EntityWrapperFactory.WrapEntityUsingContext(value, ObjectContext);
87 internal override IEntityWrapper CachedValue
89 get { return _wrappedCachedValue; }
92 internal override IEntityWrapper ReferenceValue
97 return _wrappedCachedValue;
102 //setting to same value is a no-op (SQL BU DT # 446320)
103 //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
104 if (value.Entity != null && value.Entity == _wrappedCachedValue.Entity)
109 if (null != value.Entity)
111 // Note that this is only done for the case where we are not setting the ref to null because
112 // clearing a ref is okay--it will cause the dependent to become deleted/detached.
113 ValidateOwnerWithRIConstraints(value, value == EntityWrapperFactory.NullWrapper ? null : value.EntityKey, checkBothEnds: true);
114 ObjectContext context = ObjectContext ?? value.Context;
117 context.ObjectStateManager.TransactionManager.EntityBeingReparented = GetDependentEndOfReferentialConstraint(value.Entity);
121 Add(value, /*applyConstraints*/false);
127 context.ObjectStateManager.TransactionManager.EntityBeingReparented = null;
135 if (_wrappedCachedValue.Entity != null)
137 // The other end of relationship can be the EntityReference or EntityCollection
138 // If the other end is EntityReference, its IsLoaded property should be set to FALSE
139 RelatedEnd relatedEnd = GetOtherEndOfRelationship(_wrappedCachedValue);
140 relatedEnd.OnRelatedEndClear();
147 if (ObjectContext != null && ObjectContext.ContextOptions.UseConsistentNullReferenceBehavior)
149 AttemptToNullFKsOnRefOrKeySetToNull();
153 ClearCollectionOrRef(null, null, false);
163 /// Loads the related entity or entities into the local related end using the supplied MergeOption.
165 public override void Load(MergeOption mergeOption)
169 // Validate that the Load is possible
171 ObjectQuery<TEntity> sourceQuery = ValidateLoad<TEntity>(mergeOption, "EntityReference", out hasResults);
173 _suppressEvents = true; // we do not want any event during the bulk operation
176 List<TEntity> refreshedValue = null;
179 // Only issue a query if we know it can produce results (in the case of FK, there may not be any
181 refreshedValue = new List<TEntity>(GetResults<TEntity>(sourceQuery));
183 if (null == refreshedValue || refreshedValue.Count == 0)
185 if (!((AssociationType)base.RelationMetadata).IsForeignKey && ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One)
187 //query returned zero related end; one related end was expected.
188 throw EntityUtil.LessThanExpectedRelatedEntitiesFound();
190 else if (mergeOption == MergeOption.OverwriteChanges || mergeOption == MergeOption.PreserveChanges)
192 // This entity is not related to anything in this AssociationSet and Role on the server.
193 // If there is an existing _cachedValue, we may need to clear it out, based on the MergeOption
194 EntityKey sourceKey = WrappedOwner.EntityKey;
195 EntityUtil.CheckEntityKeyNull(sourceKey);
196 ObjectStateManager.RemoveRelationships(ObjectContext, mergeOption, (AssociationSet)RelationshipSet, sourceKey, (AssociationEndMember)FromEndProperty);
198 // else this is NoTracking or AppendOnly, and no entity was retrieved by the Load, so there's nothing extra to do
200 // Since we have no value and are not doing a merge, the last step is to set IsLoaded to true
203 else if (refreshedValue.Count == 1)
205 Merge<TEntity>(refreshedValue, mergeOption, true /*setIsLoaded*/);
209 // More than 1 result, which is non-recoverable data inconsistency
210 throw EntityUtil.MoreThanExpectedRelatedEntitiesFound();
215 _suppressEvents = false;
217 // fire the AssociationChange with Refresh
218 OnAssociationChanged(CollectionChangeAction.Refresh, null);
222 /// This operation is not allowed if the owner is null
224 /// <returns></returns>
225 internal override IEnumerable GetInternalEnumerable()
227 if (ReferenceValue.Entity != null)
229 yield return (TEntity)ReferenceValue.Entity;
233 internal override IEnumerable<IEntityWrapper> GetWrappedEntities()
236 return _wrappedCachedValue.Entity == null ? new IEntityWrapper[0] : new IEntityWrapper[] { _wrappedCachedValue };
240 /// Attaches an entity to the EntityReference. The given
241 /// entity is not assumed to be the complete set of related entities.
243 /// Owner and all entities passed in must be in Unchanged or Modified state.
244 /// Deleted elements are allowed only when the state manager is already tracking the relationship
247 /// <param name="entity">The entity to attach to the EntityCollection</param>
248 /// <exception cref="ArgumentNullException">Thrown when <paramref name="entity"/> is null.</exception>
249 /// <exception cref="InvalidOperationException">Thrown when the entity cannot be related via the current relationship end.</exception>
250 public void Attach(TEntity entity)
253 EntityUtil.CheckArgumentNull(entity, "entity");
254 Attach(new IEntityWrapper[] { EntityWrapperFactory.WrapEntityUsingContext(entity, ObjectContext) }, false);
257 internal override void Include(bool addRelationshipAsUnchanged, bool doAttach)
259 Debug.Assert(this.ObjectContext != null, "Should not be trying to add entities to state manager if context is null");
261 // If we have an actual value or a key for this reference, add it to the context
262 if (null != _wrappedCachedValue.Entity)
264 // Sometimes with mixed POCO and IPOCO, you can get different instances of IEntityWrappers stored in the IPOCO related ends
265 // These should be replaced by the IEntityWrapper that is stored in the context
266 IEntityWrapper identityWrapper = EntityWrapperFactory.WrapEntityUsingContext(_wrappedCachedValue.Entity, WrappedOwner.Context);
267 if (identityWrapper != _wrappedCachedValue)
269 _wrappedCachedValue = identityWrapper;
271 IncludeEntity(_wrappedCachedValue, addRelationshipAsUnchanged, doAttach);
273 else if (DetachedEntityKey != null)
275 IncludeEntityKey(doAttach);
277 // else there is nothing to add for this relationship
280 private void IncludeEntityKey(bool doAttach)
282 ObjectStateManager manager = this.ObjectContext.ObjectStateManager;
284 bool addNewRelationship = false;
285 bool addKeyEntry = false;
286 EntityEntry existingEntry = manager.FindEntityEntry(DetachedEntityKey);
287 if (existingEntry == null)
289 // add new key entry and create a relationship with it
291 addNewRelationship = true;
295 if (existingEntry.IsKeyEntry)
297 // We have an existing key entry, so just need to add a relationship with it
299 // 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
300 if (FromEndProperty.RelationshipMultiplicity != RelationshipMultiplicity.Many)
302 // before we add a new relationship to this key entry, make sure it's not already related to something else
303 // 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
304 foreach (RelationshipEntry relationshipEntry in this.ObjectContext.ObjectStateManager.FindRelationshipsByKey(DetachedEntityKey))
306 // only care about relationships in the same AssociationSet and where the key is playing the same role that it plays in this EntityReference
307 if (relationshipEntry.IsSameAssociationSetAndRole((AssociationSet)RelationshipSet, (AssociationEndMember)ToEndMember, DetachedEntityKey) &&
308 relationshipEntry.State != EntityState.Deleted)
310 throw EntityUtil.EntityConflictsWithKeyEntry();
315 addNewRelationship = true;
319 IEntityWrapper wrappedTarget = existingEntry.WrappedEntity;
321 // Verify that the target entity is in a valid state for adding a relationship
322 if (existingEntry.State == EntityState.Deleted)
324 throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
327 // 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
328 RelatedEnd relatedEnd = wrappedTarget.RelationshipManager.GetRelatedEndInternal(RelationshipName, RelationshipNavigation.From);
329 if (FromEndProperty.RelationshipMultiplicity != RelationshipMultiplicity.Many && !relatedEnd.IsEmpty())
331 // Make sure the target entity is not already related to something else.
332 // 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
333 // and existing relationships may be deleted unexpectedly. RelatedEnd.Include should not remove existing relationships, only add new ones.
334 throw EntityUtil.EntityConflictsWithKeyEntry();
337 // We have an existing entity with the same key, just hook up the related ends
338 this.Add(wrappedTarget,
339 applyConstraints: true,
340 addRelationshipAsUnchanged: doAttach,
341 relationshipAlreadyExists: false,
342 allowModifyingOtherEndOfRelationship: true,
343 forceForeignKeyChanges: true);
345 // add to the list of promoted key references so we can cleanup if a failure occurs later
346 manager.TransactionManager.PopulatedEntityReferences.Add(this);
350 // For FKs, don't create a key entry and don't create a relationship
351 if (addNewRelationship && !IsForeignKey)
353 // devnote: If we add any validation here, it needs to go here before adding the key entry,
354 // otherwise we have to clean up that entry if the validation fails
358 EntitySet targetEntitySet = DetachedEntityKey.GetEntitySet(this.ObjectContext.MetadataWorkspace);
359 manager.AddKeyEntry(DetachedEntityKey, targetEntitySet);
362 EntityKey ownerKey = WrappedOwner.EntityKey;
363 EntityUtil.CheckEntityKeyNull(ownerKey);
364 RelationshipWrapper wrapper = new RelationshipWrapper((AssociationSet)RelationshipSet,
365 RelationshipNavigation.From, ownerKey, RelationshipNavigation.To, DetachedEntityKey);
366 manager.AddNewRelation(wrapper, doAttach ? EntityState.Unchanged : EntityState.Added);
370 internal override void Exclude()
372 Debug.Assert(this.ObjectContext != null, "Should not be trying to remove entities from state manager if context is null");
374 if (null != _wrappedCachedValue.Entity)
376 // It is possible that _cachedValue was originally null in this graph, but was only set
377 // while the graph was being added, if the DetachedEntityKey matched its key. In that case,
378 // we only want to clear _cachedValue and delete the relationship entry, but not remove the entity
379 // itself from the context.
380 TransactionManager transManager = ObjectContext.ObjectStateManager.TransactionManager;
381 bool doFullRemove = transManager.PopulatedEntityReferences.Contains(this);
382 bool doRelatedEndRemove = transManager.AlignedEntityReferences.Contains(this);
383 // For POCO, if the entity is undergoing snapshot for the first time, then in this step we actually
384 // need to really exclude it rather than just disconnecting it. If we don't, then it has the potential
385 // to remain in the context at the end of the rollback process.
386 if ((transManager.ProcessedEntities == null || !transManager.ProcessedEntities.Contains(_wrappedCachedValue)) &&
387 (doFullRemove || doRelatedEndRemove))
389 // Retrieve the relationship entry before _cachedValue is set to null during Remove
390 RelationshipEntry relationshipEntry = IsForeignKey ? null : FindRelationshipEntryInObjectStateManager(_wrappedCachedValue);
391 Debug.Assert(IsForeignKey || relationshipEntry != null, "Should have been able to find a valid relationship since _cachedValue is non-null");
393 // Remove the related ends and mark the relationship as deleted, but don't propagate the changes to the target entity itself
394 Remove(_wrappedCachedValue,
395 doFixup: doFullRemove,
398 applyReferentialConstraints: false,
399 preserveForeignKey: true);
401 // The relationship will now either be detached (if it was previously in the Added state), or Deleted (if it was previously Unchanged)
402 // If it's Deleted, we need to AcceptChanges to get rid of it completely
403 if (relationshipEntry != null && relationshipEntry.State != EntityState.Detached)
405 relationshipEntry.AcceptChanges();
408 // Since this has been processed, remove it from the list
411 transManager.PopulatedEntityReferences.Remove(this);
415 transManager.AlignedEntityReferences.Remove(this);
420 ExcludeEntity(_wrappedCachedValue);
423 else if (DetachedEntityKey != null)
425 // there may still be relationship entries with stubs that need to be removed
426 // this works whether we just added the key entry along with the relationship or if it was already existing
429 // else there is nothing to remove for this relationship
432 private void ExcludeEntityKey()
434 EntityKey ownerKey = WrappedOwner.EntityKey;
436 RelationshipEntry relationshipEntry = this.ObjectContext.ObjectStateManager.FindRelationship(RelationshipSet,
437 new KeyValuePair<string, EntityKey>(RelationshipNavigation.From, ownerKey),
438 new KeyValuePair<string, EntityKey>(RelationshipNavigation.To, DetachedEntityKey));
440 // we may have failed in adding the graph before we actually added this relationship, so make sure we actually found one
441 if (relationshipEntry != null)
443 relationshipEntry.Delete(/*doFixup*/ false);
444 // If entry was Added before, it is now Detached, otherwise AcceptChanges to detach it
445 if (relationshipEntry.State != EntityState.Detached)
447 relationshipEntry.AcceptChanges();
452 internal override void ClearCollectionOrRef(IEntityWrapper wrappedEntity, RelationshipNavigation navigation, bool doCascadeDelete)
454 if (wrappedEntity == null)
456 wrappedEntity = EntityWrapperFactory.NullWrapper;
458 if (null != _wrappedCachedValue.Entity)
460 // Following condition checks if we have already visited this graph node. If its true then
461 // we should not do fixup because that would cause circular loop
462 if ((wrappedEntity.Entity == _wrappedCachedValue.Entity) && (navigation.Equals(this.RelationshipNavigation)))
464 Remove(_wrappedCachedValue, /*fixup*/false, /*deleteEntity*/false, /*deleteOwner*/false, /*applyReferentialConstraints*/false, /*preserveForeignKey*/false);
468 Remove(_wrappedCachedValue, /*fixup*/true, doCascadeDelete, /*deleteOwner*/false, /*applyReferentialConstraints*/true, /*preserveForeignKey*/false);
473 // this entity reference could be replacing a relationship that points to a key entry
474 // we need to search relationships on the Owner entity to see if this is true, and if so remove the relationship entry
475 if (WrappedOwner.Entity != null && WrappedOwner.Context != null && !UsingNoTracking)
477 EntityEntry ownerEntry = WrappedOwner.Context.ObjectStateManager.GetEntityEntry(WrappedOwner.Entity);
478 ownerEntry.DeleteRelationshipsThatReferenceKeys(this.RelationshipSet, this.ToEndMember);
482 // If we have an Owner, clear the DetachedEntityKey.
483 // 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
484 if (this.WrappedOwner.Entity != null)
486 // Clear the detachedEntityKey as well. In cases where we have to fix up the detachedEntityKey, we will not always be able to detect
487 // if we have *only* a Deleted relationship for a given entity/relationship/role, so clearing this here will ensure that
488 // even if no other relationships are added, the key value will still be correct.
489 ((EntityReference)this).DetachedEntityKey = null;
493 internal override void ClearWrappedValues()
495 this._cachedValue = null;
496 this._wrappedCachedValue = NullEntityWrapper.NullWrapper;
502 /// <param name="entity"></param>
503 /// <param name="relationshipAlreadyExists"></param>
504 /// <returns>True if the verify succeeded, False if the Add should no-op</returns>
505 internal override bool VerifyEntityForAdd(IEntityWrapper wrappedEntity, bool relationshipAlreadyExists)
507 if (!relationshipAlreadyExists && this.ContainsEntity(wrappedEntity))
512 this.VerifyType(wrappedEntity);
517 internal override bool CanSetEntityType(IEntityWrapper wrappedEntity)
519 return wrappedEntity.Entity is TEntity;
522 internal override void VerifyType(IEntityWrapper wrappedEntity)
524 if (!CanSetEntityType(wrappedEntity))
526 throw EntityUtil.InvalidContainedTypeReference(wrappedEntity.Entity.GetType().FullName, typeof(TEntity).FullName);
531 /// Disconnected adds are not supported for an EntityReference so we should report this as an error.
533 /// <param name="entity">The entity to add to the related end in a disconnected state.</param>
534 internal override void DisconnectedAdd(IEntityWrapper wrappedEntity)
540 /// Disconnected removes are not supported for an EntityReference so we should report this as an error.
542 /// <param name="entity">The entity to remove from the related end in a disconnected state.</param>
543 internal override bool DisconnectedRemove(IEntityWrapper wrappedEntity)
550 /// Remove from the RelatedEnd
552 /// <param name="wrappedEntity"></param>
553 /// <param name="resetIsLoaded"></param>
554 /// <returns></returns>
555 internal override bool RemoveFromLocalCache(IEntityWrapper wrappedEntity, bool resetIsLoaded, bool preserveForeignKey)
557 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
558 Debug.Assert(null == _wrappedCachedValue.Entity || wrappedEntity.Entity == _wrappedCachedValue.Entity, "The specified object is not a part of this relationship.");
560 _wrappedCachedValue = EntityWrapperFactory.NullWrapper;
568 // This code sets nullable FK properties on a dependent end to null when a relationship has been nulled.
569 if (ObjectContext != null && IsForeignKey && !preserveForeignKey)
571 NullAllForeignKeys();
577 /// Remove from the POCO collection
579 /// <param name="wrappedEntity"></param>
580 /// <returns></returns>
581 internal override bool RemoveFromObjectCache(IEntityWrapper wrappedEntity)
583 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
585 // For POCO entities - clear the CLR reference
586 if (this.TargetAccessor.HasProperty)
588 this.WrappedOwner.RemoveNavigationPropertyValue(this, (TEntity)wrappedEntity.Entity);
594 // Method used to retrieve properties from principal entities.
595 // NOTE: 'properties' list is modified in this method and may already contains some properties.
596 internal override void RetrieveReferentialConstraintProperties(Dictionary<string, KeyValuePair<object, IntBox>> properties, HashSet<object> visited)
598 Debug.Assert(properties != null);
600 if (this._wrappedCachedValue.Entity != null)
602 // Dictionary< propertyName, <propertyValue, counter>>
603 Dictionary<string, KeyValuePair<object, IntBox>> retrievedProperties;
605 // PERFORMANCE: ReferentialConstraints collection in typical scenario is very small (1-3 elements)
606 foreach (ReferentialConstraint constraint in ((AssociationType)this.RelationMetadata).ReferentialConstraints)
608 if (constraint.ToRole == FromEndProperty)
610 // Detect circular references
611 if (visited.Contains(_wrappedCachedValue))
613 throw EntityUtil.CircularRelationshipsWithReferentialConstraints();
615 visited.Add(_wrappedCachedValue);
617 _wrappedCachedValue.RelationshipManager.RetrieveReferentialConstraintProperties(out retrievedProperties, visited, includeOwnValues: true);
619 Debug.Assert(retrievedProperties != null);
620 Debug.Assert(constraint.FromProperties.Count == constraint.ToProperties.Count, "Referential constraints From/To properties list have different size");
622 // Following loop rewrites properties from "retrievedProperties" into "properties".
623 // At the same time, property's name is translated from name from principal end into name from dependent end:
624 // Example: Client<C_ID> - Order<O_ID, Client_ID>
625 // Client is principal end, Order is dependent end, Client.C_ID == Order.Client_ID
626 // Input : retrievedProperties = { "C_ID" = 123 }
627 // Output: properties = { "Client_ID" = 123 }
629 // NOTE order of properties in collections constraint.From/ToProperties is important
630 for (int i = 0; i < constraint.FromProperties.Count; ++i)
632 EntityEntry.AddOrIncreaseCounter(
634 constraint.ToProperties[i].Name,
635 retrievedProperties[constraint.FromProperties[i].Name].Key);
642 internal override bool IsEmpty()
644 return _wrappedCachedValue.Entity == null;
647 internal override void VerifyMultiplicityConstraintsForAdd(bool applyConstraints)
649 if (applyConstraints && !this.IsEmpty())
651 throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(this.RelationshipNavigation.To, this.RelationshipNavigation.RelationshipName);
655 // Update IsLoaded flag if necessary
656 // This method is called when Clear() was called on the other end of relationship (if the other end is EntityCollection)
657 // or when Value property of the other end was set to null (if the other end is EntityReference).
658 // This method is used only when NoTracking option was used.
659 internal override void OnRelatedEndClear()
661 // If other end of relationship was loaded, it mean that this end was also cleared.
665 internal override bool ContainsEntity(IEntityWrapper wrappedEntity)
667 // Using operator 'as' instead of () allows calling ContainsEntity
668 // with entity of different type than TEntity.
669 return null != _wrappedCachedValue.Entity && _wrappedCachedValue.Entity == wrappedEntity.Entity;
672 // Identical code is in EntityCollection, but this can't be moved to the base class because it relies on the
673 // knowledge of the generic type, and the base class isn't generic
674 public ObjectQuery<TEntity> CreateSourceQuery()
678 return CreateSourceQuery<TEntity>(DefaultMergeOption, out hasResults);
681 internal override IEnumerable CreateSourceQueryInternal()
683 return CreateSourceQuery();
688 /// Take any values in the incoming RelatedEnd and sets them onto the values
689 /// that currently exist in this RelatedEnd
691 /// <param name="rhs"></param>
692 internal void InitializeWithValue(RelatedEnd relatedEnd)
694 Debug.Assert(this._wrappedCachedValue.Entity == null, "The EntityReference already has a value.");
695 EntityReference<TEntity> reference = relatedEnd as EntityReference<TEntity>;
696 if (reference != null && reference._wrappedCachedValue.Entity != null)
698 _wrappedCachedValue = reference._wrappedCachedValue;
699 _cachedValue = (TEntity)_wrappedCachedValue.Entity;
703 internal override bool CheckIfNavigationPropertyContainsEntity(IEntityWrapper wrapper)
705 Debug.Assert(this.RelationshipNavigation != null, "null RelationshipNavigation");
707 // If the navigation property doesn't exist (e.g. unidirectional prop), then it can't contain the entity.
708 if (!TargetAccessor.HasProperty)
713 object value = this.WrappedOwner.GetNavigationPropertyValue(this);
715 return Object.Equals(value, wrapper.Entity);
718 internal override void VerifyNavigationPropertyForAdd(IEntityWrapper wrapper)
720 if (this.TargetAccessor.HasProperty)
722 object value = WrappedOwner.GetNavigationPropertyValue(this);
723 if (!Object.ReferenceEquals(null, value) && !Object.Equals(value, wrapper.Entity))
725 throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(
726 this.RelationshipNavigation.To, this.RelationshipNavigation.RelationshipName);
731 // This method is required to maintain compatibility with the v1 binary serialization format.
732 // In particular, it recreates a entity wrapper from the serialized cached value.
733 // Note that this is only expected to work for non-POCO entities, since serialization of POCO
734 // entities will not result in serialization of the RelationshipManager or its related objects.
737 [EditorBrowsable(EditorBrowsableState.Never)]
738 public void OnRefDeserialized(StreamingContext context)
740 _wrappedCachedValue = EntityWrapperFactory.WrapEntityUsingContext(_cachedValue, ObjectContext);
745 [EditorBrowsable(EditorBrowsableState.Never)]
746 public void OnSerializing(StreamingContext context)
748 if (!(WrappedOwner.Entity is IEntityWithRelationships))
750 throw new InvalidOperationException(System.Data.Entity.Strings.RelatedEnd_CannotSerialize("EntityReference"));
757 /// AddToLocalEnd is used by both APIs a) RelatedEnd.Add b) Value property setter.
758 /// ApplyConstraints is true in case of RelatedEnd.Add because one cannot add entity to ref it its already set
759 /// however applyConstraints is false in case of Value property setter because value can be set to a new value
760 /// even if its non null.
762 internal override void AddToLocalCache(IEntityWrapper wrappedEntity, bool applyConstraints)
764 if (wrappedEntity != _wrappedCachedValue)
766 TransactionManager tm = ObjectContext != null ? ObjectContext.ObjectStateManager.TransactionManager : null;
767 if (applyConstraints && null != _wrappedCachedValue.Entity)
769 // The idea here is that we want to throw for constraint violations in things that we are bringing in,
770 // but not when replacing references of things already in the context. Therefore, if the the thing that
771 // we're replacing is in ProcessedEntities it means we're bringing it in and we should throw.
772 if (tm == null || tm.ProcessedEntities == null || tm.ProcessedEntities.Contains(_wrappedCachedValue))
774 throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(this.RelationshipNavigation.To, this.RelationshipNavigation.RelationshipName);
777 if (tm != null && wrappedEntity.Entity != null)
779 // Setting this flag will prevent the FK from being temporarily set to null while changing
780 // it from one value to the next.
781 tm.BeginRelatedEndAdd();
785 ClearCollectionOrRef(null, null, false);
786 _wrappedCachedValue = wrappedEntity;
787 _cachedValue = (TEntity)wrappedEntity.Entity;
791 if (tm != null && tm.IsRelatedEndAdd)
793 tm.EndRelatedEndAdd();
799 internal override void AddToObjectCache(IEntityWrapper wrappedEntity)
801 // For POCO entities - set the CLR reference
802 if (this.TargetAccessor.HasProperty)
804 this.WrappedOwner.SetNavigationPropertyValue(this, wrappedEntity.Entity);