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
10 {
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;
21 
22     /// <summary>
23     /// Container for the lazily created relationship navigation
24     /// property objects (collections and refs).
25     /// </summary>
26     [Serializable]
27     public class RelationshipManager
28     {
29         // ------------
30         // Constructors
31         // ------------
32 
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.
RelationshipManager()36         private RelationshipManager()
37         {
38         }
39 
40         // ------
41         // Fields
42         // ------
43 
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.
48 
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;
52 
53         private List<RelatedEnd> _relationships;
54 
55         [NonSerialized]
56         private bool _nodeVisited;
57 
58         [NonSerialized]
59         private IEntityWrapper _wrappedOwner;
60 
61         // ----------
62         // Properties
63         // ----------
64 
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
71         {
72             get
73             {
74                 EnsureRelationshipsInitialized();
75                 return _relationships.ToArray();
76             }
77         }
78 
79         /// <summary>
80         /// Lazy initialization of the _relationships collection.
81         /// </summary>
EnsureRelationshipsInitialized()82         private void EnsureRelationshipsInitialized()
83         {
84             if (null == _relationships)
85             {
86                 _relationships = new List<RelatedEnd>();
87             }
88         }
89 
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
95         {
96             get
97             {
98                 return _nodeVisited;
99             }
100             set
101             {
102                 _nodeVisited = value;
103             }
104         }
105 
106         /// <summary>
107         /// Provides access to the entity that owns this manager in its wrapped form.
108         /// </summary>
109         internal IEntityWrapper WrappedOwner
110         {
111             get
112             {
113                 if (_wrappedOwner == null)
114                 {
115                     _wrappedOwner = EntityWrapperFactory.CreateNewWrapper(_owner, null);
116                 }
117                 return _wrappedOwner;
118             }
119         }
120 
121         // -------
122         // Methods
123         // -------
124 
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>
Create(IEntityWithRelationships owner)143         public static RelationshipManager Create(IEntityWithRelationships owner)
144         {
145             EntityUtil.CheckArgumentNull(owner, "owner");
146             RelationshipManager rm = new RelationshipManager();
147             rm._owner = owner;
148             return rm;
149         }
150 
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>
Create()158         internal static RelationshipManager Create()
159         {
160             return new RelationshipManager();
161         }
162 
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>
SetWrappedOwner(IEntityWrapper wrappedOwner, object expectedOwner)167         internal void SetWrappedOwner(IEntityWrapper wrappedOwner, object expectedOwner)
168         {
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))
174             {
175                 throw EntityUtil.InvalidRelationshipManagerOwner();
176             }
177 
178             if (null != _relationships)
179             {
180                 // Not using defensive copy here since SetWrappedOwner should not cause change in underlying
181                 // _relationships collection.
182                 foreach (RelatedEnd relatedEnd in _relationships)
183                 {
184                     relatedEnd.SetWrappedOwner(wrappedOwner);
185                 }
186             }
187         }
188 
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
208         {
209             EntityCollection<TTargetEntity> collection;
210             RelatedEnd relatedEnd;
TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd)211             TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd);
212 
213             if (existingRelatedEnd == null)
214             {
215                 if (relatedEnd != null)
216                 {
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;
224                 }
225                 else
226                 {
227                     RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName, sourceAccessor, targetAccessor);
228                     return CreateRelatedEnd<TSourceEntity, TTargetEntity>(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection<TTargetEntity>;
229                 }
230             }
231             else
232             {
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");
236 
237                 if (relatedEnd != null)
238                 {
239                     Debug.Assert(_relationships != null, "Expected _relationships to be non-null.");
240                     _relationships.Remove(relatedEnd);
241                 }
242 
243                 RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName, sourceAccessor, targetAccessor);
244                 collection = CreateRelatedEnd<TSourceEntity, TTargetEntity>(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.Many, existingRelatedEnd) as EntityCollection<TTargetEntity>;
245 
246                 if (collection != null)
247                 {
248                     bool doCleanup = true;
249                     try
250                     {
251                         RemergeCollections(relatedEnd as EntityCollection<TTargetEntity>, collection);
252                         doCleanup = false;
253                     }
254                     finally
255                     {
256                         // An error occured so we need to put the previous relatedEnd back into the RelationshipManager
257                         if (doCleanup && relatedEnd != null)
258                         {
259                             Debug.Assert(_relationships != null, "Expected _relationships to be non-null.");
260                             _relationships.Remove(collection);
261                             _relationships.Add(relatedEnd);
262                         }
263                     }
264                 }
265                 return collection;
266             }
267         }
268 
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
279         {
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
284 
285             int relatedEntityCount = 0;
286 
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())
290             {
291                 tempEntities.Add(wrappedEntity);
292             }
293 
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)
299             {
300                 bool requiresMerge = true;
301                 if (previousCollection != null)
302                 {
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))
306                     {
307                         relatedEntityCount++;
308                         requiresMerge = false;
309                     }
310                 }
311 
312                 if (requiresMerge)
313                 {
314                     // Remove and re-add the item to the collections to force fixup
315                     collection.Remove(wrappedEntity, false);
316                     collection.Add(wrappedEntity);
317                 }
318             }
319 
320             // Ensure that all of the items in the previousCollection are already in the new collection
321             if (previousCollection != null && relatedEntityCount != previousCollection.CountInternal)
322             {
323                 throw EntityUtil.CannotRemergeCollections();
324             }
325         }
326 
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
344         {
345             EntityReference<TTargetEntity> entityRef;
346             RelatedEnd relatedEnd;
347 
348             if (TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd))
349             {
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;
357             }
358             else
359             {
360                 RelationshipNavigation navigation = new RelationshipNavigation(relationshipName, sourceRoleName, targetRoleName, sourceAccessor, targetAccessor);
361                 return CreateRelatedEnd<TSourceEntity, TTargetEntity>(navigation, sourceRoleMultiplicity, RelationshipMultiplicity.One, existingRelatedEnd) as EntityReference<TTargetEntity>;
362             }
363         }
364 
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>
GetRelatedEnd(string navigationProperty, bool throwArgumentException = false)371         internal RelatedEnd GetRelatedEnd(string navigationProperty, bool throwArgumentException = false)
372         {
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");
378 
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))
383             {
384                 var message = System.Data.Entity.Strings.RelationshipManager_NavigationPropertyNotFound(navigationProperty);
385                 throw throwArgumentException ? (Exception)new ArgumentException(message) : (Exception)new InvalidOperationException(message);
386             }
387             NavigationProperty navProp = (NavigationProperty)member;
388             return GetRelatedEndInternal(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
389         }
390 
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>
GetRelatedEnd(string relationshipName, string targetRoleName)401         public IRelatedEnd GetRelatedEnd(string relationshipName, string targetRoleName)
402         {
403             return GetRelatedEndInternal(PrependNamespaceToRelationshipName(relationshipName), targetRoleName);
404         }
405 
406         // Internal version of GetRelatedEnd which returns the RelatedEnd as a RelatedEnd rather than an IRelatedEnd
GetRelatedEndInternal(string relationshipName, string targetRoleName)407         internal RelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName)
408         {
409             EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
410             EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName");
411 
412             IEntityWrapper wrappedOwner = WrappedOwner;
413             if (wrappedOwner.Context == null && wrappedOwner.RequiresRelationshipChangeTracking)
414             {
415                 throw new InvalidOperationException(System.Data.Entity.Strings.RelationshipManager_CannotGetRelatEndForDetachedPocoEntity);
416             }
417 
418             RelatedEnd relatedEnd = null;
419 
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))
423             {
424                 if (_relationships != null)
425                 {
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();
431                 }
432 
433                 if (relatedEnd == null && !EntityProxyFactory.TryGetAssociationTypeFromProxyInfo(wrappedOwner, relationshipName, targetRoleName, out associationType))
434                 {
435                     // If the end still cannot be found, throw an exception
436                     throw UnableToGetMetadata(WrappedOwner, relationshipName);
437                 }
438             }
439 
440             if (relatedEnd == null)
441             {
442                 Debug.Assert(associationType != null, "associationType is null");
443                 relatedEnd = GetRelatedEndInternal(relationshipName, targetRoleName, /*existingRelatedEnd*/ null, associationType);
444             }
445 
446             return relatedEnd;
447         }
448 
GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship)449         private RelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship)
450         {
451             return GetRelatedEndInternal(relationshipName, targetRoleName, existingRelatedEnd, relationship, true);
452         }
453 
GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship, bool throwOnError)454         private RelatedEnd GetRelatedEndInternal(string relationshipName, string targetRoleName, RelatedEnd existingRelatedEnd, AssociationType relationship, bool throwOnError)
455         {
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");
460 
461             AssociationEndMember sourceEnd;
462             AssociationEndMember targetEnd;
463             Debug.Assert(relationship.AssociationEndMembers.Count == 2, "Only 2-way relationships are currently supported");
464 
465             RelatedEnd result = null;
466 
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)
471             {
472                 sourceEnd = targetEnd;
473                 targetEnd = relationship.AssociationEndMembers[0];
474                 if (targetEnd.Identity != targetRoleName)
475                 {
476                     if (throwOnError)
477                     {
478                         throw EntityUtil.InvalidTargetRole(relationshipName, targetRoleName, "targetRoleName");
479                     }
480                     else
481                     {
482                         return result;
483                     }
484                 }
485             }
486             else
487             {
488                 sourceEnd = relationship.AssociationEndMembers[0];
489             }
490 
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)))
497             {
498                 if (throwOnError)
499                 {
500                     throw EntityUtil.OwnerIsNotSourceType(wrappedOwner.IdentityType.FullName, sourceType.FullName, sourceEnd.Name, relationshipName);
501                 }
502             }
503             else if (VerifyRelationship(relationship, sourceEnd.Name, throwOnError))
504             {
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);
507             }
508             return result;
509         }
510 
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
524         {
EntityUtil.CheckArgumentNull(relationshipName, R)525             EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
EntityUtil.CheckArgumentNull(targetRoleName, R)526             EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName");
EntityUtil.CheckArgumentNull(entityReference, R)527             EntityUtil.CheckArgumentNull(entityReference, "entityReference");
528 
529             if (entityReference.WrappedOwner.Entity != null)
530             {
531                 throw EntityUtil.ReferenceAlreadyInitialized();
532             }
533 
534             IEntityWrapper wrappedOwner = WrappedOwner;
535             if (wrappedOwner.Context != null && wrappedOwner.MergeOption != MergeOption.NoTracking)
536             {
537                 throw EntityUtil.RelationshipManagerAttached();
538             }
539 
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);
544 
545             RelatedEnd relatedEnd;
546             if (TryGetCachedRelatedEnd(relationshipName, targetRoleName, out relatedEnd))
547             {
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())
551                 {
552                     entityReference.InitializeWithValue(relatedEnd);
553                 }
554                 Debug.Assert(_relationships != null, "Expected _relationships to be non-null.");
555                 _relationships.Remove(relatedEnd);
556             }
557 
558             EntityReference<TTargetEntity> reference = GetRelatedEndInternal(relationshipName, targetRoleName, entityReference, relationship) as EntityReference<TTargetEntity>;
559             if (reference == null)
560             {
561                 throw EntityUtil.ExpectedReferenceGotCollection(typeof(TTargetEntity).Name, targetRoleName, relationshipName);
562             }
563         }
564 
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
578         {
EntityUtil.CheckArgumentNull(relationshipName, R)579             EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
EntityUtil.CheckArgumentNull(targetRoleName, R)580             EntityUtil.CheckArgumentNull(targetRoleName, "targetRoleName");
EntityUtil.CheckArgumentNull(entityCollection, R)581             EntityUtil.CheckArgumentNull(entityCollection, "entityCollection");
582 
583             if (entityCollection.WrappedOwner.Entity != null)
584             {
585                 throw EntityUtil.CollectionAlreadyInitialized();
586             }
587 
588             IEntityWrapper wrappedOwner = WrappedOwner;
589             if (wrappedOwner.Context != null && wrappedOwner.MergeOption != MergeOption.NoTracking)
590             {
591                 throw EntityUtil.CollectionRelationshipManagerAttached();
592             }
593 
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);
598 
599             EntityCollection<TTargetEntity> collection = GetRelatedEndInternal(relationshipName, targetRoleName, entityCollection, relationship) as EntityCollection<TTargetEntity>;
600             if (collection == null)
601             {
602                 throw EntityUtil.ExpectedCollectionGotReference(typeof(TTargetEntity).Name, targetRoleName, relationshipName);
603             }
604         }
605 
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>
PrependNamespaceToRelationshipName(string relationshipName)615         private string PrependNamespaceToRelationshipName(string relationshipName)
616         {
617             EntityUtil.CheckArgumentNull(relationshipName, "relationshipName");
618 
619             if (!relationshipName.Contains('.'))
620             {
621                 string identityName = WrappedOwner.IdentityType.FullName;
622                 ObjectItemCollection objectItemCollection = GetObjectItemCollection(WrappedOwner);
623                 EdmType entityType = null;
624                 if (objectItemCollection != null)
625                 {
626                     objectItemCollection.TryGetItem<EdmType>(identityName, out entityType);
627                 }
628                 else
629                 {
630                     Dictionary<string, EdmType> types = ObjectItemCollection.LoadTypesExpensiveWay(WrappedOwner.IdentityType.Assembly);
631                     if (types != null)
632                     {
633                         types.TryGetValue(identityName, out entityType);
634                     }
635 
636                 }
637                 ClrEntityType clrEntityType = entityType as ClrEntityType;
638                 if (clrEntityType != null)
639                 {
640                     string ns = clrEntityType.CSpaceNamespaceName;
641                     Debug.Assert(!string.IsNullOrEmpty(ns), "Expected non-empty namespace for type.");
642 
643                     return ns + "." + relationshipName;
644                 }
645             }
646             return relationshipName;
647         }
648 
649         /// <summary>
650         /// Trys to get an ObjectItemCollection and returns null if it can;t be found.
651         /// </summary>
GetObjectItemCollection(IEntityWrapper wrappedOwner)652         private static ObjectItemCollection GetObjectItemCollection(IEntityWrapper wrappedOwner)
653         {
654             if (wrappedOwner.Context != null && wrappedOwner.Context.MetadataWorkspace != null)
655             {
656                 return (ObjectItemCollection)wrappedOwner.Context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace);
657             }
658             return null;
659         }
660 
661         /// <summary>
662         /// Trys to get the EntityType metadata and returns false if it can't be found.
663         /// </summary>
TryGetOwnerEntityType(out EntityType entityType)664         private bool TryGetOwnerEntityType(out EntityType entityType)
665         {
666             DefaultObjectMappingItemCollection mappings;
667             Map map;
668             if (TryGetObjectMappingItemCollection(WrappedOwner, out mappings) &&
669                 mappings.TryGetMap(WrappedOwner.IdentityType.FullName, DataSpace.OSpace, out map))
670             {
671                 ObjectTypeMapping objectMap = (ObjectTypeMapping)map;
672                 if (Helper.IsEntityType(objectMap.EdmType))
673                 {
674                     entityType = (EntityType)objectMap.EdmType;
675                     return true;
676                 }
677             }
678 
679             entityType = null;
680             return false;
681         }
682 
683         /// <summary>
684         /// Trys to get an DefaultObjectMappingItemCollection and returns false if it can't be found.
685         /// </summary>
TryGetObjectMappingItemCollection(IEntityWrapper wrappedOwner, out DefaultObjectMappingItemCollection collection)686         private static bool TryGetObjectMappingItemCollection(IEntityWrapper wrappedOwner, out DefaultObjectMappingItemCollection collection)
687         {
688             if (wrappedOwner.Context != null && wrappedOwner.Context.MetadataWorkspace != null)
689             {
690                 collection = (DefaultObjectMappingItemCollection)wrappedOwner.Context.MetadataWorkspace.GetItemCollection(DataSpace.OCSpace);
691                 return collection != null;
692             }
693 
694             collection = null;
695             return false;
696         }
697 
698 
699 
TryGetRelationshipType(IEntityWrapper wrappedOwner, Type entityClrType, string relationshipName, out AssociationType associationType)700         internal static bool TryGetRelationshipType(IEntityWrapper wrappedOwner, Type entityClrType, string relationshipName, out AssociationType associationType)
701         {
702             ObjectItemCollection objectItemCollection = GetObjectItemCollection(wrappedOwner);
703             if (objectItemCollection != null)
704             {
705                 associationType = objectItemCollection.GetRelationshipType(entityClrType, relationshipName);
706             }
707             else
708             {
709                 associationType = ObjectItemCollection.GetRelationshipTypeExpensiveWay(entityClrType, relationshipName);
710             }
711 
712             return (associationType != null);
713         }
714 
GetRelationshipType(Type entityClrType, string relationshipName)715         private AssociationType GetRelationshipType(Type entityClrType, string relationshipName)
716         {
717             AssociationType associationType = null;
718             if (!TryGetRelationshipType(WrappedOwner, entityClrType, relationshipName, out associationType))
719             {
720                 throw UnableToGetMetadata(WrappedOwner, relationshipName);
721             }
722             return associationType;
723         }
724 
UnableToGetMetadata(IEntityWrapper wrappedOwner, string relationshipName)725         internal static Exception UnableToGetMetadata(IEntityWrapper wrappedOwner, string relationshipName)
726         {
727             ArgumentException argException = EntityUtil.UnableToFindRelationshipTypeInMetadata(relationshipName, "relationshipName");
728             if (EntityProxyFactory.IsProxyType(wrappedOwner.Entity.GetType()))
729             {
730                 return EntityUtil.ProxyMetadataIsUnavailable(wrappedOwner.IdentityType, argException);
731             }
732             else
733             {
734                 return argException;
735             }
736         }
737 
GetAllTargetEnds(EntityType ownerEntityType, EntitySet ownerEntitySet)738         private IEnumerable<AssociationEndMember> GetAllTargetEnds(EntityType ownerEntityType, EntitySet ownerEntitySet)
739         {
740             foreach (AssociationSet assocSet in MetadataHelper.GetAssociationsForEntitySet(ownerEntitySet))
741             {
742                 EntityType end2EntityType = ((AssociationType)assocSet.ElementType).AssociationEndMembers[1].GetEntityType();
743                 if (end2EntityType.IsAssignableFrom(ownerEntityType))
744                 {
745                     yield return ((AssociationType)assocSet.ElementType).AssociationEndMembers[0];
746                 }
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))
750                 {
751                     yield return ((AssociationType)assocSet.ElementType).AssociationEndMembers[1];
752                 }
753             }
754         }
755 
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>
GetAllTargetEnds(Type entityClrType)764         private IEnumerable<AssociationEndMember> GetAllTargetEnds(Type entityClrType)
765         {
766             ObjectItemCollection objectItemCollection = GetObjectItemCollection(WrappedOwner);
767 
768             IEnumerable<AssociationType> associations = null;
769             if (objectItemCollection != null)
770             {
771                 // Metadata is available
772                 associations = objectItemCollection.GetItems<AssociationType>();
773             }
774             else
775             {
776                 // No metadata is available, attempt to load the metadata on the fly to retrieve the AssociationTypes
777                 associations = ObjectItemCollection.GetAllRelationshipTypesExpensiveWay(entityClrType.Assembly);
778             }
779 
780             foreach (AssociationType association in associations)
781             {
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))
785                 {
786                     // Return the target end
787                     yield return association.AssociationEndMembers[1];
788                 }
789 
790                 referenceType = association.AssociationEndMembers[1].TypeUsage.EdmType as RefType;
791                 if (referenceType != null && referenceType.ElementType.ClrType.IsAssignableFrom(entityClrType))
792                 {
793                     // Return the target end
794                     yield return association.AssociationEndMembers[0];
795                 }
796             }
797             yield break;
798         }
799 
800 
VerifyRelationship(AssociationType relationship, string sourceEndName, bool throwOnError)801         private bool VerifyRelationship(AssociationType relationship, string sourceEndName, bool throwOnError)
802         {
803             IEntityWrapper wrappedOwner = WrappedOwner;
804             if (wrappedOwner.Context == null)
805             {
806                 return true;// if not added to cache, can not decide- for now
807             }
808 
809             EntityKey ownerKey = null;
810             ownerKey = wrappedOwner.EntityKey;
811 
812             if (null == (object)ownerKey)
813             {
814                 return true; // if not added to cache, can not decide- for now
815             }
816 
817             TypeUsage associationTypeUsage;
818             AssociationSet association = null;
819             bool isVerified = true;
820 
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))
824             {
825                 //Get the entity container first
826                 EntityContainer entityContainer = wrappedOwner.Context.MetadataWorkspace.GetEntityContainer(
827                     ownerKey.EntityContainerName, DataSpace.CSpace);
828                 EntitySet entitySet;
829 
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);
834 
835                 if (association == null)
836                 {
837                     if (throwOnError)
838                     {
839                         throw EntityUtil.NoRelationshipSetMatched(relationship.FullName);
840                     }
841                     else
842                     {
843                         isVerified = false;
844                     }
845                 }
846                 else
847                 {
848                     Debug.Assert(association.AssociationSetEnds[sourceEndName].EntitySet == entitySet, "AssociationSetEnd does have the matching EntitySet");
849                 }
850             }
851             return isVerified;
852         }
853 
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
864         {
865             EntityCollection<TTargetEntity> collection = GetRelatedEndInternal(PrependNamespaceToRelationshipName(relationshipName), targetRoleName) as EntityCollection<TTargetEntity>;
866             if (collection == null)
867             {
868                 throw EntityUtil.ExpectedCollectionGotReference(typeof(TTargetEntity).Name, targetRoleName, relationshipName);
869             }
870             return collection;
871         }
872 
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
883         {
884             EntityReference<TTargetEntity> reference = GetRelatedEndInternal(PrependNamespaceToRelationshipName(relationshipName), targetRoleName) as EntityReference<TTargetEntity>;
885             if (reference == null)
886             {
887                 throw EntityUtil.ExpectedReferenceGotCollection(typeof(TTargetEntity).Name, targetRoleName, relationshipName);
888             }
889             return reference;
890         }
891 
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>
GetRelatedEnd(RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)903         internal RelatedEnd GetRelatedEnd(RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
904         {
905             RelatedEnd relatedEnd;
906 
907             if (TryGetCachedRelatedEnd(navigation.RelationshipName, navigation.To, out relatedEnd))
908             {
909                 return relatedEnd;
910             }
911             else
912             {
913                 relatedEnd = relationshipFixer.CreateSourceEnd(navigation, this);
914                 Debug.Assert(null != relatedEnd, "CreateSourceEnd should always return a valid RelatedEnd");
915 
916                 return relatedEnd;
917             }
918         }
919 
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
933         {
934             IRelationshipFixer relationshipFixer = new RelationshipFixer<TSourceEntity, TTargetEntity>(sourceRoleMultiplicity, targetRoleMultiplicity);
935             RelatedEnd relatedEnd = null;
936             IEntityWrapper wrappedOwner = WrappedOwner;
937             switch (targetRoleMultiplicity)
938             {
939                 case RelationshipMultiplicity.ZeroOrOne:
940                 case RelationshipMultiplicity.One:
941                     if (existingRelatedEnd != null)
942                     {
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;
946                     }
947                     else
948                     {
949                         relatedEnd = new EntityReference<TTargetEntity>(wrappedOwner, navigation, relationshipFixer);
950                     }
951                     break;
952                 case RelationshipMultiplicity.Many:
953                     if (existingRelatedEnd != null)
954                     {
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;
958                     }
959                     else
960                     {
961                         relatedEnd = new EntityCollection<TTargetEntity>(wrappedOwner, navigation, relationshipFixer);
962                     }
963                     break;
964                 default:
965                     throw EntityUtil.InvalidEnumerationValue(typeof(RelationshipMultiplicity), (int)targetRoleMultiplicity);
966             }
967 
968             // Verify that we can attach the context successfully before adding to our list of relationships
969             if (wrappedOwner.Context != null)
970             {
971                 relatedEnd.AttachContext(wrappedOwner.Context, wrappedOwner.MergeOption);
972             }
973 
EnsureRelationshipsInitialized()974             EnsureRelationshipsInitialized();
_relationships.Add(relatedEnd)975             _relationships.Add(relatedEnd);
976 
977             return relatedEnd;
978         }
979 
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>
GetAllRelatedEnds()984         public IEnumerable<IRelatedEnd> GetAllRelatedEnds()
985         {
986             IEntityWrapper wrappedOwner = WrappedOwner;
987 
988             EntityType entityType;
989             if (wrappedOwner.Context != null && wrappedOwner.Context.MetadataWorkspace != null && TryGetOwnerEntityType(out entityType))
990             {
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))
997                 {
998                     yield return GetRelatedEnd(endMember.DeclaringType.FullName, endMember.Name);
999                 }
1000             }
1001             else
1002             {
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)
1009                 {
1010                     foreach (AssociationEndMember endMember in GetAllTargetEnds(wrappedOwner.IdentityType))
1011                     {
1012                         yield return GetRelatedEnd(endMember.DeclaringType.FullName, endMember.Name);
1013                     }
1014                 }
1015             }
1016             yield break;
1017         }
1018 
1019         [EditorBrowsable(EditorBrowsableState.Never)]
1020         [Browsable(false)]
1021         [OnSerializingAttribute]
OnSerializing(StreamingContext context)1022         public void OnSerializing(StreamingContext context)
1023         {
1024             IEntityWrapper wrappedOwner = WrappedOwner;
1025             if (!(wrappedOwner.Entity is IEntityWithRelationships))
1026             {
1027                 throw new InvalidOperationException(System.Data.Entity.Strings.RelatedEnd_CannotSerialize("RelationshipManager"));
1028             }
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)
1031             {
1032                 foreach (RelatedEnd relatedEnd in GetAllRelatedEnds())
1033                 {
1034                     EntityReference reference = relatedEnd as EntityReference;
1035                     if (reference != null && reference.EntityKey != null)
1036                     {
1037                         reference.DetachedEntityKey = reference.EntityKey;
1038                     }
1039                 }
1040             }
1041         }
1042 
1043         // ----------------
1044         // Internal Methods
1045         // ----------------
1046 
1047         internal bool HasRelationships
1048         {
1049             get { return _relationships != null; }
1050         }
1051 
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>
AddRelatedEntitiesToObjectStateManager(bool doAttach)1057         internal void AddRelatedEntitiesToObjectStateManager(bool doAttach)
1058         {
1059             if (null != _relationships)
1060             {
1061                 bool doCleanup = true;
1062                 try
1063                 {
1064                     // Create a copy of this list because with self references, the set of relationships can change
1065                     foreach (RelatedEnd relatedEnd in Relationships)
1066                     {
1067                         relatedEnd.Include(/*addRelationshipAsUnchanged*/false, doAttach);
1068                     }
1069                     doCleanup = false;
1070                 }
1071                 finally
1072                 {
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)
1076                     {
1077                         IEntityWrapper wrappedOwner = WrappedOwner;
1078                         Debug.Assert(wrappedOwner.Context != null && wrappedOwner.Context.ObjectStateManager != null, "Null context or ObjectStateManager");
1079 
1080                         TransactionManager transManager = wrappedOwner.Context.ObjectStateManager.TransactionManager;
1081 
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();
1088 
1089                         NodeVisited = true;
1090                         RemoveRelatedEntitiesFromObjectStateManager(wrappedOwner);
1091 
1092                         EntityEntry entry;
1093 
1094                         Debug.Assert(doAttach == (transManager.IsAttachTracking), "In attach the recovery collection should be not null");
1095 
1096                         if (transManager.IsAttachTracking &&
1097                             transManager.PromotedKeyEntries.TryGetValue(wrappedOwner.Entity, out entry))
1098                         {
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();
1102                         }
1103                         else
1104                         {
1105                             RelatedEnd.RemoveEntityFromObjectStateManager(wrappedOwner);
1106                         }
1107                     }
1108                 }
1109             }
1110         }
1111 
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.
RemoveRelatedEntitiesFromObjectStateManager(IEntityWrapper wrappedEntity)1115         internal static void RemoveRelatedEntitiesFromObjectStateManager(IEntityWrapper wrappedEntity)
1116         {
1117             Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1118             foreach (RelatedEnd relatedEnd in wrappedEntity.RelationshipManager.Relationships)
1119             {
1120                 // only some of the related ends may have gotten attached, so just skip the ones that weren't
1121                 if (relatedEnd.ObjectContext != null)
1122                 {
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();
1126                 }
1127             }
1128         }
1129 
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.
RemoveEntityFromRelationships()1133         internal void RemoveEntityFromRelationships()
1134         {
1135             if (null != _relationships)
1136             {
1137                 foreach (RelatedEnd relatedEnd in Relationships)
1138                 {
1139                     relatedEnd.RemoveAll();
1140                 }
1141             }
1142         }
1143 
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>
NullAllFKsInDependentsForWhichThisIsThePrincipal()1148         internal void NullAllFKsInDependentsForWhichThisIsThePrincipal()
1149         {
1150             if (_relationships != null)
1151             {
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)
1157                 {
1158                     if (relatedEnd.IsForeignKey)
1159                     {
1160                         foreach (IEntityWrapper dependent in relatedEnd.GetWrappedEntities())
1161                         {
1162                             var dependentEnd = relatedEnd.GetOtherEndOfRelationship(dependent);
1163                             if (dependentEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
1164                             {
1165                                 Debug.Assert(dependentEnd is EntityReference, "Dependent end in FK relationship should always be a reference.");
1166                                 dependentEndsToProcess.Add((EntityReference)dependentEnd);
1167                             }
1168                         }
1169                     }
1170                 }
1171                 foreach (EntityReference dependentEnd in dependentEndsToProcess)
1172                 {
1173                     dependentEnd.NullAllForeignKeys();
1174                 }
1175             }
1176         }
1177 
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
DetachEntityFromRelationships(EntityState ownerEntityState)1181         internal void DetachEntityFromRelationships(EntityState ownerEntityState)
1182         {
1183             if (null != _relationships)
1184             {
1185                 foreach (RelatedEnd relatedEnd in Relationships)
1186                 {
1187                     relatedEnd.DetachAll(ownerEntityState);
1188                 }
1189             }
1190         }
1191 
1192         //For a given relationship removes passed in entity from owners relationship
RemoveEntity(string toRole, string relationshipName, IEntityWrapper wrappedEntity)1193         internal void RemoveEntity(string toRole, string relationshipName, IEntityWrapper wrappedEntity)
1194         {
1195             Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1196             RelatedEnd relatedEnd;
1197             if (TryGetCachedRelatedEnd(relationshipName, toRole, out relatedEnd))
1198             {
1199                 relatedEnd.Remove(wrappedEntity, false);
1200             }
1201         }
1202 
ClearRelatedEndWrappers()1203         internal void ClearRelatedEndWrappers()
1204         {
1205             if (_relationships != null)
1206             {
1207                 foreach (IRelatedEnd relatedEnd in Relationships)
1208                 {
1209                     ((RelatedEnd)relatedEnd).ClearWrappedValues();
1210                 }
1211             }
1212         }
1213 
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.
RetrieveReferentialConstraintProperties(out Dictionary<string, KeyValuePair<object, IntBox>> properties, HashSet<object> visited, bool includeOwnValues)1219         internal void RetrieveReferentialConstraintProperties(out Dictionary<string, KeyValuePair<object, IntBox>> properties, HashSet<object> visited, bool includeOwnValues)
1220         {
1221             IEntityWrapper wrappedOwner = WrappedOwner;
1222             Debug.Assert(wrappedOwner.Entity != null);
1223             Debug.Assert(visited != null);
1224 
1225             // Dictionary< propertyName, <propertyValue, counter>>
1226             properties = new Dictionary<string, KeyValuePair<object, IntBox>>();
1227 
1228             EntityKey ownerKey = wrappedOwner.EntityKey;
1229             Debug.Assert((object)ownerKey != null);
1230 
1231             // If the key is temporary, get values of referential constraint properties from principal entities
1232             if (ownerKey.IsTemporary)
1233             {
1234                 // Find property names which should be retrieved
1235                 List<string> propertiesToRetrieve;
1236                 bool propertiesToPropagateExist; // not used
1237 
1238                 this.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToPropagateExist, skipFK: false);
1239 
1240                 if (propertiesToRetrieve != null)
1241                 {
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.
1245 
1246                     if (_relationships != null)
1247                     {
1248                         // Not using defensive copy here since RetrieveReferentialConstraintProperties should not cause change in underlying
1249                         // _relationships collection.
1250                         foreach (RelatedEnd relatedEnd in _relationships)
1251                         {
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);
1257                         }
1258                     }
1259 
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
1265 
1266                     if (!CheckIfAllPropertiesWereRetrieved(properties, propertiesToRetrieve))
1267                     {
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);
1274 
1275                         // Check again if all properties were retrieved.
1276                         if (!CheckIfAllPropertiesWereRetrieved(properties, propertiesToRetrieve))
1277                         {
1278                             throw EntityUtil.UnableToRetrieveReferentialConstraintProperties();
1279                         }
1280                     }
1281                 }
1282             }
1283 
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)
1289             {
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);
1295             }
1296         }
1297 
1298         // properties dictionary contains name of property, its value and coutner saying how many times this property was retrieved from principal entities
CheckIfAllPropertiesWereRetrieved(Dictionary<string, KeyValuePair<object, IntBox>> properties, List<string> propertiesToRetrieve)1299         private static bool CheckIfAllPropertiesWereRetrieved(Dictionary<string, KeyValuePair<object, IntBox>> properties, List<string> propertiesToRetrieve)
1300         {
1301             Debug.Assert(properties != null);
1302             Debug.Assert(propertiesToRetrieve != null);
1303 
1304             bool isSuccess = true;
1305 
1306             List<int> countersCopy = new List<int>();
1307             ICollection<KeyValuePair<object, IntBox>> values = properties.Values;
1308 
1309             // Create copy of counters (needed in case of failure)
1310             foreach (KeyValuePair<object, IntBox> valueCounterPair in values)
1311             {
1312                 countersCopy.Add(valueCounterPair.Value.Value);
1313             }
1314 
1315             foreach (string name in propertiesToRetrieve)
1316             {
1317                 if (!properties.ContainsKey(name))
1318                 {
1319                     isSuccess = false;
1320                     break;
1321                 }
1322 
1323                 KeyValuePair<object, IntBox> valueCounterPair = properties[name];
1324                 valueCounterPair.Value.Value = valueCounterPair.Value.Value - 1;
1325                 if (valueCounterPair.Value.Value < 0)
1326                 {
1327                     isSuccess = false;
1328                     break;
1329                 }
1330             }
1331 
1332             // Check if all the coutners equal 0
1333             if (isSuccess)
1334             {
1335                 foreach (KeyValuePair<object, IntBox> valueCounterPair in values)
1336                 {
1337                     if (valueCounterPair.Value.Value != 0)
1338                     {
1339                         isSuccess = false;
1340                         break;
1341                     }
1342                 }
1343             }
1344 
1345             // Restore counters in case of failure
1346             if (!isSuccess)
1347             {
1348                 IEnumerator<int> enumerator = countersCopy.GetEnumerator();
1349                 foreach (KeyValuePair<object, IntBox> valueCounterPair in values)
1350                 {
1351                     enumerator.MoveNext();
1352                     valueCounterPair.Value.Value = enumerator.Current;
1353                 }
1354             }
1355 
1356             return isSuccess;
1357         }
1358 
1359 
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
CheckReferentialConstraintProperties(EntityEntry ownerEntry)1362         internal void CheckReferentialConstraintProperties(EntityEntry ownerEntry)
1363         {
1364             Debug.Assert(ownerEntry != null);
1365 
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);
1369 
1370             if ((propertiesToRetrieve != null || propertiesToPropagateExist) &&
1371                 _relationships != null)
1372             {
1373                 // Not using defensive copy here since CheckReferentialConstraintProperties should not cause change in underlying
1374                 // _relationships collection.
1375                 foreach (RelatedEnd relatedEnd in _relationships)
1376                 {
1377                     if (!relatedEnd.CheckReferentialConstraintProperties(ownerEntry))
1378                     {
1379                         throw EntityUtil.InconsistentReferentialConstraintProperties();
1380                     }
1381                 }
1382             }
1383         }
1384 
1385         // ----------------
1386         // Private Methods
1387         // ----------------
1388 
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()]
OnDeserialized(StreamingContext context)1396         public void OnDeserialized(StreamingContext context)
1397         {
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);
1401         }
1402 
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>
TryGetCachedRelatedEnd(string relationshipName, string targetRoleName, out RelatedEnd relatedEnd)1410         private bool TryGetCachedRelatedEnd(string relationshipName, string targetRoleName, out RelatedEnd relatedEnd)
1411         {
1412             relatedEnd = null;
1413             if (null != _relationships)
1414             {
1415                 // Not using defensive copy here since loop should not cause change in underlying
1416                 // _relationships collection.
1417                 foreach (RelatedEnd end in _relationships)
1418                 {
1419                     RelationshipNavigation relNav = end.RelationshipNavigation;
1420                     if (relNav.RelationshipName == relationshipName && relNav.To == targetRoleName)
1421                     {
1422                         relatedEnd = end;
1423                         return true;
1424                     }
1425                 }
1426             }
1427             return false;
1428         }
1429 
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
FindNamesOfReferentialConstraintProperties(out List<string> propertiesToRetrieve, out bool propertiesToPropagateExist, bool skipFK)1434         internal bool FindNamesOfReferentialConstraintProperties(out List<string> propertiesToRetrieve, out bool propertiesToPropagateExist, bool skipFK)
1435         {
1436             IEntityWrapper wrappedOwner = WrappedOwner;
1437             Debug.Assert(wrappedOwner.Entity != null);
1438             EntityKey ownerKey = wrappedOwner.EntityKey;
1439             EntityUtil.CheckEntityKeyNull(ownerKey);
1440 
1441             propertiesToRetrieve = null;
1442             propertiesToPropagateExist = false;
1443 
1444             EntityUtil.CheckContextNull(wrappedOwner.Context);
1445             EntitySet entitySet = ownerKey.GetEntitySet(wrappedOwner.Context.MetadataWorkspace);
1446             Debug.Assert(entitySet != null, "Unable to find entity set");
1447 
1448             // Get association types in which current entity's type is one of the ends.
1449             List<AssociationSet> associations = MetadataHelper.GetAssociationsForEntitySet(entitySet);
1450 
1451             bool skippedFK = false;
1452             // Find key property names which are part of referential integrity constraints
1453             foreach (AssociationSet association in associations)
1454             {
1455                 // NOTE ReferentialConstraints collection currently can contain 0 or 1 element
1456                 if (skipFK && association.ElementType.IsForeignKey)
1457                 {
1458                     skippedFK = true;
1459                 }
1460                 else
1461                 {
1462                     foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
1463                     {
1464                         if (constraint.ToRole.TypeUsage.EdmType == entitySet.ElementType.GetReferenceType())
1465                         {
1466                             // lazy creation of the list
1467                             propertiesToRetrieve = propertiesToRetrieve ?? new List<string>();
1468                             foreach (EdmProperty property in constraint.ToProperties)
1469                             {
1470                                 propertiesToRetrieve.Add(property.Name);
1471                             }
1472                         }
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())
1476                         {
1477                             propertiesToPropagateExist = true;
1478                         }
1479                     }
1480                 }
1481             }
1482             return skippedFK;
1483         }
1484 
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>
IsOwner(IEntityWrapper wrappedEntity)1490         internal bool IsOwner(IEntityWrapper wrappedEntity)
1491         {
1492             IEntityWrapper wrappedOwner = WrappedOwner;
1493             Debug.Assert(wrappedEntity != null, "IEntityWrapper instance is null.");
1494             return Object.ReferenceEquals(wrappedEntity.Entity, wrappedOwner.Entity);
1495         }
1496 
1497         /// <summary>
1498         /// Calls AttachContext on each RelatedEnd referenced by this manager.
1499         /// </summary>
AttachContextToRelatedEnds(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)1500         internal void AttachContextToRelatedEnds(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
1501         {
1502             Debug.Assert(null != context, "context");
1503             Debug.Assert(null != entitySet, "entitySet");
1504             if (null != _relationships)
1505             {
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)
1517                 {
1518                     EdmType relationshipType;
1519                     RelationshipSet relationshipSet;
1520                     relatedEnd.FindRelationshipSet(context, entitySet, out relationshipType, out relationshipSet);
1521                     if (relationshipSet != null || !relatedEnd.IsEmpty())
1522                     {
1523                         relatedEnd.AttachContext(context, entitySet, mergeOption);
1524                     }
1525                     else
1526                     {
1527                         _relationships.Remove(relatedEnd);
1528                     }
1529                 }
1530             }
1531         }
1532 
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>
ResetContextOnRelatedEnds(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)1537         internal void ResetContextOnRelatedEnds(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
1538         {
1539             Debug.Assert(null != context, "context");
1540             Debug.Assert(null != entitySet, "entitySet");
1541             if (null != _relationships)
1542             {
1543                 foreach (RelatedEnd relatedEnd in Relationships)
1544                 {
1545                     relatedEnd.AttachContext(context, entitySet, mergeOption);
1546                     foreach (IEntityWrapper wrappedEntity in relatedEnd.GetWrappedEntities())
1547                     {
1548                         wrappedEntity.ResetContext(context, relatedEnd.GetTargetEntitySetFromRelationshipSet(), mergeOption);
1549                     }
1550                 }
1551             }
1552         }
1553 
1554         /// <summary>
1555         /// Calls DetachContext on each RelatedEnd referenced by this manager.
1556         /// </summary>
DetachContextFromRelatedEnds()1557         internal void DetachContextFromRelatedEnds()
1558         {
1559             if (null != _relationships)
1560             {
1561                 // Not using defensive copy here since DetachContext should not cause change in underlying
1562                 // _relationships collection.
1563                 foreach (RelatedEnd relatedEnd in _relationships)
1564                 {
1565                     relatedEnd.DetachContext();
1566                 }
1567             }
1568         }
1569 
1570         // --------------------
1571         // Internal definitions
1572         // --------------------
1573 
1574         [Conditional("DEBUG")]
VerifyIsNotRelated()1575         internal void VerifyIsNotRelated()
1576         {
1577             if (this._relationships != null)
1578             {
1579                 foreach (var r in this._relationships)
1580                 {
1581                     if (!r.IsEmpty())
1582                     {
1583                         Debug.Assert(false, "Cannot change a state of a Deleted entity if the entity has other than deleted relationships with other entities.");
1584                     }
1585                 }
1586             }
1587         }
1588     }
1589 }
1590