1 //---------------------------------------------------------------------
2 // <copyright file="RelatedEnd.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.Objects.DataClasses
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.ComponentModel;
15 using System.Data.Common.Utils;
16 using System.Data.Metadata.Edm;
17 using System.Data.Objects.Internal;
18 using System.Diagnostics;
20 using System.Runtime.Serialization;
24 /// Base class for EntityCollection and EntityReference
27 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
30 public abstract class RelatedEnd : IRelatedEnd
34 // Internal Constructors
38 /// The default constructor is required for some serialization scenarios with EntityReference.
42 _wrappedOwner = EntityWrapperFactory.NullWrapper;
45 internal RelatedEnd(IEntityWrapper wrappedOwner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
47 EntityUtil.CheckArgumentNull(wrappedOwner, "wrappedOwner");
48 EntityUtil.CheckArgumentNull(wrappedOwner.Entity, "wrappedOwner.Entity");
49 EntityUtil.CheckArgumentNull(navigation, "navigation");
50 EntityUtil.CheckArgumentNull(relationshipFixer, "fixer");
52 InitializeRelatedEnd(wrappedOwner, navigation, relationshipFixer);
58 private const string _entityKeyParamName = "EntityKeyValue";
60 // The following fields are serialized. Adding or removing a serialized field is considered
61 // a breaking change. This includes changing the field type or field name of existing
62 // serialized fields. If you need to make this kind of change, it may be possible, but it
63 // will require some custom serialization/deserialization code.
64 // These fields should not be changed once they have been initialized with non-null values, but they can't be read-only because there
65 // are serialization scenarios where they have to be set after construction
68 /// Note that this field should no longer be used directly. Instead, use the _wrappedOwner
69 /// field. This field is retained only for compatibility with the serialization format introduced in v1.
72 private IEntityWithRelationships _owner;
74 private RelationshipNavigation _navigation;
75 private IRelationshipFixer _relationshipFixer;
77 internal bool _isLoaded;
79 // The fields in this group are set only when attached to a context, so we don't need to serialize.
81 private RelationshipSet _relationshipSet;
83 private ObjectContext _context;
85 private bool _usingNoTracking;
87 private RelationshipType _relationMetadata;
89 private RelationshipEndMember _fromEndProperty; //owner end property
91 private RelationshipEndMember _toEndProperty;
93 private string _sourceQuery;
95 private IEnumerable<EdmMember> _sourceQueryParamProperties; // indicates which properties populate query parameters
98 internal bool _suppressEvents;
100 internal CollectionChangeEventHandler _onAssociationChanged;
103 private IEntityWrapper _wrappedOwner;
110 /// Event to notify changes in the Associations.
112 public event CollectionChangeEventHandler AssociationChanged
117 _onAssociationChanged += value;
122 _onAssociationChanged -= value;
126 /// <summary>internal event to notify change in collection</summary>
127 internal virtual event CollectionChangeEventHandler AssociationChangedForObjectView
129 // we fire this event only from EntityCollection, definitely not from EntityReference
130 add { Debug.Assert(false, "should never happen"); }
131 remove { Debug.Assert(false, "should never happen"); }
140 internal bool IsForeignKey
144 Debug.Assert(this.ObjectContext != null, "the IsForeignKey property shouldn't be used in detached scenarios");
145 Debug.Assert(this._relationMetadata != null, "this._relationMetadata == null");
147 return ((AssociationType)_relationMetadata).IsForeignKey;
152 /// This class describes a relationship navigation from the
153 /// navigation property on one entity to another entity.
154 /// RelationshipNavigation uniquely identify a relationship type.
155 /// The RelationshipNavigation class is internal only, so this property is also internal.
156 /// See RelationshipName, SourceRoleName, and TargetRoleName for the public exposure
157 /// of the information contained in this RelationshipNavigation.
159 internal RelationshipNavigation RelationshipNavigation
168 /// Name of the relationship in which this RelatedEnd is participating
170 [System.Xml.Serialization.SoapIgnore]
171 [System.Xml.Serialization.XmlIgnore]
172 public string RelationshipName
177 return _navigation.RelationshipName;
182 /// Name of the relationship source role used to generate this RelatedEnd
184 [System.Xml.Serialization.SoapIgnore]
185 [System.Xml.Serialization.XmlIgnore]
186 public string SourceRoleName
191 return _navigation.From;
196 /// Name of the relationship target role used to generate this RelatedEnd
198 [System.Xml.Serialization.SoapIgnore]
199 [System.Xml.Serialization.XmlIgnore]
200 public string TargetRoleName
205 return _navigation.To;
209 IEnumerable IRelatedEnd.CreateSourceQuery()
212 return this.CreateSourceQueryInternal();
215 internal IEntityWrapper WrappedOwner
219 return this._wrappedOwner;
223 internal ObjectContext ObjectContext
227 return this._context;
231 internal virtual void BulkDeleteAll(System.Collections.Generic.List<object> list)
233 throw EntityUtil.NotSupported();
237 /// Returns the relationship metadata associated with this RelatedEnd.
238 /// This value is available once the RelatedEnd is attached to an ObjectContext
239 /// or is retrieved with MergeOption.NoTracking
241 [System.Xml.Serialization.SoapIgnore]
242 [System.Xml.Serialization.XmlIgnore]
243 public RelationshipSet RelationshipSet
248 return this._relationshipSet;
252 internal RelationshipType RelationMetadata
256 return this._relationMetadata;
260 internal RelationshipEndMember ToEndMember
264 return this._toEndProperty;
268 internal bool UsingNoTracking
272 return this._usingNoTracking;
276 internal MergeOption DefaultMergeOption
280 return UsingNoTracking ? MergeOption.NoTracking : MergeOption.AppendOnly;
284 internal RelationshipEndMember FromEndProperty
288 return this._fromEndProperty;
293 /// IsLoaded returns true if and only if Load was called.
295 [System.Xml.Serialization.SoapIgnore]
296 [System.Xml.Serialization.XmlIgnore]
302 return this._isLoaded;
306 internal void SetIsLoaded(bool value)
308 this._isLoaded = value;
312 /// This is the query which represents the source of the
313 /// related end. It is constructed on demand using the
314 /// _connection and _cache fields and a query string based on
315 /// the type of related end and the metadata passed into its
316 /// constructor indicating the particular EDM construct the
317 /// related end models. This method is called by both subclasses of this type
318 /// and those subclasses pass in their generic type parameter in order
319 /// to produce an ObjectQuery of the right type. This allows this common
320 /// functionality to be implemented here in the base class while still
321 /// allowing the base class to be non-generic.
323 /// <param name="mergeOption">MergeOption to use when creating the query</param>
324 /// <param name="hasResults">Indicates whether the query can produce results.
325 /// For instance, a lookup with null key values cannot produce results.</param>
326 /// <returns>The query loading related entities.</returns>
327 internal ObjectQuery<TEntity> CreateSourceQuery<TEntity>(MergeOption mergeOption, out bool hasResults)
329 // must have a context
330 if (_context == null)
336 EntityEntry stateEntry = _context.ObjectStateManager.FindEntityEntry(_wrappedOwner.Entity);
337 EntityState entityState;
338 if (stateEntry == null)
342 entityState = EntityState.Detached;
346 throw EntityUtil.InvalidEntityStateSource();
351 Debug.Assert(stateEntry != null, "Entity should exist in the current context");
352 entityState = stateEntry.State;
355 //Throw incase entity is in added state, unless this is the dependent end of an FK relationship
356 if (entityState == EntityState.Added &&
358 !IsDependentEndOfReferentialConstraint(checkIdentifying: false)))
360 throw EntityUtil.InvalidEntityStateSource();
363 Debug.Assert(!(entityState != EntityState.Detached && UsingNoTracking), "Entity with NoTracking option cannot exist in the ObjectStateManager");
365 // the CreateSourceQuery method can only return non-NULL when we're
366 // either detached & mergeOption is NoTracking or
367 // Added/Modified/Unchanged/Deleted and mergeOption is NOT NoTracking
368 // (if entity is attached to the context, mergeOption should never be NoTracking)
369 // If the entity state is added, at this point it is an FK dependent end
370 if (!((entityState == EntityState.Detached && UsingNoTracking) ||
371 entityState == EntityState.Modified ||
372 entityState == EntityState.Unchanged ||
373 entityState == EntityState.Deleted ||
374 entityState == EntityState.Added))
380 // Construct a new source query and return it.
381 Debug.Assert(_relationshipSet != null, "If we are attached to a context, we should have a relationship set.");
383 // Retrieve the entity key of the owner.
384 EntityKey key = _wrappedOwner.EntityKey;
385 EntityUtil.CheckEntityKeyNull(key); // Might be null because of faulty IPOCO implementation
387 // If the source query text has not been initialized, then do so now.
388 if (null == _sourceQuery)
390 Debug.Assert(_relationshipSet.BuiltInTypeKind == BuiltInTypeKind.AssociationSet, "Non-AssociationSet Relationship Set?");
391 AssociationType associationMetadata = (AssociationType)_relationMetadata;
393 EntitySet ownerEntitySet = ((AssociationSet)_relationshipSet).AssociationSetEnds[_fromEndProperty.Name].EntitySet;
394 EntitySet targetEntitySet = ((AssociationSet)_relationshipSet).AssociationSetEnds[_toEndProperty.Name].EntitySet;
396 EntityType targetEntityType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)_toEndProperty);
397 bool ofTypeRequired = false;
398 if (!targetEntitySet.ElementType.EdmEquals(targetEntityType) &&
399 !TypeSemantics.IsSubTypeOf(targetEntitySet.ElementType, targetEntityType))
401 // If the type contained in the target entity set is not equal to
402 // or a subtype of the referenced type, then an OfType must be
403 // applied to the target entityset to yield only those elements that
404 // are of the referenced type or a subtype of the referenced type.
405 ofTypeRequired = true;
407 // The type name used in the OfType clause must be the name of the
408 // corresponding O-Space Entity type, since the source query will be
409 // parsed using the CLR perspective (by ObjectQuery).
410 TypeUsage targetOSpaceTypeUsage = ObjectContext.MetadataWorkspace.GetOSpaceTypeUsage(TypeUsage.Create(targetEntityType));
411 targetEntityType = (EntityType)targetOSpaceTypeUsage.EdmType;
414 StringBuilder sourceBuilder;
415 if (associationMetadata.IsForeignKey)
417 var fkConstraint = associationMetadata.ReferentialConstraints[0];
418 var principalProps = fkConstraint.FromProperties;
419 var dependentProps = fkConstraint.ToProperties;
420 Debug.Assert(principalProps.Count == dependentProps.Count, "Mismatched foreign key properties?");
422 if (fkConstraint.ToRole.EdmEquals(_toEndProperty))
424 // This related end goes from 'principal' to 'dependent', and has the key of the principal.
425 // In this case it is sufficient to filter the target (dependent) set where the foreign key
426 // properties have the same values as the corresponding entity key properties from the principal.
429 // FROM OfType(##DependentEntityset, ##DependentEntityType)
432 // D.DependentProperty1 = @PrincipalProperty1 [AND
434 // D.DependentPropertyN = @PrincipalPropertyN]
436 // Note that the OfType operator can be omitted if the element type of ##DependentEntitySet
437 // is equal to the Entity type produced by the target end of the relationship.
438 sourceBuilder = new StringBuilder("SELECT VALUE D FROM ");
439 AppendEntitySet(sourceBuilder, targetEntitySet, targetEntityType, ofTypeRequired);
440 sourceBuilder.Append(" AS D WHERE ");
442 // For each principal key property there is a corresponding query parameter that supplies the value
443 // from this owner's entity key, so KeyParam1 corresponds to the first key member, etc.
444 // We remember the order of the corresponding principal key values in the _sourceQueryParamProperties
446 AliasGenerator keyParamNameGen = new AliasGenerator(_entityKeyParamName); // Aliases are cached in AliasGenerator
447 _sourceQueryParamProperties = principalProps;
449 for (int idx = 0; idx < dependentProps.Count; idx++)
453 sourceBuilder.Append(" AND ");
456 sourceBuilder.Append("D.[");
457 sourceBuilder.Append(dependentProps[idx].Name);
458 sourceBuilder.Append("] = @");
459 sourceBuilder.Append(keyParamNameGen.Next());
464 // This related end goes from 'dependent' to 'principal', and has the key of the dependent
465 // In this case it is necessary to filter the target (principal) entity set on the foreign
466 // key relationship properties to retrieve the corresponding principal entity.
468 // SELECT VALUE P FROM
469 // OfType(##PrincipalEntityset, ##PrincipalEntityType) AS P
471 // P.PrincipalProperty1 = @DependentProperty1 AND ...
473 Debug.Assert(fkConstraint.FromRole.EdmEquals(_toEndProperty), "Source query for foreign key association related end is not based on principal or dependent?");
475 sourceBuilder = new StringBuilder("SELECT VALUE P FROM ");
476 AppendEntitySet(sourceBuilder, targetEntitySet, targetEntityType, ofTypeRequired);
477 sourceBuilder.Append(" AS P WHERE ");
479 AliasGenerator keyParamNameGen = new AliasGenerator(_entityKeyParamName); // Aliases are cached in AliasGenerator
480 _sourceQueryParamProperties = dependentProps;
481 for (int idx = 0; idx < principalProps.Count; idx++)
485 sourceBuilder.Append(" AND ");
487 sourceBuilder.Append("P.[");
488 sourceBuilder.Append(principalProps[idx].Name);
489 sourceBuilder.Append("] = @");
490 sourceBuilder.Append(keyParamNameGen.Next());
492 _sourceQuery = sourceBuilder.ToString();
498 // SELECT VALUE [TargetEntity]
500 // (SELECT VALUE x FROM ##RelationshipSet AS x
501 // WHERE Key(x.[##SourceRoleName]) = ROW(@key1 AS key1[..., @keyN AS keyN])
502 // ) AS [AssociationEntry]
504 // OfType(##TargetEntityset, ##TargetRole.EntityType) AS [TargetEntity]
506 // Key([AssociationEntry].##TargetRoleName) = Key(Ref([TargetEntity]))
508 // Note that the OfType operator can be omitted if the element type of ##TargetEntitySet
509 // is equal to the Entity type produced by the target end of the relationship.
511 sourceBuilder = new StringBuilder("SELECT VALUE [TargetEntity] FROM (SELECT VALUE x FROM ");
512 sourceBuilder.Append("[");
513 sourceBuilder.Append(_relationshipSet.EntityContainer.Name);
514 sourceBuilder.Append("].[");
515 sourceBuilder.Append(_relationshipSet.Name);
516 sourceBuilder.Append("] AS x WHERE Key(x.[");
517 sourceBuilder.Append(_fromEndProperty.Name);
518 sourceBuilder.Append("]) = ");
520 AppendKeyParameterRow(sourceBuilder, key.GetEntitySet(ObjectContext.MetadataWorkspace).ElementType.KeyMembers);
522 sourceBuilder.Append(") AS [AssociationEntry] INNER JOIN ");
524 AppendEntitySet(sourceBuilder, targetEntitySet, targetEntityType, ofTypeRequired);
526 sourceBuilder.Append(" AS [TargetEntity] ON Key([AssociationEntry].[");
527 sourceBuilder.Append(_toEndProperty.Name);
528 sourceBuilder.Append("]) = Key(Ref([TargetEntity]))");
531 _sourceQuery = sourceBuilder.ToString();
534 // Create a new ObjectQuery based on the source query text, the object context, and the specified merge option.
535 ObjectQuery<TEntity> query = new ObjectQuery<TEntity>(_sourceQuery, _context, mergeOption);
537 // Add a parameter for each entity key value found on the key.
538 AliasGenerator paramNameGen = new AliasGenerator(_entityKeyParamName); // Aliases are cached in AliasGenerator
539 IEnumerable<EdmMember> parameterMembers = _sourceQueryParamProperties
540 ?? key.GetEntitySet(ObjectContext.MetadataWorkspace).ElementType.KeyMembers;
543 foreach (EdmMember parameterMember in parameterMembers)
545 // Create a new ObjectParameter with the next parameter name and the next entity value.
546 // When _sourceQueryParamProperties are defined, it means we are handling a foreign key association. For an FK association,
547 // the current entity values are considered truth. Otherwise, we use EntityKey values for backwards
548 // compatibility with independent association behaviors in .NET 3.5.
550 if (null == _sourceQueryParamProperties)
552 // retrieve the value from the entity key (independent association lookup)
553 value = _wrappedOwner.EntityKey.EntityKeyValues.Single(ekv => ekv.Key == parameterMember.Name).Value;
557 // retrieve the value from the entity itself (FK lookup)
558 EntityReference reference = this as EntityReference;
559 if (reference != null && ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey))
565 value = GetCurrentValueFromEntity(parameterMember);
568 ObjectParameter queryParam;
571 var parameterEdmType = parameterMember.TypeUsage.EdmType;
572 Debug.Assert(Helper.IsScalarType(parameterEdmType), "Only primitive or enum type expected for parameters");
574 Type parameterClrType = Helper.IsPrimitiveType(parameterEdmType) ?
575 ((PrimitiveType)parameterEdmType).ClrEquivalentType :
576 ((ClrEnumType)ObjectContext.MetadataWorkspace.GetObjectSpaceType((EnumType)parameterEdmType)).ClrType;
578 queryParam = new ObjectParameter(paramNameGen.Next(), parameterClrType);
579 // If any lookup value is null, the query cannot match any rows.
584 queryParam = new ObjectParameter(paramNameGen.Next(), value);
587 // Map the type of the key member to C-Space and explicitly specify this mapped type
588 // as the effective type of the new ObjectParameter - this is required so that the
589 // type of the key value parameter matches the declared type of the key member when
590 // the query text is parsed.
591 queryParam.TypeUsage = Helper.GetModelTypeUsage(parameterMember);
593 // Add the new parameter to the Parameters collection of the query.
594 query.Parameters.Add(queryParam);
597 // It should not be possible to add or remove parameters from the new query, since the query text
598 // is fixed. Adding or removing parameters will likely make the query fail to execute.
599 query.Parameters.SetReadOnly(true);
601 // Return the new ObjectQuery. Note that this is intentionally a tear-off so that any changes made
602 // to its Parameters collection (or the ObjectParameters themselves) have no effect on anyone else
603 // that may retrieve this query - each access will always return a new ObjectQuery instance.
607 private object GetCurrentValueFromEntity(EdmMember member)
609 // retrieve member accessor from the object context (which already keeps track of the relevant
611 StateManagerTypeMetadata metaType = _context.ObjectStateManager.GetOrAddStateManagerTypeMetadata(member.DeclaringType);
612 StateManagerMemberMetadata metaMember = metaType.Member(metaType.GetOrdinalforCLayerMemberName(member.Name));
613 return metaMember.GetValue(_wrappedOwner.Entity);
616 private static void AppendKeyParameterRow(StringBuilder sourceBuilder, IList<EdmMember> keyMembers)
618 sourceBuilder.Append("ROW(");
619 AliasGenerator keyParamNameGen = new AliasGenerator(_entityKeyParamName); // Aliases are cached in AliasGenerator
620 int keyMemberCount = keyMembers.Count;
621 for (int idx = 0; idx < keyMemberCount; idx++)
623 string keyParamName = keyParamNameGen.Next();
624 sourceBuilder.Append("@");
625 sourceBuilder.Append(keyParamName);
626 sourceBuilder.Append(" AS ");
627 sourceBuilder.Append(keyParamName);
629 if (idx < keyMemberCount - 1)
631 sourceBuilder.Append(",");
634 sourceBuilder.Append(")");
637 private static void AppendEntitySet(StringBuilder sourceBuilder, EntitySet targetEntitySet, EntityType targetEntityType, bool ofTypeRequired)
641 sourceBuilder.Append("OfType(");
643 sourceBuilder.Append("[");
644 sourceBuilder.Append(targetEntitySet.EntityContainer.Name);
645 sourceBuilder.Append("].[");
646 sourceBuilder.Append(targetEntitySet.Name);
647 sourceBuilder.Append("]");
650 sourceBuilder.Append(", [");
651 if (targetEntityType.NamespaceName != string.Empty)
653 sourceBuilder.Append(targetEntityType.NamespaceName);
654 sourceBuilder.Append("].[");
656 sourceBuilder.Append(targetEntityType.Name);
657 sourceBuilder.Append("])");
662 /// Validates that a call to Load has the correct conditions
663 /// This helps to reduce the complexity of the Load call (SQLBU 524128)
665 /// <returns>See RelatedEnd.CreateSourceQuery method. This is returned here so we can create it and validate the state before returning it to the caller</returns>
666 internal ObjectQuery<TEntity> ValidateLoad<TEntity>(MergeOption mergeOption, string relatedEndName, out bool hasResults)
668 ObjectQuery<TEntity> sourceQuery = CreateSourceQuery<TEntity>(mergeOption, out hasResults);
669 if (null == sourceQuery)
671 throw EntityUtil.RelatedEndNotAttachedToContext(relatedEndName);
674 EntityEntry entry = ObjectContext.ObjectStateManager.FindEntityEntry(_wrappedOwner.Entity);
675 //Throw in case entity is in deleted state
676 if (entry != null && entry.State == EntityState.Deleted)
678 throw EntityUtil.InvalidEntityStateLoad(relatedEndName);
681 // MergeOption for Load must be NoTracking if and only if the source entity was NoTracking. If the source entity was
682 // retrieved with any other MergeOption, the Load MergeOption can be anything but NoTracking. I.e. The entity could
683 // have been loaded with OverwriteChanges and the Load option can be AppendOnly.
684 if (UsingNoTracking != (mergeOption == MergeOption.NoTracking))
686 throw EntityUtil.MismatchedMergeOptionOnLoad(mergeOption);
693 throw EntityUtil.LoadCalledOnAlreadyLoadedNoTrackedRelatedEnd();
698 throw EntityUtil.LoadCalledOnNonEmptyNoTrackedRelatedEnd();
710 /// Loads the related entity or entities into the local related end using the default merge option.
715 Load(DefaultMergeOption);
719 /// Loads the related entity or entities into the local related end using the supplied MergeOption.
721 public abstract void Load(MergeOption mergeOption);
726 internal void DeferredLoad()
728 if (_wrappedOwner != null && _wrappedOwner != EntityWrapperFactory.NullWrapper &&
731 _context.ContextOptions.LazyLoadingEnabled &&
732 !_context.InMaterialization &&
735 // Ensure the parent EntityState is NoTracking, Unchanged, or Modified
736 // Detached, Added, and Deleted parents cannot call Load
737 Debug.Assert(_wrappedOwner != null, "Wrapper owner should never be null");
738 if (UsingNoTracking ||
739 (_wrappedOwner.ObjectStateEntry != null &&
740 (_wrappedOwner.ObjectStateEntry.State == EntityState.Unchanged ||
741 _wrappedOwner.ObjectStateEntry.State == EntityState.Modified ||
742 (_wrappedOwner.ObjectStateEntry.State == EntityState.Added && IsForeignKey && IsDependentEndOfReferentialConstraint(false)))))
744 // Avoid infinite recursive calls
745 _context.ContextOptions.LazyLoadingEnabled = false;
752 _context.ContextOptions.LazyLoadingEnabled = true;
758 internal virtual bool CanDeferredLoad
764 /// Takes a list of related entities and merges them into the current collection.
766 /// <param name="collection">Entities to relate to the owner of this EntityCollection</param>
767 /// <param name="mergeOption">MergeOption to use when updating existing relationships</param>
768 /// <param name="setIsLoaded">Indicates whether IsLoaded should be set to true after the Load is complete.
769 /// Should be false in cases where we cannot guarantee that the set of entities is complete
770 /// and matches the server, such as Attach.</param>
771 internal void Merge<TEntity>(IEnumerable<TEntity> collection, MergeOption mergeOption, bool setIsLoaded)
773 List<IEntityWrapper> refreshedCollection = collection as List<IEntityWrapper>;
774 if (refreshedCollection == null)
776 refreshedCollection = new List<IEntityWrapper>();
777 EntitySet targetEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[TargetRoleName].EntitySet;
778 foreach (TEntity entity in collection)
780 IEntityWrapper wrapper = EntityWrapperFactory.WrapEntityUsingContext(entity, ObjectContext);
781 // When the MergeOption is NoTraking, we need to make sure the wrapper reflects the current context and
783 if (mergeOption == MergeOption.NoTracking)
785 EntityWrapperFactory.UpdateNoTrackingWrapper(wrapper, ObjectContext, targetEntitySet);
787 refreshedCollection.Add(wrapper);
790 Merge<TEntity>(refreshedCollection, mergeOption, setIsLoaded);
793 // Internal version of Merge that works on wrapped entities.
794 internal void Merge<TEntity>(List<IEntityWrapper> collection, MergeOption mergeOption, bool setIsLoaded)
796 //Dev note: do not add event firing in Merge API, if it need to be added, add it to the caller
797 EntityKey sourceKey = _wrappedOwner.EntityKey;
798 EntityUtil.CheckEntityKeyNull(sourceKey);
800 ObjectStateManager.UpdateRelationships(this.ObjectContext, mergeOption, (AssociationSet)RelationshipSet, (AssociationEndMember)FromEndProperty, sourceKey, _wrappedOwner, (AssociationEndMember)ToEndMember, collection, setIsLoaded);
804 // If the input collection contains all related entities, mark the collection as "loaded"
810 /// Attaches an entity to the related end. This method works in exactly the same way as Attach(object).
811 /// It is maintained for backward compatibility with previous versions of IRelatedEnd.
813 /// <param name="entity">The entity to attach to the related end</param>
814 /// <exception cref="ArgumentNullException">Thrown when <paramref name="entity"/> is null.</exception>
815 /// <exception cref="InvalidOperationException">Thrown when the entity cannot be related via the current relationship end.</exception>
816 void IRelatedEnd.Attach(IEntityWithRelationships entity)
818 ((IRelatedEnd)this).Attach((object)entity);
822 /// Attaches an entity to the related end. If the related end is already filled
823 /// or partially filled, this merges the existing entities with the given entity. The given
824 /// entity is not assumed to be the complete set of related entities.
826 /// Owner and all entities passed in must be in Unchanged or Modified state.
827 /// Deleted elements are allowed only when the state manager is already tracking the relationship
830 /// <param name="entity">The entity to attach to the related end</param>
831 /// <exception cref="ArgumentNullException">Thrown when <paramref name="entity"/> is null.</exception>
832 /// <exception cref="InvalidOperationException">Thrown when the entity cannot be related via the current relationship end.</exception>
833 void IRelatedEnd.Attach(object entity)
836 EntityUtil.CheckArgumentNull(entity, "entity");
837 Attach(new IEntityWrapper[] { EntityWrapperFactory.WrapEntityUsingContext(entity, ObjectContext) }, false);
840 internal void Attach(IEnumerable<IEntityWrapper> wrappedEntities, bool allowCollection)
843 ValidateOwnerForAttach();
845 // validate children and collect them in the "refreshedCollection" for this instance
847 List<IEntityWrapper> collection = new List<IEntityWrapper>();
849 foreach (IEntityWrapper entity in wrappedEntities)
851 ValidateEntityForAttach(entity, index++, allowCollection);
852 collection.Add(entity);
855 _suppressEvents = true;
858 // After Attach, the two entities should be related in the Unchanged state, so use OverwriteChanges
859 // Since no query is done in this case, the MergeOption only controls the relationships
860 Merge(collection, MergeOption.OverwriteChanges, false /*setIsLoaded*/);
861 ReferentialConstraint constraint = ((AssociationType)RelationMetadata).ReferentialConstraints.FirstOrDefault();
862 if (constraint != null)
864 ObjectStateManager stateManager = ObjectContext.ObjectStateManager;
865 EntityEntry ownerEntry = stateManager.FindEntityEntry(_wrappedOwner.Entity);
866 Debug.Assert(ownerEntry != null, "Both entities should be attached.");
867 if (IsDependentEndOfReferentialConstraint(checkIdentifying: false))
869 Debug.Assert(collection.Count == 1, "Dependant should attach to single principal");
870 if (!VerifyRIConstraintsWithRelatedEntry(constraint, ownerEntry.GetCurrentEntityValue, collection[0].ObjectStateEntry.EntityKey))
872 throw EntityUtil.InconsistentReferentialConstraintProperties();
877 foreach (IEntityWrapper wrappedTarget in collection)
879 RelatedEnd targetRelatedEnd = GetOtherEndOfRelationship(wrappedTarget);
880 if (targetRelatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
882 EntityEntry targetEntry = stateManager.FindEntityEntry(((EntityReference)targetRelatedEnd).WrappedOwner.Entity);
883 Debug.Assert(targetEntry != null, "Both entities should be attached.");
884 if (!VerifyRIConstraintsWithRelatedEntry(constraint, targetEntry.GetCurrentEntityValue, ownerEntry.EntityKey))
886 throw EntityUtil.InconsistentReferentialConstraintProperties();
895 _suppressEvents = false;
897 OnAssociationChanged(CollectionChangeAction.Refresh, null);
900 // verifies requirements for Owner in Attach()
901 internal void ValidateOwnerForAttach()
903 if (null == this.ObjectContext || UsingNoTracking)
905 throw EntityUtil.InvalidOwnerStateForAttach();
909 EntityEntry stateEntry = this.ObjectContext.ObjectStateManager.GetEntityEntry(_wrappedOwner.Entity);
910 if (stateEntry.State != EntityState.Modified &&
911 stateEntry.State != EntityState.Unchanged)
913 throw EntityUtil.InvalidOwnerStateForAttach();
917 // verifies requirements for child entity passed to Attach()
918 internal void ValidateEntityForAttach(IEntityWrapper wrappedEntity, int index, bool allowCollection)
920 if (null == wrappedEntity || null == wrappedEntity.Entity)
924 throw EntityUtil.InvalidNthElementNullForAttach(index);
928 throw EntityUtil.ArgumentNull("wrappedEntity");
932 // Having this verification here results in having the same exception no matter how the further code path is changed.
933 this.VerifyType(wrappedEntity);
935 // verify the entity exists in the current context
936 Debug.Assert(null != this.ObjectContext,
937 "ObjectContext must not be null after call to ValidateOwnerForAttach");
938 Debug.Assert(!UsingNoTracking, "We should not be here for NoTracking case.");
939 EntityEntry stateEntry = ObjectContext.ObjectStateManager.FindEntityEntry(wrappedEntity.Entity);
940 if (null == stateEntry || !Object.ReferenceEquals(stateEntry.Entity, wrappedEntity.Entity))
944 throw EntityUtil.InvalidNthElementContextForAttach(index);
948 throw EntityUtil.InvalidEntityContextForAttach();
951 Debug.Assert(stateEntry.State != EntityState.Detached,
952 "State cannot be detached if the entry was retrieved from the context");
954 // verify the state of the entity (may not be in added state, since we only support attaching relationships
955 // to existing entities)
956 if (stateEntry.State != EntityState.Unchanged &&
957 stateEntry.State != EntityState.Modified)
961 throw EntityUtil.InvalidNthElementStateForAttach(index);
965 throw EntityUtil.InvalidEntityStateForAttach();
970 internal abstract IEnumerable CreateSourceQueryInternal();
973 /// Adds an entity to the related end. This method works in exactly the same way as Add(object).
974 /// It is maintained for backward compatibility with previous versions of IRelatedEnd.
976 /// <param name="entity">
977 /// Entity instance to add to the related end
979 void IRelatedEnd.Add(IEntityWithRelationships entity)
981 ((IRelatedEnd)this).Add((object)entity);
985 /// Adds an entity to the related end. If the owner is
986 /// attached to a cache then the all the connected ends are
987 /// added to the object cache and their corresponding relationships
988 /// are also added to the ObjectStateManager. The RelatedEnd of the
989 /// relationship is also fixed.
991 /// <param name="entity">
992 /// Entity instance to add to the related end
994 void IRelatedEnd.Add(object entity)
996 EntityUtil.CheckArgumentNull(entity, "entity");
997 this.Add(EntityWrapperFactory.WrapEntityUsingContext(entity, ObjectContext));
1000 internal void Add(IEntityWrapper wrappedEntity)
1002 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1004 if (_wrappedOwner.Entity != null)
1006 Add(wrappedEntity, /*applyConstraints*/true);
1010 // The related end is in a disconnected state, so the related end is just a container
1011 // A common scenario for this is during WCF deserialization
1012 DisconnectedAdd(wrappedEntity);
1017 /// Removes an entity from the related end. This method works in exactly the same way as Remove(object).
1018 /// It is maintained for backward compatibility with previous versions of IRelatedEnd.
1020 /// <param name="entity">
1021 /// Entity instance to remove from the related end
1023 /// <returns>Returns true if the entity was successfully removed, false if the entity was not part of the RelatedEnd.</returns>
1024 bool IRelatedEnd.Remove(IEntityWithRelationships entity)
1026 return ((IRelatedEnd)this).Remove((object)entity);
1030 /// Removes an entity from the related end. If owner is
1031 /// attached to a cache, marks relationship for deletion and if
1032 /// the relationship is composition also marks the entity for deletion.
1034 /// <param name="entity">
1035 /// Entity instance to remove from the related end
1037 /// <returns>Returns true if the entity was successfully removed, false if the entity was not part of the RelatedEnd.</returns>
1038 bool IRelatedEnd.Remove(object entity)
1040 EntityUtil.CheckArgumentNull(entity, "entity");
1042 return Remove(EntityWrapperFactory.WrapEntityUsingContext(entity, ObjectContext), false);
1045 // Internal version that works on a wrapped entity and can be called from multiple
1046 // places where the public version is no longer appropriate.
1047 internal bool Remove(IEntityWrapper wrappedEntity, bool preserveForeignKey)
1049 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1051 if (_wrappedOwner.Entity != null)
1053 if (ContainsEntity(wrappedEntity))
1056 Remove(wrappedEntity, /*fixup*/true, /*deleteEntity*/false, /*deleteOwner*/false, /*applyReferentialConstraints*/true, preserveForeignKey);
1059 // The entity is not related so return false
1065 // The related end is in a disconnected state, so the related end is just a container
1066 // A common scenario for this is during WCF deserialization
1067 return DisconnectedRemove(wrappedEntity);
1072 internal abstract void DisconnectedAdd(IEntityWrapper wrappedEntity);
1073 internal abstract bool DisconnectedRemove(IEntityWrapper wrappedEntity);
1075 internal void Add(IEntityWrapper wrappedEntity, bool applyConstraints)
1077 // SQLBU: 508819 508813 508752
1078 // Detect as soon as possible if we are trying to re-add entities which are in Deleted state.
1079 // When one of the entity is in Deleted state, attempt would be made to re-add this entity
1080 // to the OSM which is not allowed.
1081 // NOTE: Current cleaning code (which uses cleanupOwnerEntity and cleanupPassedInEntity)
1082 // works only if one of the entity is not attached to the context.
1083 // PERFORMANCE: following can be performed faster if ObjectStateManager provide method to
1084 // lookup only in dictionary with Deleted entities (because here we are interested only in Deleted entities)
1085 if (_context != null && !UsingNoTracking)
1087 ValidateStateForAdd(_wrappedOwner);
1088 ValidateStateForAdd(wrappedEntity);
1091 this.Add(wrappedEntity,
1092 applyConstraints: applyConstraints,
1093 addRelationshipAsUnchanged: false,
1094 relationshipAlreadyExists: false,
1095 allowModifyingOtherEndOfRelationship: true,
1096 forceForeignKeyChanges: true);
1100 internal void CheckRelationEntitySet(EntitySet set)
1102 Debug.Assert(set != null, "null EntitySet");
1103 Debug.Assert(_relationshipSet != null,
1104 "Should only be checking the RelationshipSet on an attached entity and it should always be non-null in that case");
1106 if ((((AssociationSet)_relationshipSet).AssociationSetEnds[_navigation.To] != null) &&
1107 (((AssociationSet)_relationshipSet).AssociationSetEnds[_navigation.To].EntitySet != set))
1109 throw EntityUtil.EntitySetIsNotValidForRelationship(set.EntityContainer.Name, set.Name, _navigation.To, _relationshipSet.EntityContainer.Name, _relationshipSet.Name);
1113 internal void ValidateStateForAdd(IEntityWrapper wrappedEntity)
1115 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1116 EntityEntry entry = this.ObjectContext.ObjectStateManager.FindEntityEntry(wrappedEntity.Entity);
1117 if (entry != null && entry.State == EntityState.Deleted)
1119 throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
1123 internal void Add(IEntityWrapper wrappedTarget,
1124 bool applyConstraints,
1125 bool addRelationshipAsUnchanged,
1126 bool relationshipAlreadyExists,
1127 bool allowModifyingOtherEndOfRelationship, // needed by ChangeRelationshipState - check multiplicity constraints instead of silently updating other end of relationship
1128 bool forceForeignKeyChanges)
1130 Debug.Assert(wrappedTarget != null, "IEntityWrapper instance is null.");
1132 if (!this.VerifyEntityForAdd(wrappedTarget, relationshipAlreadyExists))
1134 // Allow the same item to be "added" to a collection as a no-op operation
1138 EntityKey key = wrappedTarget.EntityKey;
1139 if ((object)key != null && ObjectContext != null)
1141 CheckRelationEntitySet(key.GetEntitySet(ObjectContext.MetadataWorkspace));
1144 RelatedEnd targetRelatedEnd = GetOtherEndOfRelationship(wrappedTarget);
1146 if (Object.ReferenceEquals(this.ObjectContext, targetRelatedEnd.ObjectContext) && this.ObjectContext != null)
1148 // Both entities are associated with the same non-null context
1150 // Make sure that they are either both tracked or both not tracked, or both don't have contexts
1151 if (UsingNoTracking != targetRelatedEnd.UsingNoTracking)
1153 throw EntityUtil.CannotCreateRelationshipBetweenTrackedAndNoTrackedEntities(UsingNoTracking ?
1154 this._navigation.From : this._navigation.To);
1157 else if (this.ObjectContext != null && targetRelatedEnd.ObjectContext != null)
1159 // Both entities have a context
1160 if (UsingNoTracking && targetRelatedEnd.UsingNoTracking)
1162 // Both entities are NoTracking, but have different contexts
1163 // Attach the owner's context to the target's RelationshipManager
1164 // O-C mappings are 1:1, so this operation is allowed
1165 wrappedTarget.ResetContext(this.ObjectContext, GetTargetEntitySetFromRelationshipSet(), MergeOption.NoTracking);
1169 // Both entities are already tracked by different non-null contexts
1170 throw EntityUtil.CannotCreateRelationshipEntitiesInDifferentContexts();
1173 else if ((_context == null || UsingNoTracking) && (targetRelatedEnd.ObjectContext != null && !targetRelatedEnd.UsingNoTracking))
1175 // Only the target has a context, so validate it is in a suitable state
1176 targetRelatedEnd.ValidateStateForAdd(targetRelatedEnd.WrappedOwner);
1179 targetRelatedEnd.VerifyEntityForAdd(_wrappedOwner, relationshipAlreadyExists);
1184 // Perform multiplicity constraints verification for the target related end before current related end is modified.
1185 // The "allowModifyingOtherEndOfRelationship" is used by ObjectStateManager.ChangeRelationshipState.
1186 targetRelatedEnd.VerifyMultiplicityConstraintsForAdd(!allowModifyingOtherEndOfRelationship);
1188 // Add the target entity to the source entity's collection or reference
1189 if (this.CheckIfNavigationPropertyContainsEntity(wrappedTarget))
1191 this.AddToLocalCache(wrappedTarget, applyConstraints);
1195 this.AddToCache(wrappedTarget, applyConstraints);
1198 // Fix up the target end of the relationship by adding the source entity to the target entity's collection or reference
1199 // devnote: applyConstraints should be always false to enable scenarios like this:
1200 // orderLine.Order = order1;
1201 // order2.OrderLines.Add(orderLine); // orderLine.Order is changed to order2
1202 if (targetRelatedEnd.CheckIfNavigationPropertyContainsEntity(WrappedOwner))
1204 // Example: IPOCO order, POCO customer with a bidirectional relationship
1205 // customer.Orders.Add(order);
1206 // order.Customer = customer <-- the Orders collection already contains "order" on fixup and this would add a duplicate
1207 targetRelatedEnd.AddToLocalCache(_wrappedOwner, /*applyConstraints*/ false);
1211 targetRelatedEnd.AddToCache(_wrappedOwner, /*applyConstraints*/ false);
1213 // delay event firing for targetRelatedEnd. once we fire the event, we should be at operation completed state
1215 // Ensure that both entities end up in the same context:
1216 // (1) If neither entity is attached to a context, we don't need to do anything else.
1217 // (2) If they are both in the same one, we need to make sure neither one was created with MergeOption.NoTracking,
1218 // and if not, add a relationship entry if it doesn't already exist.
1219 // (3) If both entities are already in different contexts, fail.
1220 // (4) Otherwise, only one entity is attached, and that is the context we will use.
1221 // For the entity that is not attached, attach it to that context.
1223 RelatedEnd attachedRelatedEnd = null; // the end of the relationship that is already attached to a context, if there is one.
1224 IEntityWrapper entityToAdd = null; // the entity to be added to attachedRelatedEnd
1226 if (Object.ReferenceEquals(this.ObjectContext, targetRelatedEnd.ObjectContext) && this.ObjectContext != null)
1228 // Both entities are associated with the same non-null context
1230 // Make sure that a relationship entry exists between these two entities. It is possible that the entities could
1231 // have been added to the context independently of each other, so the relationship may not exist yet.
1232 if (!this.IsForeignKey && !relationshipAlreadyExists && !UsingNoTracking)
1234 // If this Add is triggered by setting the principle end of an unchanged/modified dependent end, then the relationship should be Unchanged
1235 if (!this.ObjectContext.ObjectStateManager.TransactionManager.IsLocalPublicAPI &&
1236 this.WrappedOwner.EntityKey != null &&
1237 !this.WrappedOwner.EntityKey.IsTemporary && IsDependentEndOfReferentialConstraint(false))
1239 addRelationshipAsUnchanged = true;
1242 AddRelationshipToObjectStateManager(wrappedTarget, addRelationshipAsUnchanged, /*doAttach*/false);
1245 // The condition (IsAddTracking || IsAttachTracking || IsDetectChanges) excludes the case
1246 // when the method is called from materialization when we don't want to verify the navigation property.
1247 if (wrappedTarget.RequiresRelationshipChangeTracking &&
1248 (this.ObjectContext.ObjectStateManager.TransactionManager.IsAddTracking ||
1249 this.ObjectContext.ObjectStateManager.TransactionManager.IsAttachTracking ||
1250 this.ObjectContext.ObjectStateManager.TransactionManager.IsDetectChanges))
1252 this.AddToNavigationProperty(wrappedTarget);
1253 targetRelatedEnd.AddToNavigationProperty(this._wrappedOwner);
1256 else if (this.ObjectContext != null || targetRelatedEnd.ObjectContext != null)
1258 // Only one entity has a context, so figure out which one it is, and determine which entity we will be adding to it
1259 if (this.ObjectContext == null)
1261 attachedRelatedEnd = targetRelatedEnd;
1262 entityToAdd = _wrappedOwner;
1266 attachedRelatedEnd = this;
1267 entityToAdd = wrappedTarget;
1270 if (!attachedRelatedEnd.UsingNoTracking)
1272 TransactionManager transManager = attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.TransactionManager;
1273 transManager.BeginAddTracking();
1277 bool doCleanup = true;
1281 if (attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.TransactionManager.TrackProcessedEntities)
1283 // The Entity could have been already wrapped by DetectChanges
1284 if (!attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.TransactionManager.WrappedEntities.ContainsKey(entityToAdd.Entity))
1286 attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.TransactionManager.WrappedEntities.Add(entityToAdd.Entity, entityToAdd);
1288 attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.TransactionManager.ProcessedEntities.Add(attachedRelatedEnd.WrappedOwner);
1291 attachedRelatedEnd.AddGraphToObjectStateManager(entityToAdd, relationshipAlreadyExists,
1292 addRelationshipAsUnchanged, /*doAttach*/ false);
1294 // The code below is almost duplicated as the code few lines above
1295 // because this one can't be moved outside of try{}catch{}.
1296 if (entityToAdd.RequiresRelationshipChangeTracking && TargetAccessor.HasProperty)
1298 Debug.Assert(this.CheckIfNavigationPropertyContainsEntity(wrappedTarget), "owner's navigation property doesn't contain the target entity as expected");
1299 targetRelatedEnd.AddToNavigationProperty(this._wrappedOwner);
1308 Debug.Assert(entityToAdd != null, "entityToAdd should be set if attachedRelatedEnd is set");
1310 attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.DegradePromotedRelationships();
1312 // Remove the source entity from the target related end
1313 attachedRelatedEnd.FixupOtherEndOfRelationshipForRemove(entityToAdd, /*preserveForeignKey*/ false);
1315 // Remove the target entity from the source related end
1316 attachedRelatedEnd.RemoveFromCache(entityToAdd, /*resetIsLoaded*/ false, /*preserveForeignKey*/ false);
1318 // Remove the graph that we just tried to add to the context
1319 entityToAdd.RelationshipManager.NodeVisited = true;
1320 RelationshipManager.RemoveRelatedEntitiesFromObjectStateManager(entityToAdd);
1321 RemoveEntityFromObjectStateManager(entityToAdd);
1327 attachedRelatedEnd.WrappedOwner.Context.ObjectStateManager.TransactionManager.EndAddTracking();
1332 // FK: update foreign key values on the dependent end.
1333 if (this.ObjectContext != null &&
1334 this.IsForeignKey &&
1335 !this.ObjectContext.ObjectStateManager.TransactionManager.IsGraphUpdate)
1337 // Note that we use "forceChange" below so that the FK properties will be set as modified
1338 // even if they don't actually change.
1339 if (this.IsDependentEndOfReferentialConstraint(false))
1341 Debug.Assert(this is EntityReference, "Dependent end cannot be a collection.");
1342 ((EntityReference)this).UpdateForeignKeyValues(_wrappedOwner, wrappedTarget, changedFKs: null, forceChange: forceForeignKeyChanges);
1344 else if (targetRelatedEnd.IsDependentEndOfReferentialConstraint(false))
1346 Debug.Assert(targetRelatedEnd is EntityReference, "Dependent end cannot be a collection.");
1347 ((EntityReference)targetRelatedEnd).UpdateForeignKeyValues(wrappedTarget, _wrappedOwner, changedFKs: null, forceChange: forceForeignKeyChanges);
1351 // else neither entity is associated with a context, so there is no state manager to update
1352 // fire the Association changed event, first on targetRelatedEnd then on this EC
1353 targetRelatedEnd.OnAssociationChanged(CollectionChangeAction.Add, _wrappedOwner.Entity);
1354 OnAssociationChanged(CollectionChangeAction.Add, wrappedTarget.Entity);
1357 private void AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, bool relationshipAlreadyExists,
1358 bool addRelationshipAsUnchanged, bool doAttach)
1360 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1361 Debug.Assert(!UsingNoTracking, "Should not be attempting to add graphs to the state manager with NoTracking related ends");
1363 AddEntityToObjectStateManager(wrappedEntity, doAttach);
1364 if (!relationshipAlreadyExists &&
1365 this.ObjectContext != null && wrappedEntity.Context != null)
1367 if (!this.IsForeignKey)
1369 AddRelationshipToObjectStateManager(wrappedEntity, addRelationshipAsUnchanged, doAttach);
1372 if (wrappedEntity.RequiresRelationshipChangeTracking || WrappedOwner.RequiresRelationshipChangeTracking)
1374 this.UpdateSnapshotOfRelationships(wrappedEntity);
1377 EntityEntry entry = _context.ObjectStateManager.GetEntityEntry(wrappedEntity.Entity);
1378 wrappedEntity.RelationshipManager.CheckReferentialConstraintProperties(entry);
1382 WalkObjectGraphToIncludeAllRelatedEntities(wrappedEntity, addRelationshipAsUnchanged, doAttach);
1385 private void UpdateSnapshotOfRelationships(IEntityWrapper wrappedEntity)
1387 RelatedEnd otherRelatedEnd = this.GetOtherEndOfRelationship(wrappedEntity);
1388 if (!otherRelatedEnd.ContainsEntity(this.WrappedOwner))
1390 // Since we now align changes, we can allow the Add to remove the old value
1391 // Reference/FK violations are detected elsewhere
1392 otherRelatedEnd.AddToLocalCache(this.WrappedOwner, applyConstraints: false);
1396 internal void Remove(IEntityWrapper wrappedEntity, bool doFixup, bool deleteEntity, bool deleteOwner, bool applyReferentialConstraints, bool preserveForeignKey)
1398 if (wrappedEntity.RequiresRelationshipChangeTracking && // Is it POCO?
1399 doFixup && // Remove() is called for both ends of relationship, once with doFixup==true, once with doFixup==false. Verify only one time.
1400 this.TargetAccessor.HasProperty) // Is there anything to verify?
1402 bool contains = this.CheckIfNavigationPropertyContainsEntity(wrappedEntity);
1406 RelatedEnd relatedEnd = GetOtherEndOfRelationship(wrappedEntity);
1407 relatedEnd.RemoveFromNavigationProperty(this.WrappedOwner);
1411 if (!this.ContainsEntity(wrappedEntity))
1416 // There can be a case when symmetrical Remove() shall be performed because of Referential Constraints
1418 // Relationship Client -> Order with Referential Constraint on in.
1419 // When user calls (pseudo code) Order.Remove(Client), we perform Client.Remove(Order),
1420 // because removing relationship between Client and Order should cause cascade delete on the Order side.
1421 if (null != _context && doFixup &&
1422 applyReferentialConstraints &&
1423 IsDependentEndOfReferentialConstraint(false)) // don't check the nullability of the "from" properties
1425 // Remove _wrappedOwner from the related end with applying Referential Constraints
1426 RelatedEnd relatedEnd = GetOtherEndOfRelationship(wrappedEntity);
1427 relatedEnd.Remove(_wrappedOwner, doFixup, deleteEntity, deleteOwner, applyReferentialConstraints, preserveForeignKey);
1433 //The following call will verify that the given entity is part of the collection or ref.
1434 bool fireEvent = RemoveFromCache(wrappedEntity, false, preserveForeignKey);
1436 if (!UsingNoTracking &&
1437 this.ObjectContext != null &&
1440 MarkRelationshipAsDeletedInObjectStateManager(wrappedEntity, _wrappedOwner, _relationshipSet, _navigation);
1445 FixupOtherEndOfRelationshipForRemove(wrappedEntity, preserveForeignKey);
1447 // For the "LocalPublicAPI" just remove the entity from the related end, don't trigger cascade delete
1448 if (_context == null || !_context.ObjectStateManager.TransactionManager.IsLocalPublicAPI)
1450 //The related end "entity" cannot live without this side "owner". It should be deleted. Cascade this
1451 // effect to related entities of the "related" entity
1452 // We skip this delete/detach if the entity is being reparented (TransactionManager.EntityBeingReparented)
1453 // or if the reference is being nulled as part of fixup in a POCO proxy while setting the FK (InFKSetter).
1454 if (null != _context && (deleteEntity ||
1455 (deleteOwner && CheckCascadeDeleteFlag(_fromEndProperty)) ||
1456 (applyReferentialConstraints && IsPrincipalEndOfReferentialConstraint())) &&
1457 !ReferenceEquals(wrappedEntity.Entity, _context.ObjectStateManager.TransactionManager.EntityBeingReparented) &&
1458 !ReferenceEquals(_context.ObjectStateManager.EntityInvokingFKSetter, wrappedEntity.Entity))
1460 //Once related entity is deleted, all relationships involving related entity would be updated
1462 // RemoveEntityFromRelatedEnds check for graph circularities to make sure
1463 // it does not get into infinite loop
1464 EnsureRelationshipNavigationAccessorsInitialized();
1465 RemoveEntityFromRelatedEnds(wrappedEntity, _wrappedOwner, _navigation.Reverse);
1466 MarkEntityAsDeletedInObjectStateManager(wrappedEntity);
1473 OnAssociationChanged(CollectionChangeAction.Remove, wrappedEntity.Entity);
1479 /// Returns true if this Related end represents the dependent of a Referential Constraint
1481 /// <param name="checkIdentifying">If true then the method will only return true if the Referential Constraint is identifying</param>
1482 internal bool IsDependentEndOfReferentialConstraint(bool checkIdentifying)
1484 if (null != _relationMetadata)
1486 // NOTE Referential constraints collection will usually contains 0 or 1 element,
1487 // so performance shouldn't be an issue here
1488 foreach (ReferentialConstraint constraint in ((AssociationType)this.RelationMetadata).ReferentialConstraints)
1490 if (constraint.ToRole == this.FromEndProperty)
1492 if (checkIdentifying)
1494 EntityType entityType = constraint.ToRole.GetEntityType();
1495 bool allPropertiesAreKeyProperties = RelatedEnd.CheckIfAllPropertiesAreKeyProperties(entityType.KeyMemberNames, constraint.ToProperties);
1497 return allPropertiesAreKeyProperties;
1502 // Client<C_ID> --- Order<O_ID, Client_ID>
1503 // RI Constraint: Principal/From <Client.C_ID>, Dependent/To <Order.Client_ID>
1504 // When current RelatedEnd is a CollectionOrReference in Order's relationships,
1505 // constarint.ToRole == this._fromEndProperty == Order
1515 /// Check if current RelatedEnd is a Principal end of some Referential Constraint and if some of the "from" properties is not-nullable
1517 internal bool IsPrincipalEndOfReferentialConstraint()
1519 if (null != _relationMetadata)
1521 // NOTE Referential constraints collection will usually contains 0 or 1 element,
1522 // so performance shouldn't be an issue here
1523 foreach (ReferentialConstraint constraint in ((AssociationType)_relationMetadata).ReferentialConstraints)
1525 if (constraint.FromRole == this._fromEndProperty)
1527 EntityType entityType = constraint.ToRole.GetEntityType();
1528 bool allPropertiesAreKeyProperties = RelatedEnd.CheckIfAllPropertiesAreKeyProperties(entityType.KeyMemberNames, constraint.ToProperties);
1531 // Client<C_ID> --- Order<O_ID, Client_ID>
1532 // RI Constraint: Principal/From <Client.C_ID>, Dependent/To <Order.Client_ID>
1533 // When current RelatedEnd is a CollectionOrReference in Client's relationships,
1534 // constarint.FromRole == this._fromEndProperty == Client
1535 return allPropertiesAreKeyProperties;
1542 static internal bool CheckIfAllPropertiesAreKeyProperties(string[] keyMemberNames, ReadOnlyMetadataCollection<EdmProperty> toProperties)
1544 // Check if some of the "to" properties is not a key property
1545 foreach (EdmProperty property in toProperties)
1548 foreach (string keyPropertyName in keyMemberNames)
1550 if (keyPropertyName == property.Name)
1564 //Add given entity and its relationship to ObjectStateManager. Walk graph to recursively
1565 // add all entities in the graph.
1566 // If doAttach==TRUE, the entities are attached directly as Unchanged without calling AcceptChanges()
1567 internal void IncludeEntity(IEntityWrapper wrappedEntity, bool addRelationshipAsUnchanged, bool doAttach)
1569 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1570 Debug.Assert(!UsingNoTracking, "Should not be trying to include entities in the state manager for NoTracking related ends");
1572 //check to see if entity is already added to the cache
1573 //search by object reference so that we will not find any entries with the same key but a different object instance
1574 // NOTE: if (cacheEntry.Entity == entity) then this part of the graph is skipped
1575 EntityEntry cacheEntry = _context.ObjectStateManager.FindEntityEntry(wrappedEntity.Entity);
1576 Debug.Assert(cacheEntry == null || cacheEntry.Entity == wrappedEntity.Entity,
1577 "Expected to have looked up this state entry by reference, how did we get a different entity?");
1579 if (null != cacheEntry && cacheEntry.State == EntityState.Deleted)
1581 throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
1584 if (wrappedEntity.RequiresRelationshipChangeTracking || WrappedOwner.RequiresRelationshipChangeTracking)
1586 // Verify relationship fixup before including rest of the graph.
1587 RelatedEnd otherRelatedEnd = GetOtherEndOfRelationship(wrappedEntity);
1589 // Validate the type is compatible before trying to get/set properties on it.
1590 // The following will throw if the type is not mapped.
1591 _context.GetTypeUsage(otherRelatedEnd.WrappedOwner.IdentityType);
1593 // If the other end is a reference that is non-null, then don't overwrite it.
1594 // If the reference is non-null and doesn't match what we think it should be, then throw.
1595 EntityReference otherEndAsRef = otherRelatedEnd as EntityReference;
1596 if (otherEndAsRef != null)
1598 if (otherEndAsRef.NavigationPropertyIsNullOrMissing())
1600 otherRelatedEnd.AddToNavigationProperty(this._wrappedOwner);
1601 // If the other end is a dependent that is already tracked, then we need to make sure
1602 // its FK props are marked as modified even though we are not fixing them up.
1603 Debug.Assert(ObjectContext != null, "Expected attached context at this point.");
1604 if (cacheEntry != null &&
1605 ObjectContext.ObjectStateManager.TransactionManager.IsAddTracking &&
1607 otherRelatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
1609 otherRelatedEnd.MarkForeignKeyPropertiesModified();
1612 else if (!otherEndAsRef.CheckIfNavigationPropertyContainsEntity(this._wrappedOwner))
1614 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_ConflictingChangesOfRelationshipDetected(
1615 otherEndAsRef.RelationshipNavigation.To,
1616 otherEndAsRef.RelationshipNavigation.RelationshipName));
1621 // For collections, always add
1622 otherRelatedEnd.AddToNavigationProperty(this._wrappedOwner);
1626 if (null == cacheEntry)
1628 // NOTE (Attach): if (null == entity.Key) then check must be performed whether entity really
1629 // doesn't exist in the context (by creating fake Key and calling FindObjectStateEntry(Key) )
1630 // This is done in the ObjectContext::AttachSingleObject().
1632 AddGraphToObjectStateManager(wrappedEntity, /*relationshipAlreadyExists*/ false,
1633 addRelationshipAsUnchanged, doAttach);
1636 // There is a possibility that related entity is added to cache but relationship is not added.
1637 // Example: Suppose A and B are related. When walking the graph it is possible that
1638 // node B was visited through some relationship other than A-B.
1639 else if (null == FindRelationshipEntryInObjectStateManager(wrappedEntity))
1641 // If we have a reference with a detached key, make sure the key matches the relationship we are about to add
1642 EntityReference entityRef = this as EntityReference;
1643 if (entityRef != null && entityRef.DetachedEntityKey != null)
1645 EntityKey targetKey = wrappedEntity.EntityKey;
1646 if (entityRef.DetachedEntityKey != targetKey)
1648 // Check for the case where a NoTracking (with detached entity key) is being Added and throw the same
1649 // exception we do elsewhere for this case.
1650 // We might consider changing this behavior in the future to just put the entity in the Added state,
1651 // but for consistency for now we throw the same exception as elsewhere.
1652 if (targetKey.IsTemporary)
1654 throw EntityUtil.CannotCreateRelationshipBetweenTrackedAndNoTrackedEntities(_navigation.To);
1657 throw EntityUtil.EntityKeyValueMismatch();
1659 // else -- null just means the key isn't set, so the target entity key doesn't also have to be null
1662 if (this.ObjectContext != null && wrappedEntity.Context != null)
1664 if (!this.IsForeignKey)
1666 if (cacheEntry.State == EntityState.Added)
1668 // In POCO, when the graph is partially attached and user is calling Attach on the detached entity
1669 // and the entity in the context is in the Added state, the relationship has to created also in Added state.
1670 AddRelationshipToObjectStateManager(wrappedEntity, addRelationshipAsUnchanged, false);
1674 AddRelationshipToObjectStateManager(wrappedEntity, addRelationshipAsUnchanged, doAttach);
1678 if (wrappedEntity.RequiresRelationshipChangeTracking || WrappedOwner.RequiresRelationshipChangeTracking)
1680 this.UpdateSnapshotOfRelationships(wrappedEntity);
1681 if (doAttach && cacheEntry.State != EntityState.Added)
1683 EntityEntry entry = this.ObjectContext.ObjectStateManager.GetEntityEntry(wrappedEntity.Entity);
1684 wrappedEntity.RelationshipManager.CheckReferentialConstraintProperties(entry);
1689 // else relationship is already there, nothing more to do
1692 internal void MarkForeignKeyPropertiesModified()
1694 Debug.Assert(IsForeignKey, "cannot update foreign key values if the relationship is not a FK");
1695 ReferentialConstraint constraint = ((AssociationType)RelationMetadata).ReferentialConstraints[0];
1696 Debug.Assert(constraint != null, "null constraint");
1698 EntityEntry dependentEntry = WrappedOwner.ObjectStateEntry;
1699 Debug.Assert(dependentEntry != null, "Expected tracked entity.");
1701 // No need to try to mark properties as modified for added/deleted/detached entities.
1702 // Even if the entity is modified, the FK props may not be modified.
1703 if (dependentEntry.State == EntityState.Unchanged || dependentEntry.State == EntityState.Modified)
1705 foreach (var dependentProp in constraint.ToProperties)
1707 dependentEntry.SetModifiedProperty(dependentProp.Name);
1712 internal abstract bool CheckIfNavigationPropertyContainsEntity(IEntityWrapper wrapper);
1714 internal abstract void VerifyNavigationPropertyForAdd(IEntityWrapper wrapper);
1716 internal void AddToNavigationProperty(IEntityWrapper wrapper)
1718 Debug.Assert(this.RelationshipNavigation != null, "null RelationshipNavigation");
1720 if (this.TargetAccessor.HasProperty && !this.CheckIfNavigationPropertyContainsEntity(wrapper))
1722 Debug.Assert(wrapper.Context != null, "Expected context to be available.");
1723 // We keep track of the nav properties we have set during Add/Attach so that they
1724 // can be undone during rollback.
1725 TransactionManager tm = wrapper.Context.ObjectStateManager.TransactionManager;
1726 if (tm.IsAddTracking || tm.IsAttachTracking)
1728 wrapper.Context.ObjectStateManager.TrackPromotedRelationship(this, wrapper);
1730 this.AddToObjectCache(wrapper);
1734 internal void RemoveFromNavigationProperty(IEntityWrapper wrapper)
1736 Debug.Assert(this.RelationshipNavigation != null, "null RelationshipNavigation");
1738 if (this.TargetAccessor.HasProperty && this.CheckIfNavigationPropertyContainsEntity(wrapper))
1740 this.RemoveFromObjectCache(wrapper);
1744 // Remove given entity and its relationship from ObjectStateManager.
1745 // Traversegraph to recursively remove all entities in the graph.
1746 internal void ExcludeEntity(IEntityWrapper wrappedEntity)
1748 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1749 Debug.Assert(!UsingNoTracking, "Should not try to exclude entities from the state manager for NoTracking related ends.");
1751 if (!_context.ObjectStateManager.TransactionManager.TrackProcessedEntities ||
1752 !(_context.ObjectStateManager.TransactionManager.IsAttachTracking || _context.ObjectStateManager.TransactionManager.IsAddTracking) ||
1753 _context.ObjectStateManager.TransactionManager.ProcessedEntities.Contains(wrappedEntity))
1755 //check to see if entity is already removed from the cache
1756 EntityEntry cacheEntry = _context.ObjectStateManager.FindEntityEntry(wrappedEntity.Entity);
1758 if (null != cacheEntry && cacheEntry.State != EntityState.Deleted && !wrappedEntity.RelationshipManager.NodeVisited)
1760 wrappedEntity.RelationshipManager.NodeVisited = true;
1762 RelationshipManager.RemoveRelatedEntitiesFromObjectStateManager(wrappedEntity);
1763 if (!this.IsForeignKey)
1765 RemoveRelationshipFromObjectStateManager(wrappedEntity, _wrappedOwner, _relationshipSet, _navigation);
1767 RemoveEntityFromObjectStateManager(wrappedEntity);
1769 // There is a possibility that related entity is removed from cache but relationship is not removed.
1770 // Example: Suppose A and B are related. When walking the graph it is possible that
1771 // node B was visited through some relationship other than A-B.
1772 else if (!this.IsForeignKey && null != FindRelationshipEntryInObjectStateManager(wrappedEntity))
1774 RemoveRelationshipFromObjectStateManager(wrappedEntity, _wrappedOwner, _relationshipSet, _navigation);
1779 internal RelationshipEntry FindRelationshipEntryInObjectStateManager(IEntityWrapper wrappedEntity)
1781 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1782 Debug.Assert(!UsingNoTracking, "Should not look for RelationshipEntry in ObjectStateManager for NoTracking cases.");
1783 EntityKey entityKey = wrappedEntity.EntityKey;
1784 EntityKey ownerKey = _wrappedOwner.EntityKey;
1785 return this._context.ObjectStateManager.FindRelationship(_relationshipSet,
1786 new KeyValuePair<string, EntityKey>(_navigation.From, ownerKey),
1787 new KeyValuePair<string, EntityKey>(_navigation.To, entityKey));
1790 internal void Clear(IEntityWrapper wrappedEntity, RelationshipNavigation navigation, bool doCascadeDelete)
1792 ClearCollectionOrRef(wrappedEntity, navigation, doCascadeDelete);
1795 // Check if related entities contain proper property values
1796 // (entities with temporary keys are skipped)
1797 internal bool CheckReferentialConstraintProperties(EntityEntry ownerEntry)
1799 // if the related end contains a real entity or is a reference with a detached entitykey, we need to check for RI constraints
1800 if (!this.IsEmpty() ||
1801 ((ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne ||
1802 ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One) &&
1803 ((EntityReference)this).DetachedEntityKey != null))
1805 foreach (ReferentialConstraint constraint in ((AssociationType)this.RelationMetadata).ReferentialConstraints)
1807 // Check properties in principals
1808 if (constraint.ToRole == FromEndProperty)
1810 Debug.Assert(this is EntityReference, "Expected reference to principal");
1811 EntityKey principalKey;
1814 // Generally for foreign keys we want to use the EntityKey to do RI constraint validation
1815 // However, if we are doing an Add/Attach, we should use the DetachedEntityKey because this is the value
1816 // set by the user while the entity was detached, and should be used until the entity is fully added/attached
1817 if (this.IsForeignKey &&
1818 !(this.ObjectContext.ObjectStateManager.TransactionManager.IsAddTracking ||
1819 this.ObjectContext.ObjectStateManager.TransactionManager.IsAttachTracking))
1821 principalKey = ((EntityReference)this).EntityKey;
1825 principalKey = ((EntityReference)this).DetachedEntityKey;
1830 IEntityWrapper wrappedRelatedEntity = ((EntityReference)this).ReferenceValue;
1831 // For Added entities, it doesn't matter what the key value is since it can't be trusted anyway.
1832 if (wrappedRelatedEntity.ObjectStateEntry != null && wrappedRelatedEntity.ObjectStateEntry.State == EntityState.Added)
1836 principalKey = ExtractPrincipalKey(wrappedRelatedEntity);
1838 if (!VerifyRIConstraintsWithRelatedEntry(constraint, ownerEntry.GetCurrentEntityValue, principalKey))
1843 else if (constraint.FromRole == FromEndProperty)
1847 // related end is empty, so we must have a reference with a detached key
1848 EntityKey detachedKey = ((EntityReference)this).DetachedEntityKey;
1850 // If the constraint is not PK<->PK then we can't validate it here.
1851 // This debug code checks that we don't try to validate it.
1852 List<string> keyNames = new List<string>(from v in detachedKey.EntityKeyValues select v.Key);
1853 foreach (EdmProperty prop in constraint.ToProperties)
1855 Debug.Assert(keyNames.Contains(prop.Name), "Attempt to validate constraint where some FK values are not in the dependent PK");
1858 // don't need to validate the principal/detached key here because that has already been done during AttachContext
1859 if (!VerifyRIConstraintsWithRelatedEntry(constraint, detachedKey.FindValueByName, ownerEntry.EntityKey))
1866 foreach (IEntityWrapper wrappedRelatedEntity in GetWrappedEntities())
1868 EntityEntry dependent = wrappedRelatedEntity.ObjectStateEntry;
1869 if (dependent != null &&
1870 dependent.State != EntityState.Added &&
1871 dependent.State != EntityState.Deleted &&
1872 dependent.State != EntityState.Detached)
1874 if (!VerifyRIConstraintsWithRelatedEntry(constraint, dependent.GetCurrentEntityValue, ownerEntry.EntityKey))
1882 // else keep processing the other constraints
1888 private EntityKey ExtractPrincipalKey(IEntityWrapper wrappedRelatedEntity)
1890 EntitySet principalEntitySet = GetTargetEntitySetFromRelationshipSet();
1891 // get or create a key to use to compare the values -- the target entity might not have been attached
1892 // yet so it may not have a key, but we can create one here to use for checking the values
1893 EntityKey principalKey = wrappedRelatedEntity.EntityKey;
1894 if (null != (object)principalKey && !principalKey.IsTemporary)
1896 // Validate the key here because we need to get values from it for verification
1897 // and that will fail if the key is malformed.
1898 // Verify only if the key already exists.
1899 EntityUtil.ValidateEntitySetInKey(principalKey, principalEntitySet);
1900 principalKey.ValidateEntityKey(ObjectContext.MetadataWorkspace, principalEntitySet);
1904 principalKey = _context.ObjectStateManager.CreateEntityKey(principalEntitySet, wrappedRelatedEntity.Entity);
1906 return principalKey;
1909 internal static bool VerifyRIConstraintsWithRelatedEntry(ReferentialConstraint constraint, Func<string, object> getDependentPropertyValue, EntityKey principalKey)
1911 Debug.Assert(constraint.FromProperties.Count == constraint.ToProperties.Count, "RIC: Referential constraints From/To properties list have different size");
1913 // NOTE order of properties in collections (From/ToProperties) is important.
1914 for (int i = 0; i < constraint.FromProperties.Count; ++i)
1916 string fromPropertyName = constraint.FromProperties[i].Name;
1917 string toPropertyName = constraint.ToProperties[i].Name;
1919 object currentValue = principalKey.FindValueByName(fromPropertyName);
1920 object expectedValue = getDependentPropertyValue(toPropertyName);
1922 Debug.Assert(currentValue != null, "currentValue is part of Key on an attached entity, it must not be null");
1924 if (!ByValueEqualityComparer.Default.Equals(currentValue, expectedValue))
1926 // RI Constraint violated
1934 public IEnumerator GetEnumerator()
1936 // Due to the way the CLR handles IEnumerator return types, the check for a null owner for EntityReferences
1937 // must be made here because GetInternalEnumerator is delay-executed and so will not throw until the
1938 // enumerator is advanced
1939 if (this is EntityReference)
1944 return GetInternalEnumerable().GetEnumerator();
1947 internal void RemoveAll()
1949 //copy into list because changing collection member is not allowed during enumeration.
1950 // If possible avoid copying into list.
1951 List<IEntityWrapper> deletedEntities = null;
1953 bool fireEvent = false;
1956 _suppressEvents = true;
1957 foreach (IEntityWrapper wrappedEntity in GetWrappedEntities())
1959 if (null == deletedEntities)
1961 deletedEntities = new List<IEntityWrapper>();
1963 deletedEntities.Add(wrappedEntity);
1967 if (fireEvent = (null != deletedEntities) && (deletedEntities.Count > 0))
1969 foreach (IEntityWrapper wrappedEntity in deletedEntities)
1971 Remove(wrappedEntity, /*fixup*/true, /*deleteEntity*/false, /*deleteOwner*/true, /*applyReferentialConstraints*/true, /*preserveForeignKey*/false);
1977 _suppressEvents = false;
1981 OnAssociationChanged(CollectionChangeAction.Refresh, null);
1985 internal void DetachAll(EntityState ownerEntityState)
1987 //copy into list because changing collection member is not allowed during enumeration.
1988 // If possible avoid copying into list.
1989 List<IEntityWrapper> deletedEntities = new List<IEntityWrapper>();
1991 foreach (IEntityWrapper wrappedEntity in GetWrappedEntities())
1993 deletedEntities.Add(wrappedEntity);
1996 bool detachRelationship =
1997 ownerEntityState == EntityState.Added ||
1998 _fromEndProperty.RelationshipMultiplicity == RelationshipMultiplicity.Many;
2000 // every-fix up will fire with Remove action
2001 // every forward operation (removing from this relatedEnd) will fire with Refresh
2002 // do not merge the loops, handle the related ends separately (when the event is being fired,
2003 // we should be in good state: for every entity deleted, related event should have been fired)
2004 foreach (IEntityWrapper wrappedEntity in deletedEntities)
2006 // future enhancement: it does not make sense to return in the half way, either remove this code or
2007 // move it to the right place
2008 if (!this.ContainsEntity(wrappedEntity))
2013 // if this is a reference, set the EntityKey property before removing the relationship and entity
2014 EntityReference entityReference = this as EntityReference;
2015 if (entityReference != null)
2017 entityReference.DetachedEntityKey = entityReference.AttachedEntityKey;
2020 if (detachRelationship)
2022 DetachRelationshipFromObjectStateManager(wrappedEntity, _wrappedOwner, _relationshipSet, _navigation);
2024 RelatedEnd relatedEnd = GetOtherEndOfRelationship(wrappedEntity);
2025 relatedEnd.RemoveFromCache(_wrappedOwner, /* resetIsLoaded */ true, /*preserveForeignKey*/ false);
2026 relatedEnd.OnAssociationChanged(CollectionChangeAction.Remove, _wrappedOwner.Entity);
2029 // Clear the DetachedEntityKey if this is a foreign key
2032 EntityReference entityReference = this as EntityReference;
2033 if (entityReference != null)
2035 entityReference.DetachedEntityKey = null;
2039 foreach (IEntityWrapper wrappedEntity in deletedEntities)
2041 RelatedEnd relatedEnd = GetOtherEndOfRelationship(wrappedEntity);
2042 this.RemoveFromCache(wrappedEntity, /* resetIsLoaded */ false, /*preserveForeignKey*/ false);
2044 this.OnAssociationChanged(CollectionChangeAction.Refresh, null);
2046 Debug.Assert(this.IsEmpty(), "Collection or reference should be empty");
2051 internal void AddToCache(IEntityWrapper wrappedEntity, bool applyConstraints)
2053 AddToLocalCache(wrappedEntity, applyConstraints);
2054 AddToObjectCache(wrappedEntity);
2056 internal abstract void AddToLocalCache(IEntityWrapper wrappedEntity, bool applyConstraints);
2057 internal abstract void AddToObjectCache(IEntityWrapper wrappedEntity);
2063 internal bool RemoveFromCache(IEntityWrapper wrappedEntity, bool resetIsLoaded, bool preserveForeignKey)
2065 bool result = RemoveFromLocalCache(wrappedEntity, resetIsLoaded, preserveForeignKey);
2066 RemoveFromObjectCache(wrappedEntity);
2069 // Remove from the RelatedEnd
2070 internal abstract bool RemoveFromLocalCache(IEntityWrapper wrappedEntity, bool resetIsLoaded, bool preserveForeignKey);
2071 // Remove from the underlying POCO navigation property
2072 internal abstract bool RemoveFromObjectCache(IEntityWrapper wrappedEntity);
2076 internal abstract bool VerifyEntityForAdd(IEntityWrapper wrappedEntity, bool relationshipAlreadyExists);
2077 internal abstract void VerifyType(IEntityWrapper wrappedEntity);
2078 internal abstract bool CanSetEntityType(IEntityWrapper wrappedEntity);
2079 internal abstract void Include(bool addRelationshipAsUnchanged, bool doAttach);
2080 internal abstract void Exclude();
2081 internal abstract void ClearCollectionOrRef(IEntityWrapper wrappedEntity, RelationshipNavigation navigation, bool doCascadeDelete);
2082 internal abstract bool ContainsEntity(IEntityWrapper wrappedEntity);
2083 internal abstract IEnumerable GetInternalEnumerable();
2084 internal abstract IEnumerable<IEntityWrapper> GetWrappedEntities();
2085 internal abstract void RetrieveReferentialConstraintProperties(Dictionary<string, KeyValuePair<object, IntBox>> keyValues, HashSet<object> visited);
2086 internal abstract bool IsEmpty();
2087 internal abstract void OnRelatedEndClear();
2088 internal abstract void ClearWrappedValues();
2089 internal abstract void VerifyMultiplicityConstraintsForAdd(bool applyConstraints);
2091 internal virtual void OnAssociationChanged(CollectionChangeAction collectionChangeAction, object entity)
2093 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
2094 if (!_suppressEvents)
2096 if (_onAssociationChanged != null)
2098 _onAssociationChanged(this, (new CollectionChangeEventArgs(collectionChangeAction, entity)));
2103 private void AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, bool doAttach)
2105 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2106 Debug.Assert(_context != null, "Can't add to state manager if _context is null");
2107 Debug.Assert(!UsingNoTracking, "Should not add an Entity to ObjectStateManager for NoTracking cases.");
2109 EntitySet es = GetTargetEntitySetFromRelationshipSet();
2112 _context.AddSingleObject(es, wrappedEntity, "entity");
2116 _context.AttachSingleObject(wrappedEntity, es, "entity");
2119 // Now that we know we have a valid EntityKey for the target entity, verify that it matches the detached EntityKey, if there is one
2120 EntityReference entityRef = this as EntityReference;
2121 if (entityRef != null && entityRef.DetachedEntityKey != null)
2123 EntityKey targetKey = wrappedEntity.EntityKey;
2124 if (entityRef.DetachedEntityKey != targetKey)
2126 throw EntityUtil.EntityKeyValueMismatch();
2128 // else -- null just means the key isn't set, so the target entity key doesn't also have to be null
2132 internal EntitySet GetTargetEntitySetFromRelationshipSet()
2134 EntitySet entitySet = null;
2135 AssociationSet associationSet = (AssociationSet)_relationshipSet;
2136 Debug.Assert(associationSet != null, "(AssociationSet) cast failed");
2138 AssociationEndMember associationEndMember = (AssociationEndMember)ToEndMember;
2139 Debug.Assert(associationEndMember != null, "(AssociationEndMember) cast failed");
2141 entitySet = associationSet.AssociationSetEnds[associationEndMember.Name].EntitySet;
2142 Debug.Assert(entitySet != null, "cannot find entitySet");
2146 private RelationshipEntry AddRelationshipToObjectStateManager(IEntityWrapper wrappedEntity, bool addRelationshipAsUnchanged, bool doAttach)
2148 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2149 Debug.Assert(!UsingNoTracking, "Should not add Relationship to ObjectStateManager for NoTracking cases.");
2150 Debug.Assert(!this.IsForeignKey, "for IsForeignKey relationship ObjectStateEntries don't exist");
2151 Debug.Assert(this._context != null && wrappedEntity.Context != null, "should be called only if both entities are attached");
2152 Debug.Assert(this._context == wrappedEntity.Context, "both entities should be attached to the same context");
2154 EntityKey ownerKey = _wrappedOwner.EntityKey;
2155 EntityKey entityKey = wrappedEntity.EntityKey;
2156 EntityUtil.CheckEntityKeyNull(ownerKey);
2157 EntityUtil.CheckEntityKeyNull(entityKey);
2159 return this.ObjectContext.ObjectStateManager.AddRelation(
2160 new RelationshipWrapper((AssociationSet)_relationshipSet,
2161 new KeyValuePair<string, EntityKey>(_navigation.From, ownerKey),
2162 new KeyValuePair<string, EntityKey>(_navigation.To, entityKey)),
2163 // When Add method is called through Load API the relationship cache entries
2164 // needs to be added to ObjectStateManager in Unchanged state rather then Added state
2165 (addRelationshipAsUnchanged || doAttach) ? EntityState.Unchanged : EntityState.Added);
2168 private static void WalkObjectGraphToIncludeAllRelatedEntities(IEntityWrapper wrappedEntity,
2169 bool addRelationshipAsUnchanged, bool doAttach)
2171 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2172 foreach (RelatedEnd relatedEnd in wrappedEntity.RelationshipManager.Relationships)
2174 relatedEnd.Include(addRelationshipAsUnchanged, doAttach);
2178 internal static void RemoveEntityFromObjectStateManager(IEntityWrapper wrappedEntity)
2180 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2183 if (wrappedEntity.Context != null &&
2184 wrappedEntity.Context.ObjectStateManager.TransactionManager.IsAttachTracking &&
2185 wrappedEntity.Context.ObjectStateManager.TransactionManager.PromotedKeyEntries.TryGetValue(wrappedEntity.Entity, out entry))
2187 // This is executed only in the cleanup code from ObjectContext.AttachTo()
2188 // If the entry was promoted in AttachTo(), it has to be degraded now instead of being deleted.
2189 entry.DegradeEntry();
2193 entry = MarkEntityAsDeletedInObjectStateManager(wrappedEntity);
2194 if (entry != null && entry.State != EntityState.Detached)
2196 entry.AcceptChanges();
2201 private static void RemoveRelationshipFromObjectStateManager(IEntityWrapper wrappedEntity, IEntityWrapper wrappedOwner, RelationshipSet relationshipSet, RelationshipNavigation navigation)
2203 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2204 Debug.Assert(relationshipSet == null || !(relationshipSet.ElementType as AssociationType).IsForeignKey, "for IsForeignKey relationships ObjectStateEntries don't exist");
2206 RelationshipEntry deletedEntry = MarkRelationshipAsDeletedInObjectStateManager(wrappedEntity, wrappedOwner, relationshipSet, navigation);
2207 if (deletedEntry != null && deletedEntry.State != EntityState.Detached)
2209 deletedEntry.AcceptChanges();
2213 private void FixupOtherEndOfRelationshipForRemove(IEntityWrapper wrappedEntity, bool preserveForeignKey)
2215 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2216 RelatedEnd relatedEnd = GetOtherEndOfRelationship(wrappedEntity);
2217 relatedEnd.Remove(_wrappedOwner, /*fixup*/false, /*deleteEntity*/false, /*deleteOwner*/false, /*applyReferentialConstraints*/false, preserveForeignKey);
2218 relatedEnd.RemoveFromNavigationProperty(_wrappedOwner);
2221 private static EntityEntry MarkEntityAsDeletedInObjectStateManager(IEntityWrapper wrappedEntity)
2223 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2224 EntityEntry entry = null;
2225 if (wrappedEntity.Context != null)
2227 entry = wrappedEntity.Context.ObjectStateManager.FindEntityEntry(wrappedEntity.Entity);
2231 entry.Delete(/*doFixup*/false);
2237 private static RelationshipEntry MarkRelationshipAsDeletedInObjectStateManager(IEntityWrapper wrappedEntity, IEntityWrapper wrappedOwner, RelationshipSet relationshipSet, RelationshipNavigation navigation)
2239 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2240 Debug.Assert(relationshipSet == null || !(relationshipSet.ElementType as AssociationType).IsForeignKey, "for IsForeignKey relationships ObjectStateEntries don't exist");
2241 RelationshipEntry entry = null;
2242 if (wrappedOwner.Context != null && wrappedEntity.Context != null && relationshipSet != null)
2244 EntityKey ownerKey = wrappedOwner.EntityKey;
2245 EntityKey entityKey = wrappedEntity.EntityKey;
2247 entry = wrappedEntity.Context.ObjectStateManager.DeleteRelationship(relationshipSet,
2248 new KeyValuePair<string, EntityKey>(navigation.From, ownerKey),
2249 new KeyValuePair<string, EntityKey>(navigation.To, entityKey));
2254 private static void DetachRelationshipFromObjectStateManager(IEntityWrapper wrappedEntity, IEntityWrapper wrappedOwner, RelationshipSet relationshipSet, RelationshipNavigation navigation)
2256 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2257 if (wrappedOwner.Context != null && wrappedEntity.Context != null && relationshipSet != null)
2259 EntityKey ownerKey = wrappedOwner.EntityKey;
2260 EntityKey entityKey = wrappedEntity.EntityKey;
2261 RelationshipEntry entry = wrappedEntity.Context.ObjectStateManager.FindRelationship(relationshipSet,
2262 new KeyValuePair<string, EntityKey>(navigation.From, ownerKey),
2263 new KeyValuePair<string, EntityKey>(navigation.To, entityKey));
2266 entry.DetachRelationshipEntry();
2271 private static void RemoveEntityFromRelatedEnds(IEntityWrapper wrappedEntity1, IEntityWrapper wrappedEntity2, RelationshipNavigation navigation)
2273 Debug.Assert(wrappedEntity1 != null, "IEntityWrapper instance is null.");
2274 Debug.Assert(wrappedEntity2 != null, "IEntityWrapper instance is null.");
2275 foreach (RelatedEnd relatedEnd in wrappedEntity1.RelationshipManager.Relationships)
2277 bool doCascadeDelete = false;
2278 //check for cascade delete flag
2279 doCascadeDelete = CheckCascadeDeleteFlag(relatedEnd.FromEndProperty) || relatedEnd.IsPrincipalEndOfReferentialConstraint();
2280 //Remove the owner from the related end
2281 relatedEnd.Clear(wrappedEntity2, navigation, doCascadeDelete);
2285 private static bool CheckCascadeDeleteFlag(RelationshipEndMember relationEndProperty)
2287 if (null != relationEndProperty)
2289 return (relationEndProperty.DeleteBehavior == OperationAction.Cascade);
2295 internal void AttachContext(ObjectContext context, MergeOption mergeOption)
2297 if (!_wrappedOwner.InitializingProxyRelatedEnds)
2299 EntityKey ownerKey = _wrappedOwner.EntityKey;
2300 EntityUtil.CheckEntityKeyNull(ownerKey);
2301 EntitySet entitySet = ownerKey.GetEntitySet(context.MetadataWorkspace);
2303 AttachContext(context, entitySet, mergeOption);
2309 /// Set the context and load options so that Query can be constructed on demand.
2311 internal void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
2313 EntityUtil.CheckArgumentNull(context, "context");
2314 EntityUtil.CheckArgumentMergeOption(mergeOption);
2315 EntityUtil.CheckArgumentNull(entitySet, "entitySet");
2317 _wrappedOwner.RelationshipManager.NodeVisited = false;
2318 // If the context is the same as what we already have, and the mergeOption is consistent with our UsingNoTracking setting, nothing more to do
2319 if (_context == context && (_usingNoTracking == (mergeOption == MergeOption.NoTracking)))
2324 bool doCleanup = true;
2328 // if the source isn't null, clear it
2329 _sourceQuery = null;
2330 this._context = context;
2331 this._usingNoTracking = (mergeOption == MergeOption.NoTracking);
2333 EdmType relationshipType;
2334 RelationshipSet relationshipSet;
2335 FindRelationshipSet(_context, entitySet, out relationshipType, out relationshipSet);
2337 if (relationshipSet != null)
2339 this._relationshipSet = relationshipSet;
2340 this._relationMetadata = (RelationshipType)relationshipType;
2344 foreach (EntitySetBase set in entitySet.EntityContainer.BaseEntitySets)
2346 AssociationSet associationset = set as AssociationSet;
2347 if (associationset != null)
2349 if (associationset.ElementType == relationshipType &&
2350 associationset.AssociationSetEnds[_navigation.From].EntitySet != entitySet &&
2351 associationset.AssociationSetEnds[_navigation.From].EntitySet.ElementType == entitySet.ElementType)
2352 throw EntityUtil.EntitySetIsNotValidForRelationship(entitySet.EntityContainer.Name, entitySet.Name, _navigation.From, ((AssociationSet)set).EntityContainer.Name, ((AssociationSet)set).Name);
2355 throw EntityUtil.NoRelationshipSetMatched(_navigation.RelationshipName);
2358 //find relation end property
2359 bool foundFromRelationEnd = false;
2360 bool foundToRelationEnd = false;
2361 foreach (AssociationEndMember relationEnd in ((AssociationType)_relationMetadata).AssociationEndMembers) //Only Association relationship is supported
2363 if (relationEnd.Name == this._navigation.From)
2365 Debug.Assert(!foundFromRelationEnd, "More than one related end was found with the same role name.");
2367 foundFromRelationEnd = true;
2368 this._fromEndProperty = relationEnd;
2370 if (relationEnd.Name == this._navigation.To)
2372 Debug.Assert(!foundToRelationEnd, "More than one related end was found with the same role name.");
2374 foundToRelationEnd = true;
2375 this._toEndProperty = relationEnd;
2378 if (!(foundFromRelationEnd && foundToRelationEnd))
2380 throw EntityUtil.RelatedEndNotFound();
2383 // If this is a stub EntityReference and the DetachedEntityKey is set, make sure it is valid
2386 // if there are no contained entities but this is a reference with a detached entity key, validate the key
2387 EntityReference entityRef = this as EntityReference;
2388 if (entityRef != null && entityRef.DetachedEntityKey != null)
2390 EntityKey detachedKey = entityRef.DetachedEntityKey;
2391 if (!IsValidEntityKeyType(detachedKey))
2393 // devnote: We have to check this here instead of in the EntityKey property setter,
2394 // because the key could be set to an invalid type temporarily during deserialization
2395 throw EntityUtil.CannotSetSpecialKeys();
2397 EntitySet targetEntitySet = detachedKey.GetEntitySet(context.MetadataWorkspace);
2398 CheckRelationEntitySet(targetEntitySet);
2399 detachedKey.ValidateEntityKey(ObjectContext.MetadataWorkspace, targetEntitySet);
2402 // else even for a reference we don't need to validate the key
2403 // because it will be checked later once we have the key for the contained entity
2411 // Uninitialize fields, so the cleanup code (for example in RelationshipWrapper.RemoveRelatedEntitiesFromObjectStateManager)
2412 // knows that this RelatedEnd was not properly Attached.
2413 this.DetachContext();
2418 internal void FindRelationshipSet(ObjectContext context, EntitySet entitySet, out EdmType relationshipType, out RelationshipSet relationshipSet)
2420 // find the relationship set
2421 Debug.Assert(context.MetadataWorkspace != null, "The context should not have a null metadata workspace.");
2423 // find the TypeMetadata for the given relationship
2424 relationshipType = context.MetadataWorkspace.GetItem<EdmType>(_navigation.RelationshipName, DataSpace.CSpace);
2425 if (relationshipType == null)
2427 throw EntityUtil.NoRelationshipSetMatched(_navigation.RelationshipName);
2430 // find the RelationshipSet
2431 foreach (EntitySetBase entitySetBase in entitySet.EntityContainer.BaseEntitySets)
2433 if ((EdmType)entitySetBase.ElementType == relationshipType)
2435 if (((AssociationSet)entitySetBase).AssociationSetEnds[_navigation.From].EntitySet == entitySet)
2437 relationshipSet = (RelationshipSet)entitySetBase;
2442 relationshipSet = null;
2446 /// Clear the source and context.
2448 internal void DetachContext()
2450 if (this._context != null &&
2451 this.ObjectContext.ObjectStateManager.TransactionManager.IsAttachTracking &&
2452 this.ObjectContext.ObjectStateManager.TransactionManager.OriginalMergeOption == MergeOption.NoTracking)
2454 this._usingNoTracking = true;
2458 this._sourceQuery = null;
2459 this._context = null;
2460 this._relationshipSet = null;
2461 this._fromEndProperty = null;
2462 this._toEndProperty = null;
2463 this._relationMetadata = null;
2465 // Detached entity should have IsLoaded property set to false
2466 this._isLoaded = false;
2470 /// This method is very similar to the private method in Query of U class
2471 /// Only difference is that it calls meterializer with a overload which does
2472 /// not skip the deleted items.
2474 /// <param name="query">query of U</param>
2475 /// <returns></returns>
2476 internal static IEnumerable<U> GetResults<U>(ObjectQuery<U> query)
2478 return query.Execute(query.MergeOption);
2481 internal RelatedEnd GetOtherEndOfRelationship(IEntityWrapper wrappedEntity)
2483 Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
2484 EnsureRelationshipNavigationAccessorsInitialized();
2485 return (RelatedEnd)wrappedEntity.RelationshipManager.GetRelatedEnd(_navigation.Reverse, _relationshipFixer);
2488 // We have to allow a default constructor for serialization, so we need to make sure that the only
2489 // thing you can do with a null owner is get/set the EntityReference.EntityKey property. All other
2490 // operations are invalid. This needs to be used on all public methods in this class and EntityReference
2491 // but not in EntityCollection because EntityCollection does not have a default constructor.
2492 // It is not possible to get an EntityReference with a null Owner into the RelationshipManager, and there
2493 // is no way to access EntityReference without creating one using the default constructor or going through
2494 // the RelationshipManager, so we don't need to check this in internal or private methods.
2495 internal void CheckOwnerNull()
2497 if (_wrappedOwner.Entity == null)
2499 throw EntityUtil.OwnerIsNull();
2503 // This method is intended to be used to support the public API InitializeRelatedReference, where we have to take an existing EntityReference
2504 // and set up the appropriate fields as shown below, instead of creating a new EntityReference and setting these fields in the constructor.
2505 // This is also used by the constructor -- if we add something that needs to be set at construction time, it probably needs to be set for InitializeRelatedReference as well.
2506 internal void InitializeRelatedEnd(IEntityWrapper wrappedOwner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
2508 SetWrappedOwner(wrappedOwner);
2509 _navigation = navigation;
2510 _relationshipFixer = relationshipFixer;
2513 internal void SetWrappedOwner(IEntityWrapper wrappedOwner)
2515 _wrappedOwner = wrappedOwner != null ? wrappedOwner : EntityWrapperFactory.NullWrapper;
2516 #pragma warning disable 612 // Disable "obsolete" warning for the _owner field. Used for backwards compatibility.
2517 _owner = wrappedOwner.Entity as IEntityWithRelationships;
2518 #pragma warning restore 612
2521 internal static bool IsValidEntityKeyType(EntityKey entityKey)
2523 return !(entityKey.IsTemporary ||
2524 Object.ReferenceEquals(EntityKey.EntityNotValidKey, entityKey) ||
2525 Object.ReferenceEquals(EntityKey.NoEntitySetKey, entityKey));
2528 // This method is required to maintain compatibility with the v1 binary serialization format.
2529 // In particular, it recreates a entity wrapper from the serialized owner.
2530 // Note that this is only expected to work for non-POCO entities, since serialization of POCO
2531 // entities will not result in serialization of the RelationshipManager or its related objects.
2534 [EditorBrowsable(EditorBrowsableState.Never)]
2535 public void OnDeserialized(StreamingContext context)
2537 #pragma warning disable 612 // Disable "obsolete" warning for the _owner field. Used for backwards compatibility.
2538 _wrappedOwner = EntityWrapperFactory.WrapEntityUsingContext(_owner, ObjectContext);
2539 #pragma warning restore 612
2543 private NavigationProperty navigationPropertyCache = null;
2545 internal NavigationProperty NavigationProperty
2549 if (this.navigationPropertyCache == null && _wrappedOwner.Context != null && this.TargetAccessor.HasProperty)
2551 string navigationPropertyName = this.TargetAccessor.PropertyName;
2553 EntityType entityType = _wrappedOwner.Context.MetadataWorkspace.GetItem<EntityType>(_wrappedOwner.IdentityType.FullName, DataSpace.OSpace);
2554 NavigationProperty member;
2555 if (!entityType.NavigationProperties.TryGetValue(navigationPropertyName, false, out member))
2557 throw new InvalidOperationException(System.Data.Entity.Strings.RelationshipManager_NavigationPropertyNotFound(navigationPropertyName));
2559 // Avoid metadata lookups by caching the navigation property locally
2560 this.navigationPropertyCache = member;
2562 return this.navigationPropertyCache;
2566 #region POCO Navigation Property Accessors
2568 internal NavigationPropertyAccessor TargetAccessor
2572 if (_wrappedOwner.Entity != null)
2574 EnsureRelationshipNavigationAccessorsInitialized();
2575 return RelationshipNavigation.ToPropertyAccessor;
2579 // Disconnected RelatedEnds have no POCO navigation properties
2580 return NavigationPropertyAccessor.NoNavigationProperty;
2585 // If the RelationshipNavigation has not been fully initialized, it means this RelatedEnd was created without metadata
2586 // This can occur in serialization scenarios
2587 // Try to look up the metadata in all metadata repositories that are available and populate it
2588 // This must be called before accessing any of the Accessor properties on the RelationshipNavigation
2589 private void EnsureRelationshipNavigationAccessorsInitialized()
2591 Debug.Assert(_navigation != null, "Null RelationshipNavigation");
2592 Debug.Assert(_wrappedOwner.Entity != null, "Must be connected to lookup metadata");
2593 if (!RelationshipNavigation.IsInitialized)
2595 NavigationPropertyAccessor sourceAccessor = null;
2596 NavigationPropertyAccessor targetAccessor = null;
2598 AssociationType associationType = this.RelationMetadata as AssociationType;
2599 string relationshipName = _navigation.RelationshipName;
2600 string sourceRoleName = _navigation.From;
2601 string targetRoleName = _navigation.To;
2602 if (associationType != null ||
2603 RelationshipManager.TryGetRelationshipType(WrappedOwner, WrappedOwner.IdentityType, relationshipName, out associationType) ||
2604 EntityProxyFactory.TryGetAssociationTypeFromProxyInfo(WrappedOwner, relationshipName, targetRoleName, out associationType))
2606 AssociationEndMember sourceEnd;
2607 if (associationType.AssociationEndMembers.TryGetValue(sourceRoleName, false, out sourceEnd))
2609 EntityType sourceEntityType = MetadataHelper.GetEntityTypeForEnd(sourceEnd);
2610 targetAccessor = MetadataHelper.GetNavigationPropertyAccessor(sourceEntityType, relationshipName, sourceRoleName, targetRoleName);
2613 AssociationEndMember targetEnd;
2614 if (associationType.AssociationEndMembers.TryGetValue(targetRoleName, false, out targetEnd))
2616 EntityType targetEntityType = MetadataHelper.GetEntityTypeForEnd(targetEnd);
2617 sourceAccessor = MetadataHelper.GetNavigationPropertyAccessor(targetEntityType, relationshipName, targetRoleName, sourceRoleName);
2621 if (sourceAccessor == null || targetAccessor == null)
2623 throw RelationshipManager.UnableToGetMetadata(WrappedOwner, relationshipName);
2626 RelationshipNavigation.InitializeAccessors(sourceAccessor, targetAccessor);