1 //---------------------------------------------------------------------
2 // <copyright file="RelationshipManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 namespace System
.Data
.Objects
.DataClasses
11 using System
.Collections
;
12 using System
.Collections
.Generic
;
13 using System
.ComponentModel
;
14 using System
.Data
.Common
.Utils
;
15 using System
.Data
.Mapping
;
16 using System
.Data
.Metadata
.Edm
;
17 using System
.Data
.Objects
.Internal
;
18 using System
.Diagnostics
;
20 using System
.Runtime
.Serialization
;
23 /// Container for the lazily created relationship navigation
24 /// property objects (collections and refs).
27 public class RelationshipManager
33 // This method is private in order to force all creation of this
34 // object to occur through the public static Create method.
35 // See comments on that method for more details.
36 private RelationshipManager()
44 // The following fields are serialized. Adding or removing a serialized field is considered
45 // a breaking change. This includes changing the field type or field name of existing
46 // serialized fields. If you need to make this kind of change, it may be possible, but it
47 // will require some custom serialization/deserialization code.
49 // Note that this field should no longer be used directly. Instead, use the _wrappedOwner
50 // field. This field is retained only for compatibility with the serialization format introduced in v1.
51 private IEntityWithRelationships _owner
;
53 private List
<RelatedEnd
> _relationships
;
56 private bool _nodeVisited
;
59 private IEntityWrapper _wrappedOwner
;
66 /// Returns a defensive copy of all the known relationships. The copy is defensive because
67 /// new items may get added to the collection while the caller is iterating over it. Without
68 /// the copy this would cause an exception for concurrently modifying the collection.
70 internal IEnumerable
<RelatedEnd
> Relationships
74 EnsureRelationshipsInitialized();
75 return _relationships
.ToArray();
80 /// Lazy initialization of the _relationships collection.
82 private void EnsureRelationshipsInitialized()
84 if (null == _relationships
)
86 _relationships
= new List
<RelatedEnd
>();
91 /// this flag is used to keep track of nodes which have
92 /// been visited. Currently used for Exclude operation.
94 internal bool NodeVisited
102 _nodeVisited
= value;
107 /// Provides access to the entity that owns this manager in its wrapped form.
109 internal IEntityWrapper WrappedOwner
113 if (_wrappedOwner
== null)
115 _wrappedOwner
= EntityWrapperFactory
.CreateNewWrapper(_owner
, null);
117 return _wrappedOwner
;
126 /// Factory method to create a new RelationshipManager object.
128 /// Used by data classes that support relationships. If the change tracker
129 /// requests the RelationshipManager property and the data class does not
130 /// already have a reference to one of these objects, it calls this method
131 /// to create one, then saves a reference to that object. On subsequent accesses
132 /// to that property, the data class should return the saved reference.
134 /// The reason for using a factory method instead of a public constructor is to
135 /// emphasize that this is not something you would normally call outside of a data class.
136 /// By requiring that these objects are created via this method, developers should
137 /// give more thought to the operation, and will generally only use it when
138 /// they explicitly need to get an object of this type. It helps define the intended usage.
140 /// <param name="owner">Reference to the entity that is calling this method</param>
141 /// <exception cref="ArgumentNullException"><paramref name="owner"/> is null</exception>
142 /// <returns>A new or existing RelationshipManager for the given entity</returns>
143 public static RelationshipManager
Create(IEntityWithRelationships owner
)
145 EntityUtil
.CheckArgumentNull(owner
, "owner");
146 RelationshipManager rm
= new RelationshipManager();
152 /// Factory method that creates a new, uninitialized RelationshipManager. This should only be
153 /// used to create a RelationshipManager for an IEntityWrapper for an entity that does not
154 /// implement IEntityWithRelationships. For entities that implement IEntityWithRelationships,
155 /// the Create(IEntityWithRelationships) method should be used instead.
157 /// <returns>The new RelationshipManager</returns>
158 internal static RelationshipManager
Create()
160 return new RelationshipManager();
164 /// Replaces the existing wrapped owner with one that potentially contains more information,
165 /// such as an entity key. Both must wrap the same entity.
167 internal void SetWrappedOwner(IEntityWrapper wrappedOwner
, object expectedOwner
)
169 _wrappedOwner
= wrappedOwner
;
170 Debug
.Assert(_owner
!= null || !(wrappedOwner
.Entity
is IEntityWithRelationships
), "_owner should only be null if entity is not IEntityWithRelationships");
171 // We need to check that the RelationshipManager created by the entity has the correct owner set,
172 // since the entity can pass any value into RelationshipManager.Create().
173 if (_owner
!= null && !Object
.ReferenceEquals(expectedOwner
, _owner
))
175 throw EntityUtil
.InvalidRelationshipManagerOwner();
178 if (null != _relationships
)
180 // Not using defensive copy here since SetWrappedOwner should not cause change in underlying
181 // _relationships collection.
182 foreach (RelatedEnd relatedEnd
in _relationships
)
184 relatedEnd
.SetWrappedOwner(wrappedOwner
);
190 /// Get the collection of entities related to the current entity using the specified
191 /// combination of relationship name, source role name, and target role name
193 /// <typeparam name="TSourceEntity">Type of the entity in the source role (same as the type of this)</typeparam>
194 /// <typeparam name="TTargetEntity">Type of the entity in the target role</typeparam>
195 /// <param name="relationshipName">CSpace-qualified name of the relationship to navigate</param>
196 /// <param name="sourceRoleName">Name of the source role for the navigation. Indicates the direction of navigation across the relationship.</param>
197 /// <param name="targetRoleName">Name of the target role for the navigation. Indicates the direction of navigation across the relationship.</param>
198 /// <param name="sourcePropertyName">Name of the property on the source of the navigation.</param>
199 /// <param name="targetPropertyName">Name of the property on the target of the navigation.</param>
200 /// <param name="sourceRoleMultiplicity">Multiplicity of the source role. RelationshipMultiplicity.OneToOne and RelationshipMultiplicity.Zero are both
201 /// accepted for a reference end, and RelationshipMultiplicity.Many is accepted for a collection</param>
202 /// <returns>Collection of related entities of type TTargetEntity</returns>
203 internal EntityCollection
<TTargetEntity
> GetRelatedCollection
<TSourceEntity
, TTargetEntity
>(string relationshipName
,
204 string sourceRoleName
, string targetRoleName
, NavigationPropertyAccessor sourceAccessor
, NavigationPropertyAccessor targetAccessor
,
205 RelationshipMultiplicity sourceRoleMultiplicity
, RelatedEnd existingRelatedEnd
)
206 where TSourceEntity
: class
207 where TTargetEntity
: class
209 EntityCollection
<TTargetEntity
> collection
;
210 RelatedEnd relatedEnd
;
211 TryGetCachedRelatedEnd(relationshipName
, targetRoleName
, out relatedEnd
);
213 if (existingRelatedEnd
== null)
215 if (relatedEnd
!= null)
217 collection
= relatedEnd
as EntityCollection
<TTargetEntity
>;
218 // Because this is a private method that will only be called for target roles that actually have a
219 // multiplicity that works with EntityReference, this should never be null. If the user requests
220 // a collection or reference and it doesn't match the target role multiplicity, it will be detected
221 // in the public GetRelatedCollection<T> or GetRelatedReference<T>
222 Debug
.Assert(collection
!= null, "should never receive anything but an EntityCollection here");
227 RelationshipNavigation navigation
= new RelationshipNavigation(relationshipName
, sourceRoleName
, targetRoleName
, sourceAccessor
, targetAccessor
);
228 return CreateRelatedEnd
<TSourceEntity
, TTargetEntity
>(navigation
, sourceRoleMultiplicity
, RelationshipMultiplicity
.Many
, existingRelatedEnd
) as EntityCollection
<TTargetEntity
>;
233 // There is no need to supress events on the existingRelatedEnd because setting events on a disconnected
234 // EntityCollection is an InvalidOperation
235 Debug
.Assert(existingRelatedEnd
._onAssociationChanged
== null, "Disconnected RelatedEnd had events");
237 if (relatedEnd
!= null)
239 Debug
.Assert(_relationships
!= null, "Expected _relationships to be non-null.");
240 _relationships
.Remove(relatedEnd
);
243 RelationshipNavigation navigation
= new RelationshipNavigation(relationshipName
, sourceRoleName
, targetRoleName
, sourceAccessor
, targetAccessor
);
244 collection
= CreateRelatedEnd
<TSourceEntity
, TTargetEntity
>(navigation
, sourceRoleMultiplicity
, RelationshipMultiplicity
.Many
, existingRelatedEnd
) as EntityCollection
<TTargetEntity
>;
246 if (collection
!= null)
248 bool doCleanup
= true;
251 RemergeCollections(relatedEnd
as EntityCollection
<TTargetEntity
>, collection
);
256 // An error occured so we need to put the previous relatedEnd back into the RelationshipManager
257 if (doCleanup
&& relatedEnd
!= null)
259 Debug
.Assert(_relationships
!= null, "Expected _relationships to be non-null.");
260 _relationships
.Remove(collection
);
261 _relationships
.Add(relatedEnd
);
270 /// Re-merge items from collection so that relationship fixup is performed.
271 /// Ensure that any items in previous collection are excluded from the re-merge
273 /// <typeparam name="TTargetEntity"></typeparam>
274 /// <param name="previousCollection">The previous EntityCollection containing items that have already had fixup performed</param>
275 /// <param name="collection">The new EntityCollection</param>
276 private void RemergeCollections
<TTargetEntity
>(EntityCollection
<TTargetEntity
> previousCollection
,
277 EntityCollection
<TTargetEntity
> collection
)
278 where TTargetEntity
: class
280 Debug
.Assert(collection
!= null, "collection is null");
281 // If there is a previousCollection, we only need to merge the items that are
282 // in the collection but not in the previousCollection
283 // Ensure that all of the items in the previousCollection are already in the new collection
285 int relatedEntityCount
= 0;
287 // We will be modifing the collection's enumerator, so we need to make a copy of it
288 List
<IEntityWrapper
> tempEntities
= new List
<IEntityWrapper
>(collection
.CountInternal
);
289 foreach (IEntityWrapper wrappedEntity
in collection
.GetWrappedEntities())
291 tempEntities
.Add(wrappedEntity
);
294 // Iterate through the entities that require merging
295 // If the previousCollection already contained the entity, no additional work is needed
296 // If the previousCollection did not contain the entity,
297 // then remove it from the collection and re-add it to force relationship fixup
298 foreach (IEntityWrapper wrappedEntity
in tempEntities
)
300 bool requiresMerge
= true;
301 if (previousCollection
!= null)
303 // There is no need to merge and do fixup if the entity was already in the previousCollection because
304 // fixup would have already taken place when it was added to the previousCollection
305 if (previousCollection
.ContainsEntity(wrappedEntity
))
307 relatedEntityCount
++;
308 requiresMerge
= false;
314 // Remove and re-add the item to the collections to force fixup
315 collection
.Remove(wrappedEntity
, false);
316 collection
.Add(wrappedEntity
);
320 // Ensure that all of the items in the previousCollection are already in the new collection
321 if (previousCollection
!= null && relatedEntityCount
!= previousCollection
.CountInternal
)
323 throw EntityUtil
.CannotRemergeCollections();
328 /// Get the entity reference of a related entity using the specified
329 /// combination of relationship name, source role name, and target role name
331 /// <param name="relationshipName">CSpace-qualified name of the relationship to navigate</param>
332 /// <param name="sourceRoleName">Name of the source role for the navigation. Indicates the direction of navigation across the relationship.</param>
333 /// <param name="targetRoleName">Name of the target role for the navigation. Indicates the direction of navigation across the relationship.</param>
334 /// <param name="sourcePropertyName">Name of the property on the source of the navigation.</param>
335 /// <param name="targetPropertyName">Name of the property on the target of the navigation.</param>
336 /// <param name="sourceRoleMultiplicity">Multiplicity of the source role. RelationshipMultiplicity.OneToOne and RelationshipMultiplicity.Zero are both
337 /// accepted for a reference end, and RelationshipMultiplicity.Many is accepted for a collection</param>
338 /// <returns>Reference for related entity of type TTargetEntity</returns>
339 internal EntityReference
<TTargetEntity
> GetRelatedReference
<TSourceEntity
, TTargetEntity
>(string relationshipName
,
340 string sourceRoleName
, string targetRoleName
, NavigationPropertyAccessor sourceAccessor
, NavigationPropertyAccessor targetAccessor
,
341 RelationshipMultiplicity sourceRoleMultiplicity
, RelatedEnd existingRelatedEnd
)
342 where TSourceEntity
: class
343 where TTargetEntity
: class
345 EntityReference
<TTargetEntity
> entityRef
;
346 RelatedEnd relatedEnd
;
348 if (TryGetCachedRelatedEnd(relationshipName
, targetRoleName
, out relatedEnd
))
350 entityRef
= relatedEnd
as EntityReference
<TTargetEntity
>;
351 // Because this is a private method that will only be called for target roles that actually have a
352 // multiplicity that works with EntityReference, this should never be null. If the user requests
353 // a collection or reference and it doesn't match the target role multiplicity, it will be detected
354 // in the public GetRelatedCollection<T> or GetRelatedReference<T>
355 Debug
.Assert(entityRef
!= null, "should never receive anything but an EntityReference here");
360 RelationshipNavigation navigation
= new RelationshipNavigation(relationshipName
, sourceRoleName
, targetRoleName
, sourceAccessor
, targetAccessor
);
361 return CreateRelatedEnd
<TSourceEntity
, TTargetEntity
>(navigation
, sourceRoleMultiplicity
, RelationshipMultiplicity
.One
, existingRelatedEnd
) as EntityReference
<TTargetEntity
>;
366 /// Internal version of GetRelatedEnd that works with the o-space navigation property
367 /// name rather than the c-space relationship name and end name.
369 /// <param name="navigationProperty">the name of the property to lookup</param>
370 /// <returns>the related end for the given property</returns>
371 internal RelatedEnd
GetRelatedEnd(string navigationProperty
, bool throwArgumentException
= false)
373 IEntityWrapper wrappedOwner
= WrappedOwner
;
374 Debug
.Assert(wrappedOwner
.Entity
!= null, "Entity is null");
375 Debug
.Assert(wrappedOwner
.Context
!= null, "Context is null");
376 Debug
.Assert(wrappedOwner
.Context
.MetadataWorkspace
!= null, "MetadataWorkspace is null");
377 Debug
.Assert(wrappedOwner
.Context
.Perspective
!= null, "Perspective is null");
379 EntityType entityType
= wrappedOwner
.Context
.MetadataWorkspace
.GetItem
<EntityType
>(wrappedOwner
.IdentityType
.FullName
, DataSpace
.OSpace
);
381 if (!wrappedOwner
.Context
.Perspective
.TryGetMember(entityType
, navigationProperty
, false, out member
) ||
382 !(member
is NavigationProperty
))
384 var message
= System
.Data
.Entity
.Strings
.RelationshipManager_NavigationPropertyNotFound(navigationProperty
);
385 throw throwArgumentException
? (Exception
)new ArgumentException(message
) : (Exception
)new InvalidOperationException(message
);
387 NavigationProperty navProp
= (NavigationProperty
)member
;
388 return GetRelatedEndInternal(navProp
.RelationshipType
.FullName
, navProp
.ToEndMember
.Name
);
392 /// Returns either an EntityCollection or EntityReference of the correct type for the specified target role in a relationship
393 /// This is intended to be used in scenarios where the user doesn't have full metadata, including the static type
394 /// information for both ends of the relationship. This metadata is specified in the EdmRelationshipRoleAttribute
395 /// on each entity type in the relationship, so the metadata system can retrieve it based on the supplied relationship
396 /// name and target role name.
398 /// <param name="relationshipName">Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not.</param>
399 /// <param name="targetRoleName">Target role to use to retrieve the other end of relationshipName</param>
400 /// <returns>IRelatedEnd representing the EntityCollection or EntityReference that was retrieved</returns>
401 public IRelatedEnd
GetRelatedEnd(string relationshipName
, string targetRoleName
)
403 return GetRelatedEndInternal(PrependNamespaceToRelationshipName(relationshipName
), targetRoleName
);
406 // Internal version of GetRelatedEnd which returns the RelatedEnd as a RelatedEnd rather than an IRelatedEnd
407 internal RelatedEnd
GetRelatedEndInternal(string relationshipName
, string targetRoleName
)
409 EntityUtil
.CheckArgumentNull(relationshipName
, "relationshipName");
410 EntityUtil
.CheckArgumentNull(targetRoleName
, "targetRoleName");
412 IEntityWrapper wrappedOwner
= WrappedOwner
;
413 if (wrappedOwner
.Context
== null && wrappedOwner
.RequiresRelationshipChangeTracking
)
415 throw new InvalidOperationException(System
.Data
.Entity
.Strings
.RelationshipManager_CannotGetRelatEndForDetachedPocoEntity
);
418 RelatedEnd relatedEnd
= null;
420 // Try to get the AssociationType from metadata. This will contain all of the ospace metadata for this relationship
421 AssociationType associationType
= null;
422 if (!TryGetRelationshipType(wrappedOwner
, wrappedOwner
.IdentityType
, relationshipName
, out associationType
))
424 if (_relationships
!= null)
426 // Look for the RelatedEnd in the list that has already been retrieved
427 relatedEnd
= (from RelatedEnd end
in _relationships
428 where end
.RelationshipName
== relationshipName
&&
429 end
.TargetRoleName
== targetRoleName
430 select end
).FirstOrDefault();
433 if (relatedEnd
== null && !EntityProxyFactory
.TryGetAssociationTypeFromProxyInfo(wrappedOwner
, relationshipName
, targetRoleName
, out associationType
))
435 // If the end still cannot be found, throw an exception
436 throw UnableToGetMetadata(WrappedOwner
, relationshipName
);
440 if (relatedEnd
== null)
442 Debug
.Assert(associationType
!= null, "associationType is null");
443 relatedEnd
= GetRelatedEndInternal(relationshipName
, targetRoleName
, /*existingRelatedEnd*/ null, associationType
);
449 private RelatedEnd
GetRelatedEndInternal(string relationshipName
, string targetRoleName
, RelatedEnd existingRelatedEnd
, AssociationType relationship
)
451 return GetRelatedEndInternal(relationshipName
, targetRoleName
, existingRelatedEnd
, relationship
, true);
454 private RelatedEnd
GetRelatedEndInternal(string relationshipName
, string targetRoleName
, RelatedEnd existingRelatedEnd
, AssociationType relationship
, bool throwOnError
)
456 Debug
.Assert(relationshipName
!= null, "null relationshipNameFromUser");
457 Debug
.Assert(targetRoleName
!= null, "null targetRoleName");
458 // existingRelatedEnd can be null if we are not trying to initialize an existing end
459 Debug
.Assert(relationship
!= null, "null relationshipType");
461 AssociationEndMember sourceEnd
;
462 AssociationEndMember targetEnd
;
463 Debug
.Assert(relationship
.AssociationEndMembers
.Count
== 2, "Only 2-way relationships are currently supported");
465 RelatedEnd result
= null;
467 // There can only be two ends because we don't support n-way relationships -- figure out which end is the target and which is the source
468 // If we want to support n-way relationships in the future, we will need a different overload of GetRelatedEnd that takes the source role name as well
469 targetEnd
= relationship
.AssociationEndMembers
[1];
470 if (targetEnd
.Identity
!= targetRoleName
)
472 sourceEnd
= targetEnd
;
473 targetEnd
= relationship
.AssociationEndMembers
[0];
474 if (targetEnd
.Identity
!= targetRoleName
)
478 throw EntityUtil
.InvalidTargetRole(relationshipName
, targetRoleName
, "targetRoleName");
488 sourceEnd
= relationship
.AssociationEndMembers
[0];
491 // Validate that the source type matches the type of the owner
492 EntityType sourceEntityType
= MetadataHelper
.GetEntityTypeForEnd(sourceEnd
);
493 Debug
.Assert(sourceEntityType
.DataSpace
== DataSpace
.OSpace
&& sourceEntityType
.ClrType
!= null, "sourceEntityType must contain an ospace type");
494 Type sourceType
= sourceEntityType
.ClrType
;
495 IEntityWrapper wrappedOwner
= WrappedOwner
;
496 if (!(sourceType
.IsAssignableFrom(wrappedOwner
.IdentityType
)))
500 throw EntityUtil
.OwnerIsNotSourceType(wrappedOwner
.IdentityType
.FullName
, sourceType
.FullName
, sourceEnd
.Name
, relationshipName
);
503 else if (VerifyRelationship(relationship
, sourceEnd
.Name
, throwOnError
))
505 // Call a dynamic method that will call either GetRelatedCollection<T, T> or GetRelatedReference<T, T> for this relationship
506 result
= LightweightCodeGenerator
.GetRelatedEnd(this, sourceEnd
, targetEnd
, existingRelatedEnd
);
512 /// Takes an existing EntityReference that was created with the default constructor and initializes it using the provided relationship and target role names.
513 /// This method is designed to be used during deserialization only, and will throw an exception if the provided EntityReference has already been initialized,
514 /// if the relationship manager already contains a relationship with this name and target role, or if the relationship manager is already attached to a ObjectContext.
516 /// <typeparam name="TTargetEntity">Type of the entity represented by targetRoleName</typeparam>
517 /// <param name="relationshipName"></param>
518 /// <param name="targetRoleName"></param>
519 /// <param name="entityReference"></param>
521 [EditorBrowsable(EditorBrowsableState
.Never
)]
522 public void InitializeRelatedReference
<TTargetEntity
>(string relationshipName
, string targetRoleName
, EntityReference
<TTargetEntity
> entityReference
)
523 where TTargetEntity
: class
525 EntityUtil
.CheckArgumentNull(relationshipName
, "relationshipName");
526 EntityUtil
.CheckArgumentNull(targetRoleName
, "targetRoleName");
527 EntityUtil
.CheckArgumentNull(entityReference
, "entityReference");
529 if (entityReference
.WrappedOwner
.Entity
!= null)
531 throw EntityUtil
.ReferenceAlreadyInitialized();
534 IEntityWrapper wrappedOwner
= WrappedOwner
;
535 if (wrappedOwner
.Context
!= null && wrappedOwner
.MergeOption
!= MergeOption
.NoTracking
)
537 throw EntityUtil
.RelationshipManagerAttached();
540 // We need the CSpace-qualified name in order to determine if this relationship already exists, so look it up.
541 // If the relationship doesn't exist, we will use this type information to determine how to initialize the reference
542 relationshipName
= PrependNamespaceToRelationshipName(relationshipName
);
543 AssociationType relationship
= GetRelationshipType(wrappedOwner
.IdentityType
, relationshipName
);
545 RelatedEnd relatedEnd
;
546 if (TryGetCachedRelatedEnd(relationshipName
, targetRoleName
, out relatedEnd
))
548 // For some serialization scenarios, we have to allow replacing a related end that we already know about, but in those scenarios
549 // the end is always empty, so we can further restrict the user calling method method directly by doing this extra validation
550 if (!relatedEnd
.IsEmpty())
552 entityReference
.InitializeWithValue(relatedEnd
);
554 Debug
.Assert(_relationships
!= null, "Expected _relationships to be non-null.");
555 _relationships
.Remove(relatedEnd
);
558 EntityReference
<TTargetEntity
> reference
= GetRelatedEndInternal(relationshipName
, targetRoleName
, entityReference
, relationship
) as EntityReference
<TTargetEntity
>;
559 if (reference
== null)
561 throw EntityUtil
.ExpectedReferenceGotCollection(typeof(TTargetEntity
).Name
, targetRoleName
, relationshipName
);
566 /// Takes an existing EntityCollection that was created with the default constructor and initializes it using the provided relationship and target role names.
567 /// This method is designed to be used during deserialization only, and will throw an exception if the provided EntityCollection has already been initialized,
568 /// or if the relationship manager is already attached to a ObjectContext.
570 /// <typeparam name="TTargetEntity">Type of the entity represented by targetRoleName</typeparam>
571 /// <param name="relationshipName"></param>
572 /// <param name="targetRoleName"></param>
573 /// <param name="entityCollection"></param>
575 [EditorBrowsable(EditorBrowsableState
.Never
)]
576 public void InitializeRelatedCollection
<TTargetEntity
>(string relationshipName
, string targetRoleName
, EntityCollection
<TTargetEntity
> entityCollection
)
577 where TTargetEntity
: class
579 EntityUtil
.CheckArgumentNull(relationshipName
, "relationshipName");
580 EntityUtil
.CheckArgumentNull(targetRoleName
, "targetRoleName");
581 EntityUtil
.CheckArgumentNull(entityCollection
, "entityCollection");
583 if (entityCollection
.WrappedOwner
.Entity
!= null)
585 throw EntityUtil
.CollectionAlreadyInitialized();
588 IEntityWrapper wrappedOwner
= WrappedOwner
;
589 if (wrappedOwner
.Context
!= null && wrappedOwner
.MergeOption
!= MergeOption
.NoTracking
)
591 throw EntityUtil
.CollectionRelationshipManagerAttached();
594 // We need the CSpace-qualified name in order to determine if this relationship already exists, so look it up.
595 // If the relationship doesn't exist, we will use this type information to determine how to initialize the reference
596 relationshipName
= PrependNamespaceToRelationshipName(relationshipName
);
597 AssociationType relationship
= GetRelationshipType(wrappedOwner
.IdentityType
, relationshipName
);
599 EntityCollection
<TTargetEntity
> collection
= GetRelatedEndInternal(relationshipName
, targetRoleName
, entityCollection
, relationship
) as EntityCollection
<TTargetEntity
>;
600 if (collection
== null)
602 throw EntityUtil
.ExpectedCollectionGotReference(typeof(TTargetEntity
).Name
, targetRoleName
, relationshipName
);
607 /// Given a relationship name that may or may not be qualified with a namespace name, this method
608 /// attempts to lookup a namespace using the entity type that owns this RelationshipManager as a
609 /// source and adds that namespace to the front of the relationship name. If the namespace
610 /// can't be found, then the relationshipName is returned untouched and the expectation is that
611 /// other validations will fail later in the code paths that use this.
612 /// This method should only be used at the imediate top-level public surface since all internal
613 /// calls are expected to use fully qualified names already.
615 private string PrependNamespaceToRelationshipName(string relationshipName
)
617 EntityUtil
.CheckArgumentNull(relationshipName
, "relationshipName");
619 if (!relationshipName
.Contains('.'))
621 string identityName
= WrappedOwner
.IdentityType
.FullName
;
622 ObjectItemCollection objectItemCollection
= GetObjectItemCollection(WrappedOwner
);
623 EdmType entityType
= null;
624 if (objectItemCollection
!= null)
626 objectItemCollection
.TryGetItem
<EdmType
>(identityName
, out entityType
);
630 Dictionary
<string, EdmType
> types
= ObjectItemCollection
.LoadTypesExpensiveWay(WrappedOwner
.IdentityType
.Assembly
);
633 types
.TryGetValue(identityName
, out entityType
);
637 ClrEntityType clrEntityType
= entityType
as ClrEntityType
;
638 if (clrEntityType
!= null)
640 string ns
= clrEntityType
.CSpaceNamespaceName
;
641 Debug
.Assert(!string.IsNullOrEmpty(ns
), "Expected non-empty namespace for type.");
643 return ns
+ "." + relationshipName
;
646 return relationshipName
;
650 /// Trys to get an ObjectItemCollection and returns null if it can;t be found.
652 private static ObjectItemCollection
GetObjectItemCollection(IEntityWrapper wrappedOwner
)
654 if (wrappedOwner
.Context
!= null && wrappedOwner
.Context
.MetadataWorkspace
!= null)
656 return (ObjectItemCollection
)wrappedOwner
.Context
.MetadataWorkspace
.GetItemCollection(DataSpace
.OSpace
);
662 /// Trys to get the EntityType metadata and returns false if it can't be found.
664 private bool TryGetOwnerEntityType(out EntityType entityType
)
666 DefaultObjectMappingItemCollection mappings
;
668 if (TryGetObjectMappingItemCollection(WrappedOwner
, out mappings
) &&
669 mappings
.TryGetMap(WrappedOwner
.IdentityType
.FullName
, DataSpace
.OSpace
, out map
))
671 ObjectTypeMapping objectMap
= (ObjectTypeMapping
)map
;
672 if (Helper
.IsEntityType(objectMap
.EdmType
))
674 entityType
= (EntityType
)objectMap
.EdmType
;
684 /// Trys to get an DefaultObjectMappingItemCollection and returns false if it can't be found.
686 private static bool TryGetObjectMappingItemCollection(IEntityWrapper wrappedOwner
, out DefaultObjectMappingItemCollection collection
)
688 if (wrappedOwner
.Context
!= null && wrappedOwner
.Context
.MetadataWorkspace
!= null)
690 collection
= (DefaultObjectMappingItemCollection
)wrappedOwner
.Context
.MetadataWorkspace
.GetItemCollection(DataSpace
.OCSpace
);
691 return collection
!= null;
700 internal static bool TryGetRelationshipType(IEntityWrapper wrappedOwner
, Type entityClrType
, string relationshipName
, out AssociationType associationType
)
702 ObjectItemCollection objectItemCollection
= GetObjectItemCollection(wrappedOwner
);
703 if (objectItemCollection
!= null)
705 associationType
= objectItemCollection
.GetRelationshipType(entityClrType
, relationshipName
);
709 associationType
= ObjectItemCollection
.GetRelationshipTypeExpensiveWay(entityClrType
, relationshipName
);
712 return (associationType
!= null);
715 private AssociationType
GetRelationshipType(Type entityClrType
, string relationshipName
)
717 AssociationType associationType
= null;
718 if (!TryGetRelationshipType(WrappedOwner
, entityClrType
, relationshipName
, out associationType
))
720 throw UnableToGetMetadata(WrappedOwner
, relationshipName
);
722 return associationType
;
725 internal static Exception
UnableToGetMetadata(IEntityWrapper wrappedOwner
, string relationshipName
)
727 ArgumentException argException
= EntityUtil
.UnableToFindRelationshipTypeInMetadata(relationshipName
, "relationshipName");
728 if (EntityProxyFactory
.IsProxyType(wrappedOwner
.Entity
.GetType()))
730 return EntityUtil
.ProxyMetadataIsUnavailable(wrappedOwner
.IdentityType
, argException
);
738 private IEnumerable
<AssociationEndMember
> GetAllTargetEnds(EntityType ownerEntityType
, EntitySet ownerEntitySet
)
740 foreach (AssociationSet assocSet
in MetadataHelper
.GetAssociationsForEntitySet(ownerEntitySet
))
742 EntityType end2EntityType
= ((AssociationType
)assocSet
.ElementType
).AssociationEndMembers
[1].GetEntityType();
743 if (end2EntityType
.IsAssignableFrom(ownerEntityType
))
745 yield return ((AssociationType
)assocSet
.ElementType
).AssociationEndMembers
[0];
747 // not "else" because of associations between the same entity sets
748 EntityType end1EntityType
= ((AssociationType
)assocSet
.ElementType
).AssociationEndMembers
[0].GetEntityType();
749 if (end1EntityType
.IsAssignableFrom(ownerEntityType
))
751 yield return ((AssociationType
)assocSet
.ElementType
).AssociationEndMembers
[1];
757 /// Retrieves the AssociationEndMembers that corespond to the target end of a relationship
758 /// given a specific CLR type that exists on the source end of a relationship
759 /// Note: this method can be very expensive if this RelationshipManager is not attached to an
760 /// ObjectContext because no OSpace Metadata is available
762 /// <param name="entityClrType">A CLR type that is on the source role of the relationship</param>
763 /// <returns>The OSpace EntityType that represents this CLR type</returns>
764 private IEnumerable
<AssociationEndMember
> GetAllTargetEnds(Type entityClrType
)
766 ObjectItemCollection objectItemCollection
= GetObjectItemCollection(WrappedOwner
);
768 IEnumerable
<AssociationType
> associations
= null;
769 if (objectItemCollection
!= null)
771 // Metadata is available
772 associations
= objectItemCollection
.GetItems
<AssociationType
>();
776 // No metadata is available, attempt to load the metadata on the fly to retrieve the AssociationTypes
777 associations
= ObjectItemCollection
.GetAllRelationshipTypesExpensiveWay(entityClrType
.Assembly
);
780 foreach (AssociationType association
in associations
)
782 // Check both ends for the presence of the source CLR type
783 RefType referenceType
= association
.AssociationEndMembers
[0].TypeUsage
.EdmType
as RefType
;
784 if (referenceType
!= null && referenceType
.ElementType
.ClrType
.IsAssignableFrom(entityClrType
))
786 // Return the target end
787 yield return association
.AssociationEndMembers
[1];
790 referenceType
= association
.AssociationEndMembers
[1].TypeUsage
.EdmType
as RefType
;
791 if (referenceType
!= null && referenceType
.ElementType
.ClrType
.IsAssignableFrom(entityClrType
))
793 // Return the target end
794 yield return association
.AssociationEndMembers
[0];
801 private bool VerifyRelationship(AssociationType relationship
, string sourceEndName
, bool throwOnError
)
803 IEntityWrapper wrappedOwner
= WrappedOwner
;
804 if (wrappedOwner
.Context
== null)
806 return true;// if not added to cache, can not decide- for now
809 EntityKey ownerKey
= null;
810 ownerKey
= wrappedOwner
.EntityKey
;
812 if (null == (object)ownerKey
)
814 return true; // if not added to cache, can not decide- for now
817 TypeUsage associationTypeUsage
;
818 AssociationSet association
= null;
819 bool isVerified
= true;
821 // First, get the CSpace association type from the relationship name, since the helper method looks up
822 // association set in the CSpace, since there is no Entity Container in the OSpace
823 if (wrappedOwner
.Context
.Perspective
.TryGetTypeByName(relationship
.FullName
, false/*ignoreCase*/, out associationTypeUsage
))
825 //Get the entity container first
826 EntityContainer entityContainer
= wrappedOwner
.Context
.MetadataWorkspace
.GetEntityContainer(
827 ownerKey
.EntityContainerName
, DataSpace
.CSpace
);
830 // Get the association set from the entity container, given the association type it refers to, and the entity set
831 // name that the source end refers to
832 association
= MetadataHelper
.GetAssociationsForEntitySetAndAssociationType(entityContainer
, ownerKey
.EntitySetName
,
833 (AssociationType
)associationTypeUsage
.EdmType
, sourceEndName
, out entitySet
);
835 if (association
== null)
839 throw EntityUtil
.NoRelationshipSetMatched(relationship
.FullName
);
848 Debug
.Assert(association
.AssociationSetEnds
[sourceEndName
].EntitySet
== entitySet
, "AssociationSetEnd does have the matching EntitySet");
855 /// Get the collection of a related entity using the specified
856 /// combination of relationship name, and target role name.
857 /// Only supports 2-way relationships.
859 /// <param name="relationshipName">Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not.</param>
860 /// <param name="targetRoleName">Name of the target role for the navigation. Indicates the direction of navigation across the relationship.</param>
861 /// <returns>Collection of entities of type TTargetEntity</returns>
862 public EntityCollection
<TTargetEntity
> GetRelatedCollection
<TTargetEntity
>(string relationshipName
, string targetRoleName
)
863 where TTargetEntity
: class
865 EntityCollection
<TTargetEntity
> collection
= GetRelatedEndInternal(PrependNamespaceToRelationshipName(relationshipName
), targetRoleName
) as EntityCollection
<TTargetEntity
>;
866 if (collection
== null)
868 throw EntityUtil
.ExpectedCollectionGotReference(typeof(TTargetEntity
).Name
, targetRoleName
, relationshipName
);
874 /// Get the entity reference of a related entity using the specified
875 /// combination of relationship name, and target role name.
876 /// Only supports 2-way relationships.
878 /// <param name="relationshipName">Name of the relationship in which targetRoleName is defined. Can be CSpace-qualified or not.</param>
879 /// <param name="targetRoleName">Name of the target role for the navigation. Indicates the direction of navigation across the relationship.</param>
880 /// <returns>Reference for related entity of type TTargetEntity</returns>
881 public EntityReference
<TTargetEntity
> GetRelatedReference
<TTargetEntity
>(string relationshipName
, string targetRoleName
)
882 where TTargetEntity
: class
884 EntityReference
<TTargetEntity
> reference
= GetRelatedEndInternal(PrependNamespaceToRelationshipName(relationshipName
), targetRoleName
) as EntityReference
<TTargetEntity
>;
885 if (reference
== null)
887 throw EntityUtil
.ExpectedReferenceGotCollection(typeof(TTargetEntity
).Name
, targetRoleName
, relationshipName
);
893 /// Gets collection or ref of related entity for a particular navigation.
895 /// <param name="navigation">
896 /// Describes the relationship and navigation direction
898 /// <param name="relationshipFixer">
899 /// Encapsulates information about the other end's type and cardinality,
900 /// and knows how to create the other end
902 /// <returns></returns>
903 internal RelatedEnd
GetRelatedEnd(RelationshipNavigation navigation
, IRelationshipFixer relationshipFixer
)
905 RelatedEnd relatedEnd
;
907 if (TryGetCachedRelatedEnd(navigation
.RelationshipName
, navigation
.To
, out relatedEnd
))
913 relatedEnd
= relationshipFixer
.CreateSourceEnd(navigation
, this);
914 Debug
.Assert(null != relatedEnd
, "CreateSourceEnd should always return a valid RelatedEnd");
921 /// Factory method for creating new related ends
923 /// <typeparam name="TSourceEntity">Type of the source end</typeparam>
924 /// <typeparam name="TTargetEntity">Type of the target end</typeparam>
925 /// <param name="navigation">RelationshipNavigation to be set on the new RelatedEnd</param>
926 /// <param name="sourceRoleMultiplicity">Multiplicity of the source role</param>
927 /// <param name="targetRoleMultiplicity">Multiplicity of the target role</param>
928 /// <param name="existingRelatedEnd">An existing related end to initialize instead of creating a new one</param>
929 /// <returns>new EntityCollection or EntityReference, depending on the specified target multiplicity</returns>
930 internal RelatedEnd CreateRelatedEnd
<TSourceEntity
, TTargetEntity
>(RelationshipNavigation navigation
, RelationshipMultiplicity sourceRoleMultiplicity
, RelationshipMultiplicity targetRoleMultiplicity
, RelatedEnd existingRelatedEnd
)
931 where TSourceEntity
: class
932 where TTargetEntity
: class
934 IRelationshipFixer relationshipFixer
= new RelationshipFixer
<TSourceEntity
, TTargetEntity
>(sourceRoleMultiplicity
, targetRoleMultiplicity
);
935 RelatedEnd relatedEnd
= null;
936 IEntityWrapper wrappedOwner
= WrappedOwner
;
937 switch (targetRoleMultiplicity
)
939 case RelationshipMultiplicity
.ZeroOrOne
:
940 case RelationshipMultiplicity
.One
:
941 if (existingRelatedEnd
!= null)
943 Debug
.Assert(wrappedOwner
.Context
== null || wrappedOwner
.MergeOption
== MergeOption
.NoTracking
, "Expected null context when initializing an existing related end");
944 existingRelatedEnd
.InitializeRelatedEnd(wrappedOwner
, navigation
, relationshipFixer
);
945 relatedEnd
= existingRelatedEnd
;
949 relatedEnd
= new EntityReference
<TTargetEntity
>(wrappedOwner
, navigation
, relationshipFixer
);
952 case RelationshipMultiplicity
.Many
:
953 if (existingRelatedEnd
!= null)
955 Debug
.Assert(wrappedOwner
.Context
== null || wrappedOwner
.MergeOption
== MergeOption
.NoTracking
, "Expected null context or NoTracking when initializing an existing related end");
956 existingRelatedEnd
.InitializeRelatedEnd(wrappedOwner
, navigation
, relationshipFixer
);
957 relatedEnd
= existingRelatedEnd
;
961 relatedEnd
= new EntityCollection
<TTargetEntity
>(wrappedOwner
, navigation
, relationshipFixer
);
965 throw EntityUtil
.InvalidEnumerationValue(typeof(RelationshipMultiplicity
), (int)targetRoleMultiplicity
);
968 // Verify that we can attach the context successfully before adding to our list of relationships
969 if (wrappedOwner
.Context
!= null)
971 relatedEnd
.AttachContext(wrappedOwner
.Context
, wrappedOwner
.MergeOption
);
974 EnsureRelationshipsInitialized();
975 _relationships
.Add(relatedEnd
);
981 /// Returns an enumeration of all the related ends. The enumeration
982 /// will be empty if the relationships have not been populated.
984 public IEnumerable
<IRelatedEnd
> GetAllRelatedEnds()
986 IEntityWrapper wrappedOwner
= WrappedOwner
;
988 EntityType entityType
;
989 if (wrappedOwner
.Context
!= null && wrappedOwner
.Context
.MetadataWorkspace
!= null && TryGetOwnerEntityType(out entityType
))
991 // For attached scenario:
992 // MEST: This returns RelatedEnds representing AssociationTypes which belongs to AssociationSets
993 // which have one end of EntitySet of wrappedOwner.Entity's EntitySet
994 Debug
.Assert(wrappedOwner
.EntityKey
!= null, "null entityKey on a attached entity");
995 EntitySet entitySet
= wrappedOwner
.Context
.GetEntitySet(wrappedOwner
.EntityKey
.EntitySetName
, wrappedOwner
.EntityKey
.EntityContainerName
);
996 foreach (AssociationEndMember endMember
in GetAllTargetEnds(entityType
, entitySet
))
998 yield return GetRelatedEnd(endMember
.DeclaringType
.FullName
, endMember
.Name
);
1003 // Disconnected scenario
1004 // MEST: this returns RelatedEnds representing all AssociationTypes which have one end of type of wrappedOwner.Entity's type.
1005 // The returned collection of RelatedEnds is a superset of RelatedEnds which can make sense for a single entity, because
1006 // an entity can belong only to one EntitySet. Note that the ideal would be to return the same collection as for attached scenario,
1007 // but it's not possible because we don't know to which EntitySet the wrappedOwner.Entity belongs.
1008 if (wrappedOwner
.Entity
!= null)
1010 foreach (AssociationEndMember endMember
in GetAllTargetEnds(wrappedOwner
.IdentityType
))
1012 yield return GetRelatedEnd(endMember
.DeclaringType
.FullName
, endMember
.Name
);
1019 [EditorBrowsable(EditorBrowsableState
.Never
)]
1021 [OnSerializingAttribute
]
1022 public void OnSerializing(StreamingContext context
)
1024 IEntityWrapper wrappedOwner
= WrappedOwner
;
1025 if (!(wrappedOwner
.Entity
is IEntityWithRelationships
))
1027 throw new InvalidOperationException(System
.Data
.Entity
.Strings
.RelatedEnd_CannotSerialize("RelationshipManager"));
1029 // If we are attached to a context we need to go fixup the detached entity key on any EntityReferences
1030 if (wrappedOwner
.Context
!= null && wrappedOwner
.MergeOption
!= MergeOption
.NoTracking
)
1032 foreach (RelatedEnd relatedEnd
in GetAllRelatedEnds())
1034 EntityReference reference
= relatedEnd
as EntityReference
;
1035 if (reference
!= null && reference
.EntityKey
!= null)
1037 reference
.DetachedEntityKey
= reference
.EntityKey
;
1047 internal bool HasRelationships
1049 get { return _relationships != null; }
1053 /// Add the rest of the graph, attached to this owner, to ObjectStateManager
1055 /// <param name="doAttach">if TRUE, the rest of the graph is attached directly as Unchanged
1056 /// without calling AcceptChanges()</param>
1057 internal void AddRelatedEntitiesToObjectStateManager(bool doAttach
)
1059 if (null != _relationships
)
1061 bool doCleanup
= true;
1064 // Create a copy of this list because with self references, the set of relationships can change
1065 foreach (RelatedEnd relatedEnd
in Relationships
)
1067 relatedEnd
.Include(/*addRelationshipAsUnchanged*/false, doAttach
);
1073 // If error happens, while attaching entity graph to context, clean-up
1074 // is done on the Owner entity and all its relating entities.
1077 IEntityWrapper wrappedOwner
= WrappedOwner
;
1078 Debug
.Assert(wrappedOwner
.Context
!= null && wrappedOwner
.Context
.ObjectStateManager
!= null, "Null context or ObjectStateManager");
1080 TransactionManager transManager
= wrappedOwner
.Context
.ObjectStateManager
.TransactionManager
;
1082 // The graph being attached is connected to graph already existing in the OSM only through "promoted" relationships
1083 // (relationships which originally existed only in OSM between key entries and entity entries but later were
1084 // "promoted" to normal relationships in EntityRef/Collection when the key entries were promoted).
1085 // The cleanup code traverse all the graph being added to the OSM, so we have to disconnect it from the graph already
1086 // existing in the OSM by degrading promoted relationships.
1087 wrappedOwner
.Context
.ObjectStateManager
.DegradePromotedRelationships();
1090 RemoveRelatedEntitiesFromObjectStateManager(wrappedOwner
);
1094 Debug
.Assert(doAttach
== (transManager
.IsAttachTracking
), "In attach the recovery collection should be not null");
1096 if (transManager
.IsAttachTracking
&&
1097 transManager
.PromotedKeyEntries
.TryGetValue(wrappedOwner
.Entity
, out entry
))
1099 // This is executed only in the cleanup code from ObjectContext.AttachTo()
1100 // If the entry was promoted in AttachTo(), it has to be degraded now instead of being deleted.
1101 entry
.DegradeEntry();
1105 RelatedEnd
.RemoveEntityFromObjectStateManager(wrappedOwner
);
1112 // Method is used to remove all entities and relationships, of a given entity
1113 // graph, from ObjectStateManager. This method is used when adding entity graph,
1114 // or a portion of it, raise exception.
1115 internal static void RemoveRelatedEntitiesFromObjectStateManager(IEntityWrapper wrappedEntity
)
1117 Debug
.Assert(wrappedEntity
!= null, "IEntityWrapper instance is null.");
1118 foreach (RelatedEnd relatedEnd
in wrappedEntity
.RelationshipManager
.Relationships
)
1120 // only some of the related ends may have gotten attached, so just skip the ones that weren't
1121 if (relatedEnd
.ObjectContext
!= null)
1123 Debug
.Assert(!relatedEnd
.UsingNoTracking
, "Shouldn't be touching the state manager with entities that were retrieved with NoTracking");
1124 relatedEnd
.Exclude();
1125 relatedEnd
.DetachContext();
1130 // Remove entity from its relationships and do cascade delete if required.
1131 // All removed relationships are marked for deletion and all cascade deleted
1132 // entitites are also marked for deletion.
1133 internal void RemoveEntityFromRelationships()
1135 if (null != _relationships
)
1137 foreach (RelatedEnd relatedEnd
in Relationships
)
1139 relatedEnd
.RemoveAll();
1145 /// Traverse the relationships and find all the dependent ends that contain FKs, then attempt
1146 /// to null all of those FKs.
1148 internal void NullAllFKsInDependentsForWhichThisIsThePrincipal()
1150 if (_relationships
!= null)
1152 // Build a list of the dependent RelatedEnds because with overlapping FKs we could
1153 // end up removing a relationship before we have suceeded in nulling all the FK values
1154 // for that relationship.
1155 var dependentEndsToProcess
= new List
<EntityReference
>();
1156 foreach (RelatedEnd relatedEnd
in Relationships
)
1158 if (relatedEnd
.IsForeignKey
)
1160 foreach (IEntityWrapper dependent
in relatedEnd
.GetWrappedEntities())
1162 var dependentEnd
= relatedEnd
.GetOtherEndOfRelationship(dependent
);
1163 if (dependentEnd
.IsDependentEndOfReferentialConstraint(checkIdentifying
: false))
1165 Debug
.Assert(dependentEnd
is EntityReference
, "Dependent end in FK relationship should always be a reference.");
1166 dependentEndsToProcess
.Add((EntityReference
)dependentEnd
);
1171 foreach (EntityReference dependentEnd
in dependentEndsToProcess
)
1173 dependentEnd
.NullAllForeignKeys();
1178 // Removes entity from its relationships.
1179 // Relationship entries are removed from ObjectStateManager if owner is in Added state
1180 // or when owner is "many" end of the relationship
1181 internal void DetachEntityFromRelationships(EntityState ownerEntityState
)
1183 if (null != _relationships
)
1185 foreach (RelatedEnd relatedEnd
in Relationships
)
1187 relatedEnd
.DetachAll(ownerEntityState
);
1192 //For a given relationship removes passed in entity from owners relationship
1193 internal void RemoveEntity(string toRole
, string relationshipName
, IEntityWrapper wrappedEntity
)
1195 Debug
.Assert(wrappedEntity
!= null, "IEntityWrapper instance is null.");
1196 RelatedEnd relatedEnd
;
1197 if (TryGetCachedRelatedEnd(relationshipName
, toRole
, out relatedEnd
))
1199 relatedEnd
.Remove(wrappedEntity
, false);
1203 internal void ClearRelatedEndWrappers()
1205 if (_relationships
!= null)
1207 foreach (IRelatedEnd relatedEnd
in Relationships
)
1209 ((RelatedEnd
)relatedEnd
).ClearWrappedValues();
1214 // Method used to retrieve properties from principal entities.
1215 // Parameter includeOwnValues means that values from current entity should be also added to "properties"
1216 // includeOwnValues is false only when this method is called from ObjectStateEntry.AcceptChanges()
1217 // Parmeter "visited" is a set containig entities which were already visited during traversing the graph.
1218 // If _owner already exists in the set, it means that there is a cycle in the graph of relationships with RI Constraints.
1219 internal void RetrieveReferentialConstraintProperties(out Dictionary
<string, KeyValuePair
<object, IntBox
>> properties
, HashSet
<object> visited
, bool includeOwnValues
)
1221 IEntityWrapper wrappedOwner
= WrappedOwner
;
1222 Debug
.Assert(wrappedOwner
.Entity
!= null);
1223 Debug
.Assert(visited
!= null);
1225 // Dictionary< propertyName, <propertyValue, counter>>
1226 properties
= new Dictionary
<string, KeyValuePair
<object, IntBox
>>();
1228 EntityKey ownerKey
= wrappedOwner
.EntityKey
;
1229 Debug
.Assert((object)ownerKey
!= null);
1231 // If the key is temporary, get values of referential constraint properties from principal entities
1232 if (ownerKey
.IsTemporary
)
1234 // Find property names which should be retrieved
1235 List
<string> propertiesToRetrieve
;
1236 bool propertiesToPropagateExist
; // not used
1238 this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve
, out propertiesToPropagateExist
, skipFK
: false);
1240 if (propertiesToRetrieve
!= null)
1242 // At first try to retrieve properties from entities which are in collections or references.
1243 // This is the most common scenario.
1244 // Only if properties couldn't be retrieved this way, try to retrieve properties from related stubs.
1246 if (_relationships
!= null)
1248 // Not using defensive copy here since RetrieveReferentialConstraintProperties should not cause change in underlying
1249 // _relationships collection.
1250 foreach (RelatedEnd relatedEnd
in _relationships
)
1252 // NOTE: If the following call throws UnableToRetrieveReferentialConstraintProperties,
1253 // it means that properties couldn't be found in indirectly related entities,
1254 // so it doesn't make sense to search for properties in directly related stubs,
1255 // so exception is not being caught here.
1256 relatedEnd
.RetrieveReferentialConstraintProperties(properties
, visited
);
1260 // Check if all properties were retrieved.
1261 // There are 3 scenarios in which not every expected property can be retrieved:
1262 // 1. There is no related entity from which the property is supposed to be retrieved.
1263 // 2. Related entity which supposed to contains the property doesn't have fixed entity key.
1264 // 3. Property should be retrieved from related key entry
1266 if (!CheckIfAllPropertiesWereRetrieved(properties
, propertiesToRetrieve
))
1268 // Properties couldn't be found in entities in collections or refrences.
1269 // Try to find missing properties in related key entries.
1270 // This process is slow but it is not a common case.
1271 EntityEntry entry
= wrappedOwner
.Context
.ObjectStateManager
.FindEntityEntry(ownerKey
);
1272 Debug
.Assert(entry
!= null, "Owner entry not found in the object state manager");
1273 entry
.RetrieveReferentialConstraintPropertiesFromKeyEntries(properties
);
1275 // Check again if all properties were retrieved.
1276 if (!CheckIfAllPropertiesWereRetrieved(properties
, propertiesToRetrieve
))
1278 throw EntityUtil
.UnableToRetrieveReferentialConstraintProperties();
1284 // 1. If key is temporary, properties from principal entities were retrieved above.
1285 // The other key properties are properties which are not Dependent end of some Referential Constraint.
1286 // 2. If key is not temporary and this method was not called from AcceptChanges() - all key values
1287 // of the current entity are added to 'properties'.
1288 if (!ownerKey
.IsTemporary
|| includeOwnValues
)
1290 // NOTE this part is never executed when the method is called from ObjectStateManager.AcceptChanges(),
1291 // so we don't try to "retrieve" properties from the the same (callers) entity.
1292 EntityEntry entry
= wrappedOwner
.Context
.ObjectStateManager
.FindEntityEntry(ownerKey
);
1293 Debug
.Assert(entry
!= null, "Owner entry not found in the object state manager");
1294 entry
.GetOtherKeyProperties(properties
);
1298 // properties dictionary contains name of property, its value and coutner saying how many times this property was retrieved from principal entities
1299 private static bool CheckIfAllPropertiesWereRetrieved(Dictionary
<string, KeyValuePair
<object, IntBox
>> properties
, List
<string> propertiesToRetrieve
)
1301 Debug
.Assert(properties
!= null);
1302 Debug
.Assert(propertiesToRetrieve
!= null);
1304 bool isSuccess
= true;
1306 List
<int> countersCopy
= new List
<int>();
1307 ICollection
<KeyValuePair
<object, IntBox
>> values
= properties
.Values
;
1309 // Create copy of counters (needed in case of failure)
1310 foreach (KeyValuePair
<object, IntBox
> valueCounterPair
in values
)
1312 countersCopy
.Add(valueCounterPair
.Value
.Value
);
1315 foreach (string name
in propertiesToRetrieve
)
1317 if (!properties
.ContainsKey(name
))
1323 KeyValuePair
<object, IntBox
> valueCounterPair
= properties
[name
];
1324 valueCounterPair
.Value
.Value
= valueCounterPair
.Value
.Value
- 1;
1325 if (valueCounterPair
.Value
.Value
< 0)
1332 // Check if all the coutners equal 0
1335 foreach (KeyValuePair
<object, IntBox
> valueCounterPair
in values
)
1337 if (valueCounterPair
.Value
.Value
!= 0)
1345 // Restore counters in case of failure
1348 IEnumerator
<int> enumerator
= countersCopy
.GetEnumerator();
1349 foreach (KeyValuePair
<object, IntBox
> valueCounterPair
in values
)
1351 enumerator
.MoveNext();
1352 valueCounterPair
.Value
.Value
= enumerator
.Current
;
1360 // Check consistency between properties of current entity and Principal entities
1361 // If some of Principal entities don't exist or some property cannot be checked - this is violation of RI Constraints
1362 internal void CheckReferentialConstraintProperties(EntityEntry ownerEntry
)
1364 Debug
.Assert(ownerEntry
!= null);
1366 List
<string> propertiesToRetrieve
; // used to check if the owner is a dependent end of some RI Constraint
1367 bool propertiesToPropagateExist
; // used to check if the owner is a principal end of some RI Constraint
1368 this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve
, out propertiesToPropagateExist
, skipFK
: false);
1370 if ((propertiesToRetrieve
!= null || propertiesToPropagateExist
) &&
1371 _relationships
!= null)
1373 // Not using defensive copy here since CheckReferentialConstraintProperties should not cause change in underlying
1374 // _relationships collection.
1375 foreach (RelatedEnd relatedEnd
in _relationships
)
1377 if (!relatedEnd
.CheckReferentialConstraintProperties(ownerEntry
))
1379 throw EntityUtil
.InconsistentReferentialConstraintProperties();
1389 // This method is required to maintain compatibility with the v1 binary serialization format.
1390 // In particular, it recreates a entity wrapper from the serialized owner.
1391 // Note that this is only expected to work for non-POCO entities, since serialization of POCO
1392 // entities will not result in serialization of the RelationshipManager or its related objects.
1393 [EditorBrowsable(EditorBrowsableState
.Never
)]
1396 public void OnDeserialized(StreamingContext context
)
1398 // Note that when deserializing, the context is always null since we never serialize
1399 // the context with the entity.
1400 _wrappedOwner
= EntityWrapperFactory
.WrapEntityUsingContext(_owner
, null);
1404 /// Searches the list of relationships for an entry with the specified relationship name and role names
1406 /// <param name="relationshipName">CSpace-qualified name of the relationship</param>
1407 /// <param name="targetRoleName">name of the target role</param>
1408 /// <param name="relatedEnd">the RelatedEnd if found, otherwise null</param>
1409 /// <returns>true if the entry found, false otherwise</returns>
1410 private bool TryGetCachedRelatedEnd(string relationshipName
, string targetRoleName
, out RelatedEnd relatedEnd
)
1413 if (null != _relationships
)
1415 // Not using defensive copy here since loop should not cause change in underlying
1416 // _relationships collection.
1417 foreach (RelatedEnd end
in _relationships
)
1419 RelationshipNavigation relNav
= end
.RelationshipNavigation
;
1420 if (relNav
.RelationshipName
== relationshipName
&& relNav
.To
== targetRoleName
)
1430 // Find properties which are Dependent/Principal ends of some referential constraint
1431 // Returned lists are never null.
1432 // NOTE This method will be removed when bug 505935 is solved
1433 // Returns true if any FK relationships were skipped so that they can be checked again after fixup
1434 internal bool FindNamesOfReferentialConstraintProperties(out List
<string> propertiesToRetrieve
, out bool propertiesToPropagateExist
, bool skipFK
)
1436 IEntityWrapper wrappedOwner
= WrappedOwner
;
1437 Debug
.Assert(wrappedOwner
.Entity
!= null);
1438 EntityKey ownerKey
= wrappedOwner
.EntityKey
;
1439 EntityUtil
.CheckEntityKeyNull(ownerKey
);
1441 propertiesToRetrieve
= null;
1442 propertiesToPropagateExist
= false;
1444 EntityUtil
.CheckContextNull(wrappedOwner
.Context
);
1445 EntitySet entitySet
= ownerKey
.GetEntitySet(wrappedOwner
.Context
.MetadataWorkspace
);
1446 Debug
.Assert(entitySet
!= null, "Unable to find entity set");
1448 // Get association types in which current entity's type is one of the ends.
1449 List
<AssociationSet
> associations
= MetadataHelper
.GetAssociationsForEntitySet(entitySet
);
1451 bool skippedFK
= false;
1452 // Find key property names which are part of referential integrity constraints
1453 foreach (AssociationSet association
in associations
)
1455 // NOTE ReferentialConstraints collection currently can contain 0 or 1 element
1456 if (skipFK
&& association
.ElementType
.IsForeignKey
)
1462 foreach (ReferentialConstraint constraint
in association
.ElementType
.ReferentialConstraints
)
1464 if (constraint
.ToRole
.TypeUsage
.EdmType
== entitySet
.ElementType
.GetReferenceType())
1466 // lazy creation of the list
1467 propertiesToRetrieve
= propertiesToRetrieve
?? new List
<string>();
1468 foreach (EdmProperty property
in constraint
.ToProperties
)
1470 propertiesToRetrieve
.Add(property
.Name
);
1473 // There are schemas, in which relationship has the same entitySet on both ends
1474 // that is why following 'if' statement is not inside of 'else' of previous 'if' statement
1475 if (constraint
.FromRole
.TypeUsage
.EdmType
== entitySet
.ElementType
.GetReferenceType())
1477 propertiesToPropagateExist
= true;
1486 /// Helper method to validate consistency of RelationshipManager instances
1488 /// <param name="entity">entity to compare against</param>
1489 /// <returns>True if entity is the owner of this RelationshipManager, otherwise false</returns>
1490 internal bool IsOwner(IEntityWrapper wrappedEntity
)
1492 IEntityWrapper wrappedOwner
= WrappedOwner
;
1493 Debug
.Assert(wrappedEntity
!= null, "IEntityWrapper instance is null.");
1494 return Object
.ReferenceEquals(wrappedEntity
.Entity
, wrappedOwner
.Entity
);
1498 /// Calls AttachContext on each RelatedEnd referenced by this manager.
1500 internal void AttachContextToRelatedEnds(ObjectContext context
, EntitySet entitySet
, MergeOption mergeOption
)
1502 Debug
.Assert(null != context
, "context");
1503 Debug
.Assert(null != entitySet
, "entitySet");
1504 if (null != _relationships
)
1506 // If GetAllRelatedEnds was called while the entity was not attached to the context
1507 // then _relationships may contain RelatedEnds that do not belong in based on the
1508 // entity set that the owner ultimately was attached to. This means that when attaching
1509 // we need to trim the list to get rid of those RelatedEnds.
1510 // It is possible that the RelatedEnds may have been obtained explicitly rather than through
1511 // GetAllRelatedEnds. If this is the case, then we prune anyway unless the RelatedEnd actually
1512 // has something attached to it, in which case we try to attach the context which will cause
1513 // an exception to be thrown. This is all a bit messy, but it's the best we could do given that
1514 // GetAllRelatedEnds was implemented in 3.5sp1 without taking MEST into account.
1515 // Note that the Relationships property makes a copy so we can modify the list while iterating
1516 foreach (RelatedEnd relatedEnd
in Relationships
)
1518 EdmType relationshipType
;
1519 RelationshipSet relationshipSet
;
1520 relatedEnd
.FindRelationshipSet(context
, entitySet
, out relationshipType
, out relationshipSet
);
1521 if (relationshipSet
!= null || !relatedEnd
.IsEmpty())
1523 relatedEnd
.AttachContext(context
, entitySet
, mergeOption
);
1527 _relationships
.Remove(relatedEnd
);
1534 /// Calls AttachContext on each RelatedEnd referenced by this manager and also on all the enties
1535 /// referenced by that related end.
1537 internal void ResetContextOnRelatedEnds(ObjectContext context
, EntitySet entitySet
, MergeOption mergeOption
)
1539 Debug
.Assert(null != context
, "context");
1540 Debug
.Assert(null != entitySet
, "entitySet");
1541 if (null != _relationships
)
1543 foreach (RelatedEnd relatedEnd
in Relationships
)
1545 relatedEnd
.AttachContext(context
, entitySet
, mergeOption
);
1546 foreach (IEntityWrapper wrappedEntity
in relatedEnd
.GetWrappedEntities())
1548 wrappedEntity
.ResetContext(context
, relatedEnd
.GetTargetEntitySetFromRelationshipSet(), mergeOption
);
1555 /// Calls DetachContext on each RelatedEnd referenced by this manager.
1557 internal void DetachContextFromRelatedEnds()
1559 if (null != _relationships
)
1561 // Not using defensive copy here since DetachContext should not cause change in underlying
1562 // _relationships collection.
1563 foreach (RelatedEnd relatedEnd
in _relationships
)
1565 relatedEnd
.DetachContext();
1570 // --------------------
1571 // Internal definitions
1572 // --------------------
1574 [Conditional("DEBUG")]
1575 internal void VerifyIsNotRelated()
1577 if (this._relationships
!= null)
1579 foreach (var r
in this._relationships
)
1583 Debug
.Assert(false, "Cannot change a state of a Deleted entity if the entity has other than deleted relationships with other entities.");