Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / DataClasses / RelationshipManager.cs
blobdd3a408546ddaa6deaf514ddc761a55535b5f950
1 //---------------------------------------------------------------------
2 // <copyright file="RelationshipManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
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;
19 using System.Linq;
20 using System.Runtime.Serialization;
22 /// <summary>
23 /// Container for the lazily created relationship navigation
24 /// property objects (collections and refs).
25 /// </summary>
26 [Serializable]
27 public class RelationshipManager
29 // ------------
30 // Constructors
31 // ------------
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()
40 // ------
41 // Fields
42 // ------
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;
55 [NonSerialized]
56 private bool _nodeVisited;
58 [NonSerialized]
59 private IEntityWrapper _wrappedOwner;
61 // ----------
62 // Properties
63 // ----------
65 /// <summary>
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.
69 /// </summary>
70 internal IEnumerable<RelatedEnd> Relationships
72 get
74 EnsureRelationshipsInitialized();
75 return _relationships.ToArray();
79 /// <summary>
80 /// Lazy initialization of the _relationships collection.
81 /// </summary>
82 private void EnsureRelationshipsInitialized()
84 if (null == _relationships)
86 _relationships = new List<RelatedEnd>();
90 /// <summary>
91 /// this flag is used to keep track of nodes which have
92 /// been visited. Currently used for Exclude operation.
93 /// </summary>
94 internal bool NodeVisited
96 get
98 return _nodeVisited;
102 _nodeVisited = value;
106 /// <summary>
107 /// Provides access to the entity that owns this manager in its wrapped form.
108 /// </summary>
109 internal IEntityWrapper WrappedOwner
113 if (_wrappedOwner == null)
115 _wrappedOwner = EntityWrapperFactory.CreateNewWrapper(_owner, null);
117 return _wrappedOwner;
121 // -------
122 // Methods
123 // -------
125 /// <summary>
126 /// Factory method to create a new RelationshipManager object.
127 ///
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.
133 ///
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.
139 /// </summary>
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();
147 rm._owner = owner;
148 return rm;
151 /// <summary>
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.
156 /// </summary>
157 /// <returns>The new RelationshipManager</returns>
158 internal static RelationshipManager Create()
160 return new RelationshipManager();
163 /// <summary>
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.
166 /// </summary>
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);
189 /// <summary>
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
192 /// </summary>
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");
223 return collection;
225 else
227 RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName, sourceAccessor, targetAccessor);
228 return CreateRelatedEnd<TSourceEntity, TTargetEntity>(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection<TTargetEntity>;
231 else
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);
252 doCleanup = false;
254 finally
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);
265 return collection;
269 /// <summary>
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
272 /// </summary>
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;
312 if (requiresMerge)
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();
327 /// <summary>
328 /// Get the entity reference of a related entity using the specified
329 /// combination of relationship name, source role name, and target role name
330 /// </summary>
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");
356 return entityRef;
358 else
360 RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName, sourceAccessor, targetAccessor);
361 return CreateRelatedEnd<TSourceEntity, TTargetEntity>(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.One, existingRelatedEnd) as EntityReference<TTargetEntity>;
365 /// <summary>
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.
368 /// </summary>
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);
380 EdmMember member;
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);
391 /// <summary>
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.
397 /// </summary>
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);
446 return relatedEnd;
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)
476 if (throwOnError)
478 throw EntityUtil.InvalidTargetRole(relationshipName, targetRoleName, "targetRoleName");
480 else
482 return result;
486 else
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)))
498 if (throwOnError)
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);
508 return result;
511 /// <summary>
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.
515 /// </summary>
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>
520 [Browsable(false)]
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);
565 /// <summary>
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.
569 /// </summary>
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>
574 [Browsable(false)]
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);
606 /// <summary>
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.
614 /// </summary>
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);
628 else
630 Dictionary<string, EdmType> types = ObjectItemCollection.LoadTypesExpensiveWay(WrappedOwner.IdentityType.Assembly);
631 if (types != null)
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;
649 /// <summary>
650 /// Trys to get an ObjectItemCollection and returns null if it can;t be found.
651 /// </summary>
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);
658 return null;
661 /// <summary>
662 /// Trys to get the EntityType metadata and returns false if it can't be found.
663 /// </summary>
664 private bool TryGetOwnerEntityType(out EntityType entityType)
666 DefaultObjectMappingItemCollection mappings;
667 Map map;
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;
675 return true;
679 entityType = null;
680 return false;
683 /// <summary>
684 /// Trys to get an DefaultObjectMappingItemCollection and returns false if it can't be found.
685 /// </summary>
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;
694 collection = null;
695 return false;
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);
707 else
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);
732 else
734 return 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];
756 /// <summary>
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
761 /// </summary>
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>();
774 else
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];
797 yield break;
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);
828 EntitySet entitySet;
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)
837 if (throwOnError)
839 throw EntityUtil.NoRelationshipSetMatched(relationship.FullName);
841 else
843 isVerified = false;
846 else
848 Debug.Assert(association.AssociationSetEnds[sourceEndName].EntitySet == entitySet, "AssociationSetEnd does have the matching EntitySet");
851 return isVerified;
854 /// <summary>
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.
858 /// </summary>
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);
870 return collection;
873 /// <summary>
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.
877 /// </summary>
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);
889 return reference;
892 /// <summary>
893 /// Gets collection or ref of related entity for a particular navigation.
894 /// </summary>
895 /// <param name="navigation">
896 /// Describes the relationship and navigation direction
897 /// </param>
898 /// <param name="relationshipFixer">
899 /// Encapsulates information about the other end's type and cardinality,
900 /// and knows how to create the other end
901 /// </param>
902 /// <returns></returns>
903 internal RelatedEnd GetRelatedEnd(RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
905 RelatedEnd relatedEnd;
907 if (TryGetCachedRelatedEnd(navigation.RelationshipName, navigation.To, out relatedEnd))
909 return relatedEnd;
911 else
913 relatedEnd = relationshipFixer.CreateSourceEnd(navigation, this);
914 Debug.Assert(null != relatedEnd, "CreateSourceEnd should always return a valid RelatedEnd");
916 return relatedEnd;
920 /// <summary>
921 /// Factory method for creating new related ends
922 /// </summary>
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;
947 else
949 relatedEnd = new EntityReference<TTargetEntity>(wrappedOwner, navigation, relationshipFixer);
951 break;
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;
959 else
961 relatedEnd = new EntityCollection<TTargetEntity>(wrappedOwner, navigation, relationshipFixer);
963 break;
964 default:
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);
977 return relatedEnd;
980 /// <summary>
981 /// Returns an enumeration of all the related ends. The enumeration
982 /// will be empty if the relationships have not been populated.
983 /// </summary>
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);
1001 else
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);
1016 yield break;
1019 [EditorBrowsable(EditorBrowsableState.Never)]
1020 [Browsable(false)]
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;
1043 // ----------------
1044 // Internal Methods
1045 // ----------------
1047 internal bool HasRelationships
1049 get { return _relationships != null; }
1052 /// <summary>
1053 /// Add the rest of the graph, attached to this owner, to ObjectStateManager
1054 /// </summary>
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);
1069 doCleanup = false;
1071 finally
1073 // If error happens, while attaching entity graph to context, clean-up
1074 // is done on the Owner entity and all its relating entities.
1075 if (doCleanup)
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();
1089 NodeVisited = true;
1090 RemoveRelatedEntitiesFromObjectStateManager(wrappedOwner);
1092 EntityEntry entry;
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();
1103 else
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();
1144 /// <summary>
1145 /// Traverse the relationships and find all the dependent ends that contain FKs, then attempt
1146 /// to null all of those FKs.
1147 /// </summary>
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))
1319 isSuccess = false;
1320 break;
1323 KeyValuePair<object, IntBox> valueCounterPair = properties[name];
1324 valueCounterPair.Value.Value = valueCounterPair.Value.Value - 1;
1325 if (valueCounterPair.Value.Value < 0)
1327 isSuccess = false;
1328 break;
1332 // Check if all the coutners equal 0
1333 if (isSuccess)
1335 foreach (KeyValuePair<object, IntBox> valueCounterPair in values)
1337 if (valueCounterPair.Value.Value != 0)
1339 isSuccess = false;
1340 break;
1345 // Restore counters in case of failure
1346 if (!isSuccess)
1348 IEnumerator<int> enumerator = countersCopy.GetEnumerator();
1349 foreach (KeyValuePair<object, IntBox> valueCounterPair in values)
1351 enumerator.MoveNext();
1352 valueCounterPair.Value.Value = enumerator.Current;
1356 return isSuccess;
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();
1385 // ----------------
1386 // Private Methods
1387 // ----------------
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)]
1394 [Browsable(false)]
1395 [OnDeserialized()]
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);
1403 /// <summary>
1404 /// Searches the list of relationships for an entry with the specified relationship name and role names
1405 /// </summary>
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)
1412 relatedEnd = null;
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)
1422 relatedEnd = end;
1423 return true;
1427 return false;
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)
1458 skippedFK = true;
1460 else
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;
1482 return skippedFK;
1485 /// <summary>
1486 /// Helper method to validate consistency of RelationshipManager instances
1487 /// </summary>
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);
1497 /// <summary>
1498 /// Calls AttachContext on each RelatedEnd referenced by this manager.
1499 /// </summary>
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);
1525 else
1527 _relationships.Remove(relatedEnd);
1533 /// <summary>
1534 /// Calls AttachContext on each RelatedEnd referenced by this manager and also on all the enties
1535 /// referenced by that related end.
1536 /// </summary>
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);
1554 /// <summary>
1555 /// Calls DetachContext on each RelatedEnd referenced by this manager.
1556 /// </summary>
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)
1581 if (!r.IsEmpty())
1583 Debug.Assert(false, "Cannot change a state of a Deleted entity if the entity has other than deleted relationships with other entities.");