Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / DataClasses / EntityReference.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EntityReference.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Objects.DataClasses
11 {
12     using System.Collections.Generic;
13     using System.Data;
14     using System.Data.Common.Utils;
15     using System.Data.Metadata.Edm;
16     using System.Data.Objects.Internal;
17     using System.Diagnostics;
18     using System.Linq;
19     using System.Runtime.Serialization;
20
21     /// <summary>
22     /// Models a relationship end with multiplicity 1.
23     /// </summary>
24     [DataContract]
25     [Serializable]
26     public abstract class EntityReference : RelatedEnd
27     {
28         // ------
29         // Fields
30         // ------
31
32         // The following fields are serialized.  Adding or removing a serialized field is considered
33         // a breaking change.  This includes changing the field type or field name of existing
34         // serialized fields. If you need to make this kind of change, it may be possible, but it
35         // will require some custom serialization/deserialization code.
36
37         // The following field is valid only for detached EntityReferences, see EntityKey property for more details.
38         private EntityKey _detachedEntityKey = null;
39
40         // The following field is used to cache the FK value to the principal for FK relationships.
41         // It is okay to not serialize this field because it is only used when the entity is tracked.
42         // For a detached entity it can always be null and cause no problems.
43         [NonSerialized]
44         private EntityKey _cachedForeignKey;
45
46         // ------------
47         // Constructors
48         // ------------
49
50         /// <summary>
51         /// The default constructor is required for some serialization scenarios. It should not be used to 
52         /// create new EntityReferences. Use the GetRelatedReference or GetRelatedEnd methods on the RelationshipManager
53         /// class instead.
54         /// </summary>
55         internal EntityReference()
56         {
57         }
58
59         internal EntityReference(IEntityWrapper wrappedOwner, RelationshipNavigation navigation, IRelationshipFixer relationshipFixer)
60             : base(wrappedOwner, navigation, relationshipFixer)
61         {
62         }
63
64         /// <summary>
65         /// Returns the EntityKey of the target entity associated with this EntityReference.
66         /// 
67         /// Is non-null in the following scenarios:
68         /// (a) Entities are tracked by a context and an Unchanged or Added client-side relationships exists for this EntityReference's owner with the
69         ///     same RelationshipName and source role. This relationship could have been created explicitly by the user (e.g. by setting
70         ///     the EntityReference.Value, setting this property directly, or by calling EntityCollection.Add) or automatically through span queries.
71         /// (b) If the EntityKey was non-null before detaching an entity from the context, it will still be non-null after detaching, until any operation
72         ///     occurs that would set it to null, as described below.
73         /// (c) Entities are detached and the EntityKey is explicitly set to non-null by the user.
74         /// (d) Entity graph was created using a NoTracking query with full span
75         /// 
76         /// Is null in the following scenarios:
77         /// (a) Entities are tracked by a context but there is no Unchanged or Added client-side relationship for this EntityReference's owner with the
78         ///     same RelationshipName and source role.
79         /// (b) Entities are tracked by a context and a relationship exists, but the target entity has a temporary key (i.e. it is Added) or the key
80         ///     is one of the special keys
81         /// (c) Entities are detached and the relationship was explicitly created by the user.
82         /// </summary>   
83         [DataMember]
84         public EntityKey EntityKey
85         {
86             // This is the only scenario where it is valid to have a null Owner, so don't check it
87
88             get
89             {
90                 if (this.ObjectContext != null && !UsingNoTracking)
91                 {
92                     Debug.Assert(this.WrappedOwner.Entity != null, "Unexpected null Owner on EntityReference attached to a context");
93
94                     EntityKey attachedKey = null;
95
96                     // If this EntityReference contains an entity, look up the key on that object
97                     if (CachedValue.Entity != null)
98                     {
99                         // While processing an attach the owner may have a context while the target does not.  This means
100                         // that the target may gave an entity but not yet have an attached entity key.
101                         attachedKey = CachedValue.EntityKey;
102                         if (attachedKey != null && !IsValidEntityKeyType(attachedKey))
103                         {
104                             // don't return temporary or special keys from this property
105                             attachedKey = null;
106                         }
107                     }
108                     else
109                     {
110                         if (IsForeignKey)
111                         {
112                             // For dependent ends, return the value of the cached foreign key if it is not conceptually null
113                             if (IsDependentEndOfReferentialConstraint(false) && _cachedForeignKey != null)
114                             {
115                                 if (!ForeignKeyFactory.IsConceptualNullKey(_cachedForeignKey))
116                                 {
117                                     attachedKey = _cachedForeignKey;
118                                 }
119                             }
120                             else
121                             {
122                                 // Principal ends or ends that haven't been fixed up yet (i.e during Add/Attach) should use the DetachedEntityKey value
123                                 // that contains the last known value that was set
124                                 attachedKey = DetachedEntityKey;
125                             }
126                         }
127                         else
128                         {
129                             // There could still be an Added or Unchanged relationship with a stub entry
130                             EntityKey ownerKey = WrappedOwner.EntityKey;
131                             foreach (RelationshipEntry relationshipEntry in this.ObjectContext.ObjectStateManager.FindRelationshipsByKey(ownerKey))
132                             {
133                                 // We only care about the relationships that match the AssociationSet and source role for the owner of this EntityReference
134                                 if (relationshipEntry.State != EntityState.Deleted &&
135                                     relationshipEntry.IsSameAssociationSetAndRole((AssociationSet)RelationshipSet, (AssociationEndMember)this.FromEndProperty, ownerKey))
136                                 {
137                                     Debug.Assert(attachedKey == null, "Found more than one non-Deleted relationship for the same AssociationSet and source role");
138                                     attachedKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(ownerKey);
139                                     // key should never be temporary or special since it came from a key entry
140                                 }
141                             }
142                         }
143                     }
144                     Debug.Assert(attachedKey == null || IsValidEntityKeyType(attachedKey),
145                         "Unexpected temporary or special key");
146                     return attachedKey;
147                 }
148                 else
149                 {
150                     return DetachedEntityKey;
151                 }
152             }
153             set
154             {
155                 SetEntityKey(value, forceFixup: false);
156             }
157         }
158
159         internal void SetEntityKey(EntityKey value, bool forceFixup)
160         {
161             if (value != null && value == EntityKey && (ReferenceValue.Entity != null || (ReferenceValue.Entity == null && !forceFixup)))
162             {
163                 // "no-op" -- this is not really no-op in the attached case, because at a minimum we have to do a key lookup,
164                 // worst case we have to review all relationships for the owner entity
165                 // However, if we don't do this, we can get into a scenario where we are setting the key to the same thing it's already set to
166                 // and this could have side effects, especially with RI constraints and cascade delete. We don't want to delete something
167                 // and then add it back, if that deleting could have additional unexpected effects. Don't bother doing this check if value is
168                 // null, because EntityKey could be null even if there are Added/Unchanged relationships, if the target entity has a temporary key.
169                 // In that case, we still need to delete that existing relationship, so it's not a no-op
170                 return;
171             }
172
173             if (this.ObjectContext != null && !UsingNoTracking)
174             {
175                 Debug.Assert(this.WrappedOwner.Entity != null, "Unexpected null Owner on EntityReference attached to a context");
176
177                 // null is a valid value for the EntityKey, but temporary and special keys are not    
178                 // devnote: Can't check this on detached references because this property could be set to a temp key during deserialization,
179                 //          if the key hasn't finished deserializing yet.
180                 if (value != null && !IsValidEntityKeyType(value))
181                 {
182                     throw EntityUtil.CannotSetSpecialKeys();
183                 }
184
185                 if (value == null)
186                 {
187                     if (AttemptToNullFKsOnRefOrKeySetToNull())
188                     {
189                         DetachedEntityKey = null;
190                     }
191                     else
192                     {
193                         ReferenceValue = EntityWrapperFactory.NullWrapper;
194                     }
195                 }
196                 else
197                 {
198                     // Verify that the key has the right EntitySet for this RelationshipSet
199                     EntitySet targetEntitySet = value.GetEntitySet(ObjectContext.MetadataWorkspace);
200                     CheckRelationEntitySet(targetEntitySet);
201                     value.ValidateEntityKey(ObjectContext.MetadataWorkspace, targetEntitySet, true /*isArgumentException */, "value");
202
203                     ObjectStateManager manager = this.ObjectContext.ObjectStateManager;
204
205                     // If we already have an entry with this key, we just need to create a relationship with it
206                     bool addNewRelationship = false;
207                     // If we don't already have any matching entries for this key, we'll have to create a new entry
208                     bool addKeyEntry = false;
209                     EntityEntry targetEntry = manager.FindEntityEntry(value);
210                     if (targetEntry != null)
211                     {
212                         // If it's not a key entry, just use the entity to set this reference's Value
213                         if (!targetEntry.IsKeyEntry)
214                         {
215                             // Delegate to the Value property to clear any existing relationship
216                             // and to add the new one. This will fire the appropriate events and
217                             // ensure that the related ends are connected.
218
219                             // It has to be a TEntity since we already verified that the EntitySet is correct above
220                             this.ReferenceValue = targetEntry.WrappedEntity;
221                         }
222                         else
223                         {
224                             // if the existing entry is a key entry, we just need to
225                             // add a new relationship between the source entity and that key
226                             addNewRelationship = true;
227                         }
228                     }
229                     else
230                     {
231                         // no entry exists, so we'll need to add a key along with the relationship
232                         addKeyEntry = !IsForeignKey;
233                         addNewRelationship = true;
234                     }
235
236                     if (addNewRelationship)
237                     {
238                         EntityKey ownerKey = ValidateOwnerWithRIConstraints(targetEntry == null ? null : targetEntry.WrappedEntity, value, checkBothEnds: true);
239
240                         // Verify that the owner is in a valid state for adding a relationship
241                         ValidateStateForAdd(this.WrappedOwner);
242
243                         if (addKeyEntry)
244                         {
245                             manager.AddKeyEntry(value, targetEntitySet);
246                         }
247
248                         // First, clear any existing relationships
249                         manager.TransactionManager.EntityBeingReparented = WrappedOwner.Entity;
250                         try
251                         {
252                             ClearCollectionOrRef(null, null, /*doCascadeDelete*/ false);
253                         }
254                         finally
255                         {
256                             manager.TransactionManager.EntityBeingReparented = null;
257                         }
258
259                         // Then add the new one
260                         if (IsForeignKey)
261                         {
262                             DetachedEntityKey = value;
263                             // Update the FK values in this entity
264                             if (IsDependentEndOfReferentialConstraint(false))
265                             {
266                                 UpdateForeignKeyValues(WrappedOwner, value);
267                             }
268                         }
269                         else
270                         {
271                             RelationshipWrapper wrapper = new RelationshipWrapper((AssociationSet)RelationshipSet, RelationshipNavigation.From, ownerKey, RelationshipNavigation.To, value);
272                             // Add the relationship in the unchanged state if
273                             EntityState relationshipState = EntityState.Added;
274
275                             // If this is an unchanged/modified dependent end of a relationship and we are allowing the EntityKey to be set
276                             // create the relationship in the Unchanged state because the state must "match" the dependent end state
277                             if (!ownerKey.IsTemporary && IsDependentEndOfReferentialConstraint(false))
278                             {
279                                 relationshipState = EntityState.Unchanged;
280                             }
281                             manager.AddNewRelation(wrapper, relationshipState);
282                         }
283                     }
284                 }
285             }
286             else
287             {
288                 // Just set the field for detached object -- during Attach/Add we will make sure this value
289                 // is not in conflict if the EntityReference contains a real entity. We cannot always determine the
290                 // EntityKey for any real entity in the detached state, so we don't bother to do it here.
291                 DetachedEntityKey = value;
292             }
293         }
294
295         /// <summary>
296         /// This method is called when either the EntityKey or the Value property is set to null when it is
297         /// already null. For an FK association of a tracked entity the method will attempt to null FKs
298         /// thereby deleting the relationship. This may result in conceptual nulls being set.
299         /// </summary>
300         internal bool AttemptToNullFKsOnRefOrKeySetToNull()
301         {
302             if (ReferenceValue.Entity == null &&
303                 WrappedOwner.Entity != null &&
304                 WrappedOwner.Context != null &&
305                 !UsingNoTracking &&
306                 IsForeignKey)
307             {
308                 // For identifying relationships, we throw, since we cannot set primary key values to null, unless
309                 // the entity is in the Added state.
310                 if (WrappedOwner.ObjectStateEntry.State != EntityState.Added &&
311                     IsDependentEndOfReferentialConstraint(checkIdentifying: true))
312                 {
313                     throw EntityUtil.CannotChangeReferentialConstraintProperty();
314                 }
315
316                 // For unloaded FK relationships in the context we attempt to null FK values here, which will
317                 // delete the relationship.
318                 RemoveFromLocalCache(EntityWrapperFactory.NullWrapper, resetIsLoaded: true, preserveForeignKey: false);
319
320                 return true;
321             }
322             return false;
323         }
324
325         internal EntityKey AttachedEntityKey
326         {
327             get
328             {
329                 Debug.Assert(this.ObjectContext != null && !UsingNoTracking, "Should only need to access AttachedEntityKey property on attached EntityReferences");
330                 return this.EntityKey;
331             }
332         }
333
334         internal EntityKey DetachedEntityKey
335         {
336             get
337             {
338                 return _detachedEntityKey;
339             }
340             set
341             {
342                 _detachedEntityKey = value;
343             }
344         }
345
346         internal EntityKey CachedForeignKey
347         {
348             get
349             {
350                 return EntityKey ?? _cachedForeignKey;
351             }
352         }
353
354         internal void SetCachedForeignKey(EntityKey newForeignKey, EntityEntry source)
355         {
356             if (this.ObjectContext != null && this.ObjectContext.ObjectStateManager != null &&  // are we attached?
357                 source != null && // do we have an entry?
358                 _cachedForeignKey != null && !ForeignKeyFactory.IsConceptualNullKey(_cachedForeignKey) // do we have an fk?
359                 && _cachedForeignKey != newForeignKey) // is the FK different from the one that we already have?
360             {
361                 this.ObjectContext.ObjectStateManager.RemoveEntryFromForeignKeyIndex(_cachedForeignKey, source);
362             }
363             _cachedForeignKey = newForeignKey;
364         }
365
366         internal IEnumerable<EntityKey> GetAllKeyValues()
367         {
368             if (EntityKey != null)
369             {
370                 yield return EntityKey;
371             }
372
373             if (_cachedForeignKey != null)
374             {
375                 yield return _cachedForeignKey;
376             }
377
378             if (_detachedEntityKey != null)
379             {
380                 yield return _detachedEntityKey;
381             }
382         }
383
384         internal abstract IEntityWrapper CachedValue
385         {
386             get;
387         }
388
389         internal abstract IEntityWrapper ReferenceValue
390         {
391             get;
392             set;
393         }
394
395
396         internal EntityKey ValidateOwnerWithRIConstraints(IEntityWrapper targetEntity, EntityKey targetEntityKey, bool checkBothEnds)
397         {
398             EntityKey ownerKey = WrappedOwner.EntityKey;
399
400             // Check if Referential Constraints are violated
401             if ((object)ownerKey != null &&
402                 !ownerKey.IsTemporary &&
403                 IsDependentEndOfReferentialConstraint(checkIdentifying: true))
404             {
405                 Debug.Assert(CachedForeignKey != null || EntityKey == null, "CachedForeignKey should not be null if EntityKey is not null.");
406                 ValidateSettingRIConstraints(targetEntity,
407                                              targetEntityKey == null,
408                                              (this.CachedForeignKey != null && this.CachedForeignKey != targetEntityKey));
409             }
410             else if (checkBothEnds && targetEntity != null && targetEntity.Entity != null)
411             {
412                 EntityReference otherEnd = GetOtherEndOfRelationship(targetEntity) as EntityReference;
413                 if (otherEnd != null)
414                 {
415                     otherEnd.ValidateOwnerWithRIConstraints(WrappedOwner, ownerKey, checkBothEnds: false);
416                 }
417             }
418
419
420             return ownerKey;
421         }
422
423         internal void ValidateSettingRIConstraints(IEntityWrapper targetEntity, bool settingToNull, bool changingForeignKeyValue)
424         {
425             bool isNoTracking = targetEntity != null && targetEntity.MergeOption == MergeOption.NoTracking;
426
427             if (settingToNull ||                    // setting the principle to null
428                 changingForeignKeyValue ||          // existing key does not match incoming key
429                 (targetEntity != null &&
430                  !isNoTracking &&
431                                  (targetEntity.ObjectStateEntry == null ||  // setting to a detached principle
432                                  (EntityKey == null && targetEntity.ObjectStateEntry.State == EntityState.Deleted || // setting to a deleted principle
433                                  (CachedForeignKey == null && targetEntity.ObjectStateEntry.State == EntityState.Added)))))    // setting to an added principle
434             {
435                 throw EntityUtil.CannotChangeReferentialConstraintProperty();
436             }
437         }
438
439         /// <summary>
440         /// EntityReferences can only deferred load if they are empty
441         /// </summary>
442         internal override bool CanDeferredLoad
443         {
444             get
445             {
446                 return IsEmpty();
447             }
448         }
449
450         /// <summary>
451         /// Takes key values from the given principal entity and transfers them to the foreign key properties
452         /// of the dependant entry.  This method requires a context, but does not require that either
453         /// entity is in the context.  This allows it to work in NoTracking cases where we have the context
454         /// but we're not tracked by that context.
455         /// </summary>
456         /// <param name="dependentEntity">The entity into which foreign key values will be written</param>
457         /// <param name="principalEntity">The entity from which key values will be obtained</param>
458         /// <param name="changedFKs">If non-null, then keeps track of FKs that have already been set such that an exception can be thrown if we find conflicting values</param>
459         /// <param name="forceChange">If true, then the property setter is called even if FK values already match,
460         ///                           which causes the FK properties to be marked as modified.</param>
461         internal void UpdateForeignKeyValues(IEntityWrapper dependentEntity, IEntityWrapper principalEntity, Dictionary<int, object> changedFKs, bool forceChange)
462         {
463             Debug.Assert(dependentEntity.Entity != null, "dependentEntity.Entity == null");
464             Debug.Assert(principalEntity.Entity != null, "principalEntity.Entity == null");
465             Debug.Assert(this.IsForeignKey, "cannot update foreign key values if the relationship is not a FK");
466             ReferentialConstraint constraint = ((AssociationType)this.RelationMetadata).ReferentialConstraints[0];
467             Debug.Assert(constraint != null, "null constraint");
468
469             bool isUnchangedDependent = (object)WrappedOwner.EntityKey != null &&
470                                         !WrappedOwner.EntityKey.IsTemporary &&
471                                         IsDependentEndOfReferentialConstraint(checkIdentifying: true);
472
473             ObjectStateManager stateManager = ObjectContext.ObjectStateManager;
474             stateManager.TransactionManager.BeginForeignKeyUpdate(this);
475             try
476             {
477                 EntitySet principalEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[ToEndMember.Name].EntitySet;
478                 StateManagerTypeMetadata principalTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(principalEntity.IdentityType, principalEntitySet);
479
480                 EntitySet dependentEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[FromEndProperty.Name].EntitySet;
481                 StateManagerTypeMetadata dependentTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(dependentEntity.IdentityType, dependentEntitySet);
482
483                 var principalProps = constraint.FromProperties;
484                 int numValues = principalProps.Count;
485                 string[] keyNames = null;
486                 object[] values = null;
487                 if (numValues > 1)
488                 {
489                     keyNames = principalEntitySet.ElementType.KeyMemberNames;
490                     values = new object[numValues];
491                 }
492                 for (int i = 0; i < numValues; i++)
493                 {
494                     int principalOrdinal = principalTypeMetadata.GetOrdinalforOLayerMemberName(principalProps[i].Name);
495                     object value = principalTypeMetadata.Member(principalOrdinal).GetValue(principalEntity.Entity);
496                     int dependentOrdinal = dependentTypeMetadata.GetOrdinalforOLayerMemberName(constraint.ToProperties[i].Name);
497                     bool valueChanging = !ByValueEqualityComparer.Default.Equals(dependentTypeMetadata.Member(dependentOrdinal).GetValue(dependentEntity.Entity), value);
498                     if (forceChange || valueChanging)
499                     {
500                         if (isUnchangedDependent)
501                         {
502                             ValidateSettingRIConstraints(principalEntity, settingToNull: value == null, changingForeignKeyValue: valueChanging);
503                         }
504                         // If we're tracking FK values that have already been set, then compare the value we are about to set
505                         // to the value we previously set for this ordinal, if such a value exists.  If they don't match then
506                         // it means that we got conflicting FK values from two different PKs and we should throw.
507                         if (changedFKs != null)
508                         {
509                             object previouslySetValue;
510                             if (changedFKs.TryGetValue(dependentOrdinal, out previouslySetValue))
511                             {
512                                 if (!ByValueEqualityComparer.Default.Equals(previouslySetValue, value))
513                                 {
514                                     throw new InvalidOperationException(System.Data.Entity.Strings.Update_ReferentialConstraintIntegrityViolation);
515                                 }
516                             }
517                             else
518                             {
519                                 changedFKs[dependentOrdinal] = value;
520                             }
521                         }
522                         dependentEntity.SetCurrentValue(
523                             dependentEntity.ObjectStateEntry,
524                             dependentTypeMetadata.Member(dependentOrdinal),
525                             -1,
526                             dependentEntity.Entity,
527                             value);
528                     }
529
530                     if (numValues > 1)
531                     {
532                         int keyIndex = Array.IndexOf(keyNames, principalProps[i].Name);
533                         Debug.Assert(keyIndex >= 0 && keyIndex < numValues, "Could not find constraint prop name in entity set key names");
534                         values[keyIndex] = value;
535                     }
536                     else
537                     {
538                         SetCachedForeignKey(new EntityKey(principalEntitySet, value), dependentEntity.ObjectStateEntry);
539                     }
540                 }
541
542                 if (numValues > 1)
543                 {
544                     SetCachedForeignKey(new EntityKey(principalEntitySet, values), dependentEntity.ObjectStateEntry);
545                 }
546                 if (WrappedOwner.ObjectStateEntry != null)
547                 {
548                     stateManager.ForgetEntryWithConceptualNull(WrappedOwner.ObjectStateEntry, resetAllKeys: false);
549                 }
550             }
551             finally
552             {
553                 stateManager.TransactionManager.EndForeignKeyUpdate();
554             }
555         }
556
557         /// <summary>
558         /// Takes key values from the given principal key and transfers them to the foreign key properties
559         /// of the dependant entry.  This method requires a context, but does not require that either
560         /// entity or key is in the context.  This allows it to work in NoTracking cases where we have the context
561         /// but we're not tracked by that context.
562         /// </summary>
563         /// <param name="dependentEntity">The entity into which foreign key values will be written</param>
564         /// <param name="principalEntity">The key from which key values will be obtained</param>
565         internal void UpdateForeignKeyValues(IEntityWrapper dependentEntity, EntityKey principalKey)
566         {
567             Debug.Assert(dependentEntity.Entity != null, "dependentEntity.Entity == null");
568             Debug.Assert(principalKey != null, "principalKey == null");
569             Debug.Assert(!principalKey.IsTemporary, "Cannot update from a temp key");
570             Debug.Assert(this.IsForeignKey, "cannot update foreign key values if the relationship is not a FK");
571             ReferentialConstraint constraint = ((AssociationType)this.RelationMetadata).ReferentialConstraints[0];
572             Debug.Assert(constraint != null, "null constraint");
573
574             ObjectStateManager stateManager = ObjectContext.ObjectStateManager;
575             stateManager.TransactionManager.BeginForeignKeyUpdate(this);
576             try
577             {
578                 EntitySet dependentEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[FromEndProperty.Name].EntitySet;
579                 StateManagerTypeMetadata dependentTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(dependentEntity.IdentityType, dependentEntitySet);
580
581                 for (int i = 0; i < constraint.FromProperties.Count; i++)
582                 {
583                     object value = principalKey.FindValueByName(constraint.FromProperties[i].Name);
584                     int dependentOrdinal = dependentTypeMetadata.GetOrdinalforOLayerMemberName(constraint.ToProperties[i].Name);
585                     object currentValue = dependentTypeMetadata.Member(dependentOrdinal).GetValue(dependentEntity.Entity);
586                     if (!ByValueEqualityComparer.Default.Equals(currentValue, value))
587                     {
588                         dependentEntity.SetCurrentValue(
589                             dependentEntity.ObjectStateEntry,
590                             dependentTypeMetadata.Member(dependentOrdinal),
591                             -1,
592                             dependentEntity.Entity,
593                             value);
594                     }
595                 }
596
597                 SetCachedForeignKey(principalKey, dependentEntity.ObjectStateEntry);
598                 if (WrappedOwner.ObjectStateEntry != null)
599                 {
600                     stateManager.ForgetEntryWithConceptualNull(WrappedOwner.ObjectStateEntry, resetAllKeys: false);
601                 }
602             }
603             finally
604             {
605                 stateManager.TransactionManager.EndForeignKeyUpdate();
606             }
607         }
608
609         internal object GetDependentEndOfReferentialConstraint(object relatedValue)
610         {
611             return IsDependentEndOfReferentialConstraint(checkIdentifying: false) ?
612                 WrappedOwner.Entity :
613                 relatedValue;
614         }
615
616         internal bool NavigationPropertyIsNullOrMissing()
617         {
618             Debug.Assert(RelationshipNavigation != null, "null RelationshipNavigation");
619
620             return !TargetAccessor.HasProperty || WrappedOwner.GetNavigationPropertyValue(this) == null;
621         }
622
623         /// <summary>
624         /// Attempts to null all FKs associated with the dependent end of this relationship on this entity.
625         /// This may result in setting conceptual nulls if the FK is not nullable.
626         /// </summary>
627         internal void NullAllForeignKeys()
628         {
629             Debug.Assert(ObjectContext != null, "Nulling FKs only works when attached.");
630             Debug.Assert(IsForeignKey, "Cannot null FKs for independent associations.");
631
632             ObjectStateManager stateManager = ObjectContext.ObjectStateManager;
633             EntityEntry entry = WrappedOwner.ObjectStateEntry;
634             TransactionManager transManager = stateManager.TransactionManager;
635             if (!transManager.IsGraphUpdate && !transManager.IsAttachTracking && !transManager.IsRelatedEndAdd)
636             {
637                 ReferentialConstraint constraint = ((AssociationType)RelationMetadata).ReferentialConstraints.Single();
638                 if (TargetRoleName == constraint.FromRole.Name) // Only do this on the dependent end
639                 {
640                     if (transManager.IsDetaching)
641                     {
642                         // If the principal is being detached, then the dependent must be added back to the
643                         // dangling keys index.
644                         // Perf note: The dependent currently gets added when it is being detached and is then
645                         // removed again later in the process.  The code could be optimized to prevent this.
646                         Debug.Assert(entry != null, "State entry must exist while detaching.");
647                         EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(entry, this);
648                         if (foreignKey != null)
649                         {
650                             stateManager.AddEntryContainingForeignKeyToIndex(foreignKey, entry);
651                         }
652                     }
653                     else if (!ReferenceEquals(stateManager.EntityInvokingFKSetter, WrappedOwner.Entity) && !transManager.IsForeignKeyUpdate)
654                     {
655                         transManager.BeginForeignKeyUpdate(this);
656                         try
657                         {
658                             bool unableToNull = true;
659                             bool canSetModifiedProps = entry != null && (entry.State == EntityState.Modified || entry.State == EntityState.Unchanged);
660                             EntitySet dependentEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[FromEndProperty.Name].EntitySet;
661                             StateManagerTypeMetadata dependentTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(WrappedOwner.IdentityType, dependentEntitySet);
662
663                             for (int i = 0; i < constraint.FromProperties.Count; i++)
664                             {
665                                 string propertyName = constraint.ToProperties[i].Name;
666                                 int dependentOrdinal = dependentTypeMetadata.GetOrdinalforOLayerMemberName(propertyName);
667                                 StateManagerMemberMetadata member = dependentTypeMetadata.Member(dependentOrdinal);
668                                 
669                                 // This is a check for nullability in o-space. However, o-space nullability is not the
670                                 // same as nullability of the underlying type. In particular, one difference is that when
671                                 // attribute-based mapping is used then a property can be marked as not nullable in o-space
672                                 // even when the underlying CLR type is nullable. For such a case, we treat the property
673                                 // as if it were not nullable (since that's what we have shipped) even though we could
674                                 // technically set it to null.
675                                 if (member.ClrMetadata.Nullable)
676                                 {
677                                     // Only set the value to null if it is not already null.
678                                     if (member.GetValue(WrappedOwner.Entity) != null)
679                                     {
680                                         WrappedOwner.SetCurrentValue(
681                                             WrappedOwner.ObjectStateEntry,
682                                             dependentTypeMetadata.Member(dependentOrdinal),
683                                             -1,
684                                             WrappedOwner.Entity,
685                                             null);
686                                     }
687                                     else
688                                     {
689                                         // Given that the current value is null, this next check confirms that the original
690                                         // value is also null.  If it isn't, then we must make sure that the entity is marked
691                                         // as modified.
692                                         // This case can happen because fixup in the entity can set the FK to null while processing
693                                         // a RelatedEnd operation.  This will be detected by DetectChanges, but when performing
694                                         // RelatedEnd operations the user is not required to call DetectChanges.
695                                         if (canSetModifiedProps && WrappedOwner.ObjectStateEntry.OriginalValues.GetValue(dependentOrdinal) != null)
696                                         {
697                                             entry.SetModifiedProperty(propertyName);
698                                         }
699                                     }
700                                     unableToNull = false;
701                                 }
702                                 else if (canSetModifiedProps)
703                                 {
704                                     entry.SetModifiedProperty(propertyName);
705                                 }
706                             }
707                             if (unableToNull)
708                             {
709                                 // We were unable to null out the FK because all FK properties were non-nullable.
710                                 // We need to keep track of this state so that we treat the FK as null even though
711                                 // we were not able to null it.  This prevents the FK from being used for fixup and
712                                 // also causes an exception to be thrown if an attempt is made to commit in this state.
713
714                                 //We should only set a conceptual null if the entity is tracked
715                                 if (entry != null)
716                                 {
717                                     //The CachedForeignKey may be null if we are putting
718                                     //back a Conceptual Null as part of roll back
719                                     EntityKey realKey = CachedForeignKey;
720                                     if (realKey == null)
721                                     {
722                                         realKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(entry, this);
723                                     }
724
725                                     // Note that the realKey can still be null here for a situation where the key is marked not nullable
726                                     // in o-space and yet the underlying type is nullable and the entity has been added or attached with a null
727                                     // value for the property. This will cause SaveChanges to throw unless the entity is marked
728                                     // as deleted before SaveChanges is called, in which case we don't want to set a conceptual
729                                     // null here as the call might very well succeed in the database since, unless the FK is
730                                     // a concurrency token, the value we have for it is not used at all for the delete.
731                                     if (realKey != null)
732                                     {
733                                         SetCachedForeignKey(ForeignKeyFactory.CreateConceptualNullKey(realKey), entry);
734                                         stateManager.RememberEntryWithConceptualNull(entry);
735                                     }
736                                 }
737                             }
738                             else
739                             {
740                                 SetCachedForeignKey(null, entry);
741                             }
742                         }
743                         finally
744                         {
745                             transManager.EndForeignKeyUpdate();
746                         }
747                     }
748                 }
749             }
750         }
751     }
752 }