1 //---------------------------------------------------------------------
2 // <copyright file="EntityEntry.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
10 {
11     using System.Collections;
12     using System.Collections.Generic;
13     using System.ComponentModel;
14     using System.Data.Common;
15     using System.Data.Common.Utils;
16     using System.Data.Metadata.Edm;
17     using System.Data.Objects.DataClasses;
18     using System.Data.Objects.Internal;
19     using System.Diagnostics;
20     using System.Linq;
21 
22     internal sealed class EntityEntry : ObjectStateEntry
23     {
24         private StateManagerTypeMetadata _cacheTypeMetadata;
25         private EntityKey _entityKey;       // !null if IsKeyEntry or Entity
26         private IEntityWrapper _wrappedEntity;     // Contains null entity if IsKeyEntry
27 
28         // entity entry change tracking
29         private BitArray _modifiedFields;  // only and always exists if state is Modified or after Delete() on Modified
30         private List<StateManagerValue> _originalValues; // only exists if _modifiedFields has a true-bit
31 
32         // The _originalComplexObjects should always contain references to the values of complex objects which are "original"
33         // at the moment of calling GetComplexObjectSnapshot().  They are used to get original scalar values from _originalValues
34         // and to check if complex object instance was changed.
35         private Dictionary<object, Dictionary<int, object>> _originalComplexObjects; // used for POCO Complex Objects change tracking
36 
37         private bool _requiresComplexChangeTracking;
38         private bool _requiresScalarChangeTracking;
39         private bool _requiresAnyChangeTracking;
40 
41         #region RelationshipEnd fields
42 
43         /// <summary>
44         /// Singlely-linked list of RelationshipEntry.
45         /// One of the ends in the RelationshipEntry must equal this.EntityKey
46         /// </summary>
47         private RelationshipEntry _headRelationshipEnds;
48 
49         /// <summary>
50         /// Number of RelationshipEntry in the _relationshipEnds list.
51         /// </summary>
52         private int _countRelationshipEnds;
53 
54         #endregion
55 
56         #region Constructors
57 
58         // EntityEntry
EntityEntry(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata, EntityState state)59         internal EntityEntry(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache,
60             StateManagerTypeMetadata typeMetadata, EntityState state)
61             : base(cache, entitySet, state)
62         {
63             Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null.");
64             Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null.");
65             Debug.Assert(typeMetadata != null, "typeMetadata cannot be null.");
66             Debug.Assert(entitySet != null, "entitySet cannot be null.");
67             Debug.Assert((null == (object)entityKey) || (entityKey.EntitySetName == entitySet.Name), "different entitySet");
68 
69             _wrappedEntity = wrappedEntity;
70             _cacheTypeMetadata = typeMetadata;
71             _entityKey = entityKey;
72 
73             wrappedEntity.ObjectStateEntry = this;
74 
75             SetChangeTrackingFlags();
76         }
77 
78         /// <summary>
79         /// Looks at the type of entity represented by this entry and sets flags defining the type of
80         /// change tracking that will be needed. The three main types are:
81         /// - Pure POCO objects or non-change-tracking proxies which need DetectChanges for everything.
82         /// - Entities derived from EntityObject which don't need DetectChanges at all.
83         /// - Change tracking proxies, which only need DetectChanges for complex properties.
84         /// </summary>
SetChangeTrackingFlags()85         private void SetChangeTrackingFlags()
86         {
87             _requiresScalarChangeTracking = Entity != null && !(Entity is IEntityWithChangeTracker);
88 
89             _requiresComplexChangeTracking = Entity != null &&
90                                              (_requiresScalarChangeTracking ||
91                                               (WrappedEntity.IdentityType != Entity.GetType() &&
92                                                _cacheTypeMetadata.Members.Any(m => m.IsComplex)));
93 
94             _requiresAnyChangeTracking = Entity != null &&
95                                          (!(Entity is IEntityWithRelationships) ||
96                                           _requiresComplexChangeTracking ||
97                                           _requiresScalarChangeTracking);
98         }
99 
100         // KeyEntry
EntityEntry(EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata)101         internal EntityEntry(EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata)
102             : base(cache, entitySet, EntityState.Unchanged)
103         {
104             Debug.Assert((object)entityKey != null, "entityKey cannot be null.");
105             Debug.Assert(entitySet != null, "extent cannot be null.");
106             Debug.Assert(typeMetadata != null, "typeMetadata cannot be null.");
107             Debug.Assert(entityKey.EntitySetName == entitySet.Name, "different entitySet");
108 
109             _wrappedEntity = EntityWrapperFactory.NullWrapper;
110             _entityKey = entityKey;
111             _cacheTypeMetadata = typeMetadata;
112 
113             SetChangeTrackingFlags();
114         }
115 
116         #endregion
117 
118         #region Public members
119 
120         override public bool IsRelationship
121         {
122             get
123             {
124                 ValidateState();
125                 return false;
126             }
127         }
128 
129         override public object Entity
130         {
131             get
132             {
133                 ValidateState();
134                 return _wrappedEntity.Entity;
135             }
136         }
137 
138         /// <summary>
139         /// The EntityKey associated with the ObjectStateEntry
140         /// </summary>
141         override public EntityKey EntityKey
142         {
143             get
144             {
145                 ValidateState();
146                 return _entityKey;
147             }
148             internal set
149             {
150                 _entityKey = value;
151             }
152         }
153 
154         internal IEnumerable<Tuple<AssociationSet, ReferentialConstraint>> ForeignKeyDependents
155         {
156             get
157             {
158                 foreach (var foreignKey in ((EntitySet)EntitySet).ForeignKeyDependents)
159                 {
160                     AssociationSet associationSet = foreignKey.Item1;
161                     ReferentialConstraint constraint = foreignKey.Item2;
162                     EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole);
163                     if (dependentType.IsAssignableFrom(_cacheTypeMetadata.DataRecordInfo.RecordType.EdmType))
164                     {
165                         yield return foreignKey;
166                     }
167                 }
168             }
169         }
170 
171         internal IEnumerable<Tuple<AssociationSet, ReferentialConstraint>> ForeignKeyPrincipals
172         {
173             get
174             {
175                 foreach (var foreignKey in ((EntitySet)EntitySet).ForeignKeyPrincipals)
176                 {
177                     AssociationSet associationSet = foreignKey.Item1;
178                     ReferentialConstraint constraint = foreignKey.Item2;
179                     EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.FromRole);
180                     if (dependentType.IsAssignableFrom(_cacheTypeMetadata.DataRecordInfo.RecordType.EdmType))
181                     {
182                         yield return foreignKey;
183                     }
184                 }
185             }
186         }
187 
GetModifiedProperties()188         override public IEnumerable<string> GetModifiedProperties()
189         {
190             ValidateState();
191             if (EntityState.Modified == this.State && _modifiedFields != null)
192             {
193                 Debug.Assert(null != _modifiedFields, "null fields");
194                 for (int i = 0; i < _modifiedFields.Count; i++)
195                 {
196                     if (_modifiedFields[i])
197                     {
198                         yield return (GetCLayerName(i, _cacheTypeMetadata));
199                     }
200                 }
201             }
202         }
203 
204         /// <summary>
205         /// Marks specified property as modified.
206         /// </summary>
207         /// <param name="propertyName">This API recognizes the names in terms of OSpace</param>
208         /// <exception cref="InvalidOperationException">If State is not Modified or Unchanged</exception>
209         ///
SetModifiedProperty(string propertyName)210         override public void SetModifiedProperty(string propertyName)
211         {
212             int ordinal = ValidateAndGetOrdinalForProperty(propertyName, "SetModifiedProperty");
213 
214             Debug.Assert(State == EntityState.Unchanged || State == EntityState.Modified, "ValidateAndGetOrdinalForProperty should have thrown.");
215 
216             if (EntityState.Unchanged == State)
217             {
218                 State = EntityState.Modified;
219                 _cache.ChangeState(this, EntityState.Unchanged, State);
220             }
221 
222             SetModifiedPropertyInternal(ordinal);
223         }
224 
SetModifiedPropertyInternal(int ordinal)225         internal void SetModifiedPropertyInternal(int ordinal)
226         {
227             if (null == _modifiedFields)
228             {
229                 _modifiedFields = new BitArray(GetFieldCount(_cacheTypeMetadata));
230             }
231 
232             _modifiedFields[ordinal] = true;
233         }
234 
ValidateAndGetOrdinalForProperty(string propertyName, string methodName)235         private int ValidateAndGetOrdinalForProperty(string propertyName, string methodName)
236         {
237             EntityUtil.CheckArgumentNull(propertyName, "propertyName");
238 
239             // Throw for detached entities
240             ValidateState();
241 
242             if (IsKeyEntry)
243             {
244                 throw EntityUtil.CannotModifyKeyEntryState();
245             }
246 
247             int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(propertyName);
248             if (ordinal == -1)
249             {
250                 throw EntityUtil.InvalidModifiedPropertyName(propertyName);
251             }
252 
253             if (State == EntityState.Added || State == EntityState.Deleted)
254             {
255                 // Threw for detached above; this throws for Added or Deleted entities
256                 throw EntityUtil.SetModifiedStates(methodName);
257             }
258 
259             return ordinal;
260         }
261 
262         /// <summary>
263         /// Rejects any changes made to the property with the given name since the property was last loaded,
264         /// attached, saved, or changes were accepted. The orginal value of the property is stored and the
265         /// property will no longer be marked as modified.
266         /// </summary>
267         /// <remarks>
268         /// If the result is that no properties of the entity are marked as modified, then the entity will
269         /// be marked as Unchanged.
270         /// Changes to properties can only rejected for entities that are in the Modified or Unchanged state.
271         /// Calling this method for entities in other states (Added, Deleted, or Detached) will result in
272         /// an exception being thrown.
273         /// Rejecting changes to properties of an Unchanged entity or unchanged properties of a Modifed
274         /// is a no-op.
275         /// </remarks>
276         /// <param name="propertyName">The name of the property to change.</param>
RejectPropertyChanges(string propertyName)277         override public void RejectPropertyChanges(string propertyName)
278         {
279             int ordinal = ValidateAndGetOrdinalForProperty(propertyName, "RejectPropertyChanges");
280 
281             if (State == EntityState.Unchanged)
282             {
283                 // No-op for unchanged entities since all properties must be unchanged.
284                 return;
285             }
286 
287             Debug.Assert(State == EntityState.Modified, "Should have handled all other states above.");
288 
289             if (_modifiedFields != null && _modifiedFields[ordinal])
290             {
291                 // Reject the change by setting the current value to the original value
292                 DetectChangesInComplexProperties();
293                 var originalValue = GetOriginalEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.OriginalReadonly);
294                 SetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, originalValue);
295                 _modifiedFields[ordinal] = false;
296 
297                 // Check if any properties remain modified. If any are modified, then we leave the entity state as Modified and we are done.
298                 for (int i = 0; i < _modifiedFields.Count; i++)
299                 {
300                     if (_modifiedFields[i])
301                     {
302                         return;
303                     }
304                 }
305 
306                 // No properties are modified so change the state of the entity to Unchanged.
307                 ChangeObjectState(EntityState.Unchanged);
308             }
309         }
310 
311         /// <summary>
312         /// Original values of entity
313         /// </summary>
314         /// <param></param>
315         /// <returns> DbDataRecord </returns>
316         [DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
317         override public DbDataRecord OriginalValues
318         {
319             get
320             {
321                 return InternalGetOriginalValues(true /*readOnly*/);
322             }
323         }
324 
325         /// <summary>
326         /// Gets a version of the OriginalValues property that can be updated
327         /// </summary>
GetUpdatableOriginalValues()328         public override OriginalValueRecord GetUpdatableOriginalValues()
329         {
330             return (OriginalValueRecord)InternalGetOriginalValues(false /*readOnly*/);
331         }
332 
InternalGetOriginalValues(bool readOnly)333         private DbDataRecord InternalGetOriginalValues(bool readOnly)
334         {
335             ValidateState();
336             if (this.State == EntityState.Added)
337             {
338                 throw EntityUtil.OriginalValuesDoesNotExist();
339             }
340 
341             if (this.IsKeyEntry)
342             {
343                 throw EntityUtil.CannotAccessKeyEntryValues();
344             }
345             else
346             {
347                 DetectChangesInComplexProperties();
348 
349                 if (readOnly)
350                 {
351                     return new ObjectStateEntryDbDataRecord(this, _cacheTypeMetadata, _wrappedEntity.Entity);
352                 }
353                 else
354                 {
355                     return new ObjectStateEntryOriginalDbUpdatableDataRecord_Public(this, _cacheTypeMetadata, _wrappedEntity.Entity, s_EntityRoot);
356                 }
357             }
358         }
359 
DetectChangesInComplexProperties()360         private void DetectChangesInComplexProperties()
361         {
362             if (this.RequiresScalarChangeTracking)
363             {
364                 // POCO: the snapshot of complex objects has to be updated
365                 // without chaning state of the entry or marking properties as modified.
366                 // The IsOriginalValuesGetter is used in EntityMemberChanged to skip the state transition.
367                 // The snapshot has to be updated in case the complex object instance was changed (not only scalar values).
368                 this.ObjectStateManager.TransactionManager.BeginOriginalValuesGetter();
369                 try
370                 {
371                     // Process only complex objects. The method will not change the state of the entry.
372                     this.DetectChangesInProperties(true /*detectOnlyComplexProperties*/);
373                 }
374                 finally
375                 {
376                     this.ObjectStateManager.TransactionManager.EndOriginalValuesGetter();
377                 }
378             }
379         }
380 
381         /// <summary>
382         /// Current values of entity/ DataRow
383         /// </summary>
384         /// <param></param>
385         /// <returns> DbUpdatableDataRecord </returns>
386         [DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
387         override public CurrentValueRecord CurrentValues
388         {
389             get
390             {
391                 ValidateState();
392                 if (this.State == EntityState.Deleted)
393                 {
394                     throw EntityUtil.CurrentValuesDoesNotExist();
395                 }
396 
397                 if (this.IsKeyEntry)
398                 {
399                     throw EntityUtil.CannotAccessKeyEntryValues();
400                 }
401                 else
402                 {
403                     return new ObjectStateEntryDbUpdatableDataRecord(this, _cacheTypeMetadata, _wrappedEntity.Entity);
404                 }
405             }
406         }
407 
Delete()408         override public void Delete()
409         {
410             // doFixup flag is used for Cache and Collection & Ref consistency
411             // When some entity is deleted if "doFixup" is true then Delete method
412             // calls the Collection & Ref code to do the necessary fix-ups.
413             // "doFixup" equals to False is only called from EntityCollection & Ref code
414             Delete(/*doFixup*/true);
415         }
416 
417         /// <summary>
418         /// API to accept the current values as original values and  mark the entity as Unchanged.
419         /// </summary>
420         /// <param></param>
421         /// <returns></returns>
AcceptChanges()422         override public void AcceptChanges()
423         {
424             ValidateState();
425 
426             if (ObjectStateManager.EntryHasConceptualNull(this))
427             {
428                 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectContext_CommitWithConceptualNull);
429             }
430 
431             Debug.Assert(!this.IsKeyEntry || State == EntityState.Unchanged, "Key ObjectStateEntries must always be unchanged.");
432 
433             switch (State)
434             {
435                 case EntityState.Deleted:
436                     this.CascadeAcceptChanges();
437                     // Current entry could be already detached if this is relationship entry and if one end of relationship was a KeyEntry
438                     if (_cache != null)
439                     {
440                         _cache.ChangeState(this, EntityState.Deleted, EntityState.Detached);
441                     }
442                     break;
443                 case EntityState.Added:
444                     // If this entry represents an entity, perform key fixup.
445                     Debug.Assert(Entity != null, "Non-relationship entries should have a non-null entity.");
446                     Debug.Assert((object)_entityKey != null, "All entities in the state manager should have a non-null EntityKey.");
447                     Debug.Assert(_entityKey.IsTemporary, "All entities in the Added state should have a temporary EntityKey.");
448 
449                     // Retrieve referential constraint properties from Principal entities (possibly recursively)
450                     // and check referential constraint properties in the Dependent entities (1 level only)
451                     // We have to do this before fixing up keys to preserve v1 behavior around when stubs are promoted.
452                     // However, we can't check FKs until after fixup, which happens after key fixup.  Therefore,
453                     // we keep track of whether or not we need to go check again after fixup.  Also, checking for independent associations
454                     // happens using RelationshipEntries, while checking for constraints in FKs has to use the graph.
455                     bool skippedFKs = RetrieveAndCheckReferentialConstraintValuesInAcceptChanges();
456 
457                     _cache.FixupKey(this);
458 
459                     _modifiedFields = null;
460                     _originalValues = null;
461                     _originalComplexObjects = null;
462                     State = EntityState.Unchanged;
463 
464                     if (skippedFKs)
465                     {
466                         // If we skipped checking constraints on any FK relationships above, then
467                         // do it now on the fixuped RelatedEnds.
468                         RelationshipManager.CheckReferentialConstraintProperties(this);
469                     }
470 
471                     _wrappedEntity.TakeSnapshot(this);
472 
473                     break;
474                 case EntityState.Modified:
475                     _cache.ChangeState(this, EntityState.Modified, EntityState.Unchanged);
476                     _modifiedFields = null;
477                     _originalValues = null;
478                     _originalComplexObjects = null;
479                     State = EntityState.Unchanged;
480                     _cache.FixupReferencesByForeignKeys(this);
481 
482                     // Need to check constraints here because fixup could have got us into an invalid state
483                     RelationshipManager.CheckReferentialConstraintProperties(this);
484                     _wrappedEntity.TakeSnapshot(this);
485 
486                     break;
487                 case EntityState.Unchanged:
488                     break;
489             }
490         }
491 
SetModified()492         override public void SetModified()
493         {
494             ValidateState();
495 
496             if (this.IsKeyEntry)
497             {
498                 throw EntityUtil.CannotModifyKeyEntryState();
499             }
500             else
501             {
502                 if (EntityState.Unchanged == State)
503                 {
504                     State = EntityState.Modified;
505                     _cache.ChangeState(this, EntityState.Unchanged, State);
506                 }
507                 else if (EntityState.Modified != State)
508                 {
509                     throw EntityUtil.SetModifiedStates("SetModified");
510                 }
511             }
512         }
513 
514         override public RelationshipManager RelationshipManager
515         {
516             get
517             {
518                 ValidateState();
519                 if (IsKeyEntry)
520                 {
521                     throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_RelationshipAndKeyEntriesDoNotHaveRelationshipManagers);
522                 }
523                 if (WrappedEntity.Entity == null)
524                 {
525                     throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotGetRelationshipManagerForDetachedPocoEntity);
526                 }
527                 return WrappedEntity.RelationshipManager;
528             }
529         }
530 
531         internal override BitArray ModifiedProperties
532         {
533             get { return _modifiedFields; }
534         }
535 
536         /// <summary>
537         /// Changes state of the entry to the specified <paramref name="state"/>
538         /// </summary>
539         /// <param name="state">The requested state</param>
ChangeState(EntityState state)540         public override void ChangeState(EntityState state)
541         {
542             EntityUtil.CheckValidStateForChangeEntityState(state);
543 
544             if (this.State == EntityState.Detached && state == EntityState.Detached)
545             {
546                 return;
547             }
548 
549             ValidateState();
550 
551             // store a referece to the cache because this.ObjectStatemanager will be null if the requested state is Detached
552             ObjectStateManager osm = this.ObjectStateManager;
553             osm.TransactionManager.BeginLocalPublicAPI();
554             try
555             {
556                 this.ChangeObjectState(state);
557             }
558             finally
559             {
560                 osm.TransactionManager.EndLocalPublicAPI();
561             }
562         }
563 
564         /// <summary>
565         /// Apply modified properties to the original object.
566         /// </summary>
567         /// <param name="currentEntity">object with modified properties</param>
ApplyCurrentValues(object currentEntity)568         public override void ApplyCurrentValues(object currentEntity)
569         {
570             EntityUtil.CheckArgumentNull(currentEntity, "currentEntity");
571 
572             ValidateState();
573 
574             if (this.IsKeyEntry)
575             {
576                 throw EntityUtil.CannotAccessKeyEntryValues();
577             }
578 
579             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(currentEntity, this.ObjectStateManager);
580 
581             this.ApplyCurrentValuesInternal(wrappedEntity);
582         }
583 
584         /// <summary>
585         /// Apply original values to the entity.
586         /// </summary>
587         /// <param name="originalEntity">The object with original values</param>
ApplyOriginalValues(object originalEntity)588         public override void ApplyOriginalValues(object originalEntity)
589         {
590             EntityUtil.CheckArgumentNull(originalEntity, "originalEntity");
591 
592             ValidateState();
593 
594             if (this.IsKeyEntry)
595             {
596                 throw EntityUtil.CannotAccessKeyEntryValues();
597             }
598 
599             IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(originalEntity, this.ObjectStateManager);
600 
601             this.ApplyOriginalValuesInternal(wrappedEntity);
602         }
603 
604         #endregion // Public members
605 
606         #region RelationshipEnd methods
607 
608         /// <summary>
609         /// Add a RelationshipEntry (one of its ends must equal this.EntityKey)
610         /// </summary>
AddRelationshipEnd(RelationshipEntry item)611         internal void AddRelationshipEnd(RelationshipEntry item)
612         {
613 #if DEBUG
614             Debug.Assert(null != item, "null item");
615             Debug.Assert(null != item.RelationshipWrapper, "null RelationshipWrapper");
616             Debug.Assert(0 <= _countRelationshipEnds, "negative _relationshipEndCount");
617             Debug.Assert(EntityKey.Equals(item.RelationshipWrapper.Key0) || EntityKey.Equals(item.RelationshipWrapper.Key1), "entity key doesn't match");
618 
619             for (RelationshipEntry current = _headRelationshipEnds;
620                  null != current;
621                  current = current.GetNextRelationshipEnd(EntityKey))
622             {
623                 Debug.Assert(!Object.ReferenceEquals(item, current), "RelationshipEntry already in list");
624                 Debug.Assert(!item.RelationshipWrapper.Equals(current.RelationshipWrapper), "RelationshipWrapper already in list");
625             }
626 #endif
627             // the item will become the head of the list
628             // i.e. you walk the list in reverse order of items being added
629             item.SetNextRelationshipEnd(this.EntityKey, _headRelationshipEnds);
630             _headRelationshipEnds = item;
631             _countRelationshipEnds++;
632 
633             Debug.Assert(_countRelationshipEnds == (new RelationshipEndEnumerable(this)).ToArray().Length, "different count");
634         }
635 
636         /// <summary>
637         /// Determines if a given relationship entry is present in the list of entries
638         /// </summary>
639         /// <param name="item">The entry to look for</param>
640         /// <returns>True of the relationship end is found</returns>
ContainsRelationshipEnd(RelationshipEntry item)641         internal bool ContainsRelationshipEnd(RelationshipEntry item)
642         {
643             for (RelationshipEntry current = _headRelationshipEnds;
644                  null != current;
645                  current = current.GetNextRelationshipEnd(EntityKey))
646             {
647                 if (object.ReferenceEquals(current, item))
648                 {
649                     return true;
650                 }
651             }
652             return false;
653         }
654 
655         /// <summary>
656         /// Remove a RelationshipEntry (one of its ends must equal this.EntityKey)
657         /// </summary>
658         /// <param name="item"></param>
RemoveRelationshipEnd(RelationshipEntry item)659         internal void RemoveRelationshipEnd(RelationshipEntry item)
660         {
661             Debug.Assert(null != item, "removing null");
662             Debug.Assert(null != item.RelationshipWrapper, "null RelationshipWrapper");
663             Debug.Assert(1 <= _countRelationshipEnds, "negative _relationshipEndCount");
664             Debug.Assert(EntityKey.Equals(item.RelationshipWrapper.Key0) || EntityKey.Equals(item.RelationshipWrapper.Key1), "entity key doesn't match");
665 
666             // walk the singly-linked list, remembering the previous node so we can remove the current node
667             RelationshipEntry current = _headRelationshipEnds;
668             RelationshipEntry previous = null;
669             bool previousIsKey0 = false;
670             while (null != current)
671             {
672                 // short-circuit if the key matches either candidate by reference
673                 bool currentIsKey0 = object.ReferenceEquals(this.EntityKey, current.Key0) ||
674                     (!object.ReferenceEquals(this.EntityKey, current.Key1) && this.EntityKey.Equals(current.Key0));
675                 if (Object.ReferenceEquals(item, current))
676                 {
677                     RelationshipEntry next;
678                     if (currentIsKey0)
679                     {   // if this.EntityKey matches Key0, NextKey0 is the next element in the lsit
680                         Debug.Assert(EntityKey.Equals(current.RelationshipWrapper.Key0), "entity key didn't match");
681                         next = current.NextKey0;
682                         current.NextKey0 = null;
683                     }
684                     else
685                     {   // if this.EntityKey matches Key1, NextKey1 is the next element in the lsit
686                         Debug.Assert(EntityKey.Equals(current.RelationshipWrapper.Key1), "entity key didn't match");
687                         next = current.NextKey1;
688                         current.NextKey1 = null;
689                     }
690                     if (null == previous)
691                     {
692                         _headRelationshipEnds = next;
693                     }
694                     else if (previousIsKey0)
695                     {
696                         previous.NextKey0 = next;
697                     }
698                     else
699                     {
700                         previous.NextKey1 = next;
701                     }
702                     --_countRelationshipEnds;
703 
704                     Debug.Assert(_countRelationshipEnds == (new RelationshipEndEnumerable(this)).ToArray().Length, "different count");
705                     return;
706                 }
707                 Debug.Assert(!item.RelationshipWrapper.Equals(current.RelationshipWrapper), "same wrapper, different RelationshipEntry instances");
708 
709                 previous = current;
710                 current = currentIsKey0 ? current.NextKey0 : current.NextKey1;
711                 previousIsKey0 = currentIsKey0;
712             }
713             Debug.Assert(false, "didn't remove a RelationshipEntry");
714         }
715 
716         /// <summary>
717         /// Update one of the ends for the related RelationshipEntry
718         /// </summary>
719         /// <param name="oldKey">the EntityKey the relationship should currently have</param>
720         /// <param name="promotedEntry">if promoting entity stub to full entity</param>
UpdateRelationshipEnds(EntityKey oldKey, EntityEntry promotedEntry)721         internal void UpdateRelationshipEnds(EntityKey oldKey, EntityEntry promotedEntry)
722         {
723             Debug.Assert(null != (object)oldKey, "bad oldKey");
724             Debug.Assert(!Object.ReferenceEquals(this, promotedEntry), "shouldn't be same reference");
725 
726             // traverse the list to update one of the ends in the relationship entry
727             int count = 0;
728             RelationshipEntry next = _headRelationshipEnds;
729             while (null != next)
730             {
731                 // get the next relationship end before we change the key of current relationship end
732                 RelationshipEntry current = next;
733                 next = next.GetNextRelationshipEnd(oldKey);
734 
735                 // update the RelationshipEntry from the temporary key to real key
736                 current.ChangeRelatedEnd(oldKey, EntityKey);
737 
738                 // If we have a promoted entry, copy the relationship entries to the promoted entry
739                 // only if the promoted entry doesn't already know about that particular relationship entry
740                 // This can be the case with self referencing entities
741                 if (null != promotedEntry && !promotedEntry.ContainsRelationshipEnd(current))
742                 {   // all relationship ends moved to new promotedEntry
743                     promotedEntry.AddRelationshipEnd(current);
744                 }
745                 ++count;
746             }
747             Debug.Assert(count == _countRelationshipEnds, "didn't traverse all relationships");
748             if (null != promotedEntry)
749             {   // cleanup existing (dead) entry to reduce confusion
750                 _headRelationshipEnds = null;
751                 _countRelationshipEnds = 0;
752             }
753         }
754 
755         #region Enumerable and Enumerator
GetRelationshipEnds()756         internal RelationshipEndEnumerable GetRelationshipEnds()
757         {
758             return new RelationshipEndEnumerable(this);
759         }
760 
761         /// <summary>
762         /// An enumerable so that EntityEntry doesn't implement it
763         /// </summary>
764         internal struct RelationshipEndEnumerable : IEnumerable<RelationshipEntry>, IEnumerable<IEntityStateEntry>
765         {
766             internal static readonly RelationshipEntry[] EmptyRelationshipEntryArray = new RelationshipEntry[0];
767             private readonly EntityEntry _entityEntry;
768 
RelationshipEndEnumerableSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable769             internal RelationshipEndEnumerable(EntityEntry entityEntry)
770             {   // its okay if entityEntry is null
771                 _entityEntry = entityEntry;
772             }
GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable773             public RelationshipEndEnumerator GetEnumerator()
774             {
775                 return new RelationshipEndEnumerator(_entityEntry);
776             }
GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable777             IEnumerator<IEntityStateEntry> IEnumerable<IEntityStateEntry>.GetEnumerator()
778             {
779                 return GetEnumerator();
780             }
GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable781             IEnumerator<RelationshipEntry> IEnumerable<RelationshipEntry>.GetEnumerator()
782             {
783                 Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerable");
784                 return GetEnumerator();
785             }
IEnumerable.GetEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerable786             IEnumerator IEnumerable.GetEnumerator()
787             {
788                 Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerable");
789                 return GetEnumerator();
790             }
791 
792             /// <summary>
793             /// Convert the singly-linked list into an Array
794             /// </summary>
ToArraySystem.Data.Objects.EntityEntry.RelationshipEndEnumerable795             internal RelationshipEntry[] ToArray()
796             {
797                 RelationshipEntry[] list = null;
798                 if ((null != _entityEntry) && (0 < _entityEntry._countRelationshipEnds))
799                 {
800                     RelationshipEntry relationshipEnd = _entityEntry._headRelationshipEnds;
801                     list = new RelationshipEntry[_entityEntry._countRelationshipEnds];
802                     for (int i = 0; i < list.Length; ++i)
803                     {
804                         Debug.Assert(null != relationshipEnd, "count larger than list");
805                         Debug.Assert(_entityEntry.EntityKey.Equals(relationshipEnd.Key0) || _entityEntry.EntityKey.Equals(relationshipEnd.Key1), "entity key mismatch");
806                         list[i] = relationshipEnd;
807 
808                         relationshipEnd = relationshipEnd.GetNextRelationshipEnd(_entityEntry.EntityKey);
809                     }
810                     Debug.Assert(null == relationshipEnd, "count smaller than list");
811                 }
812                 return list ?? EmptyRelationshipEntryArray;
813             }
814         }
815 
816         /// <summary>
817         /// An enumerator to walk the RelationshipEntry linked-list
818         /// </summary>
819         internal struct RelationshipEndEnumerator : IEnumerator<RelationshipEntry>, IEnumerator<IEntityStateEntry>
820         {
821             private readonly EntityEntry _entityEntry;
822             private RelationshipEntry _current;
823 
RelationshipEndEnumeratorSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator824             internal RelationshipEndEnumerator(EntityEntry entityEntry)
825             {
826                 _entityEntry = entityEntry;
827                 _current = null;
828             }
829             public RelationshipEntry Current
830             {
831                 get { return _current; }
832             }
833             IEntityStateEntry IEnumerator<IEntityStateEntry>.Current
834             {
835                 get { return _current; }
836             }
837             object IEnumerator.Current
838             {
839                 get
840                 {
841                     Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerator");
842                     return _current;
843                 }
844             }
DisposeSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator845             public void Dispose()
846             {
847             }
MoveNextSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator848             public bool MoveNext()
849             {
850                 if (null != _entityEntry)
851                 {
852                     if (null == _current)
853                     {
854                         _current = _entityEntry._headRelationshipEnds;
855                     }
856                     else
857                     {
858                         _current = _current.GetNextRelationshipEnd(_entityEntry.EntityKey);
859                     }
860                 }
861                 return (null != _current);
862             }
ResetSystem.Data.Objects.EntityEntry.RelationshipEndEnumerator863             public void Reset()
864             {
865                 Debug.Assert(false, "not implemented");
866             }
867         }
868         #endregion
869         #endregion
870 
871         #region ObjectStateEntry members
872 
873         override internal bool IsKeyEntry
874         {
875             get
876             {
877                 return null == _wrappedEntity.Entity;
878             }
879         }
880 
881         /// <summary>
882         /// Reuse or create a new (Entity)DataRecordInfo.
883         /// </summary>
GetDataRecordInfo(StateManagerTypeMetadata metadata, object userObject)884         override internal DataRecordInfo GetDataRecordInfo(StateManagerTypeMetadata metadata, object userObject)
885         {
886             if (Helper.IsEntityType(metadata.CdmMetadata.EdmType) && (null != (object)_entityKey))
887             {
888                 // is EntityType with null EntityKey when constructing new EntityKey during ObjectStateManager.Add
889                 // always need a new EntityRecordInfo instance for the different key (reusing DataRecordInfo's FieldMetadata).
890                 return new EntityRecordInfo(metadata.DataRecordInfo, _entityKey, (EntitySet)EntitySet);
891             }
892             else
893             {
894                 // ObjectContext.AttachTo uses CurrentValueRecord to build EntityKey for EntityType
895                 // so the Entity doesn't have an EntityKey yet, SQLBU 525130
896                 //Debug.Assert(Helper.IsComplexType(metadata.CdmMetadata.EdmType), "!IsComplexType");
897                 return metadata.DataRecordInfo;
898             }
899         }
900 
Reset()901         override internal void Reset()
902         {
903             Debug.Assert(_cache != null, "Cannot Reset an entity that is not currently attached to a context.");
904             RemoveFromForeignKeyIndex();
905             _cache.ForgetEntryWithConceptualNull(this, resetAllKeys: true);
906 
907             DetachObjectStateManagerFromEntity();
908 
909             _wrappedEntity = EntityWrapperFactory.NullWrapper;
910             _entityKey = null;
911             _modifiedFields = null;
912             _originalValues = null;
913             _originalComplexObjects = null;
914 
915             SetChangeTrackingFlags();
916 
917             base.Reset();
918         }
919 
GetFieldType(int ordinal, StateManagerTypeMetadata metadata)920         override internal Type GetFieldType(int ordinal, StateManagerTypeMetadata metadata)
921         {
922             // 'metadata' is used for ComplexTypes
923 
924             return metadata.GetFieldType(ordinal);
925         }
926 
GetCLayerName(int ordinal, StateManagerTypeMetadata metadata)927         override internal string GetCLayerName(int ordinal, StateManagerTypeMetadata metadata)
928         {
929             return metadata.CLayerMemberName(ordinal);
930         }
931 
GetOrdinalforCLayerName(string name, StateManagerTypeMetadata metadata)932         override internal int GetOrdinalforCLayerName(string name, StateManagerTypeMetadata metadata)
933         {
934             return metadata.GetOrdinalforCLayerMemberName(name);
935         }
936 
RevertDelete()937         override internal void RevertDelete()
938         {
939             // just change the state from deleted, to last state.
940             State = (_modifiedFields == null) ? EntityState.Unchanged : EntityState.Modified;
941             _cache.ChangeState(this, EntityState.Deleted, State);
942         }
943 
GetFieldCount(StateManagerTypeMetadata metadata)944         override internal int GetFieldCount(StateManagerTypeMetadata metadata)
945         {
946             return metadata.FieldCount;
947         }
948 
CascadeAcceptChanges()949         private void CascadeAcceptChanges()
950         {
951             foreach (RelationshipEntry entry in _cache.CopyOfRelationshipsByKey(EntityKey))
952             {
953                 // CascadeAcceptChanges is only called on Entity ObjectStateEntry when it is
954                 // in deleted state. Entity is in deleted state therefore for all related Relationship
955                 // cache entries only valid state is Deleted.
956                 Debug.Assert(entry.State == EntityState.Deleted, "Relationship ObjectStateEntry should be in deleted state");
957                 entry.AcceptChanges();
958             }
959         }
960 
SetModifiedAll()961         override internal void SetModifiedAll()
962         {
963             Debug.Assert(!this.IsKeyEntry, "SetModifiedAll called on a KeyEntry");
964             Debug.Assert(State == EntityState.Modified, "SetModifiedAll called when not modified");
965 
966             ValidateState();
967             if (null == _modifiedFields)
968             {
969                 _modifiedFields = new BitArray(GetFieldCount(_cacheTypeMetadata));
970             }
971             _modifiedFields.SetAll(true);
972         }
973 
974         /// <summary>
975         /// Used to report that a scalar entity property is about to change
976         /// The current value of the specified property is cached when this method is called.
977         /// </summary>
978         /// <param name="entityMemberName">The name of the entity property that is changing</param>
EntityMemberChanging(string entityMemberName)979         override internal void EntityMemberChanging(string entityMemberName)
980         {
981             if (this.IsKeyEntry)
982             {
983                 throw EntityUtil.CannotAccessKeyEntryValues();
984             }
985             this.EntityMemberChanging(entityMemberName, null, null);
986         }
987 
988         /// <summary>
989         /// Used to report that a scalar entity property has been changed
990         /// The property value that was cached during EntityMemberChanging is now
991         /// added to OriginalValues
992         /// </summary>
993         /// <param name="entityMemberName">The name of the entity property that has changing</param>
EntityMemberChanged(string entityMemberName)994         override internal void EntityMemberChanged(string entityMemberName)
995         {
996             if (this.IsKeyEntry)
997             {
998                 throw EntityUtil.CannotAccessKeyEntryValues();
999             }
1000             this.EntityMemberChanged(entityMemberName, null, null);
1001         }
1002 
1003         /// <summary>
1004         /// Used to report that a complex property is about to change
1005         /// The current value of the specified property is cached when this method is called.
1006         /// </summary>
1007         /// <param name="entityMemberName">The name of the top-level entity property that is changing</param>
1008         /// <param name="complexObject">The complex object that contains the property that is changing</param>
1009         /// <param name="complexObjectMemberName">The name of the property that is changing on complexObject</param>
EntityComplexMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)1010         override internal void EntityComplexMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)
1011         {
1012             if (this.IsKeyEntry)
1013             {
1014                 throw EntityUtil.CannotAccessKeyEntryValues();
1015             }
1016             EntityUtil.CheckArgumentNull(complexObjectMemberName, "complexObjectMemberName");
1017             EntityUtil.CheckArgumentNull(complexObject, "complexObject");
1018             this.EntityMemberChanging(entityMemberName, complexObject, complexObjectMemberName);
1019         }
1020 
1021         /// <summary>
1022         /// Used to report that a complex property has been changed
1023         /// The property value that was cached during EntityMemberChanging is now added to OriginalValues
1024         /// </summary>
1025         /// <param name="entityMemberName">The name of the top-level entity property that has changed</param>
1026         /// <param name="complexObject">The complex object that contains the property that changed</param>
1027         /// <param name="complexObjectMemberName">The name of the property that changed on complexObject</param>
EntityComplexMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)1028         override internal void EntityComplexMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)
1029         {
1030             if (this.IsKeyEntry)
1031             {
1032                 throw EntityUtil.CannotAccessKeyEntryValues();
1033             }
1034             EntityUtil.CheckArgumentNull(complexObjectMemberName, "complexObjectMemberName");
1035             EntityUtil.CheckArgumentNull(complexObject, "complexObject");
1036             this.EntityMemberChanged(entityMemberName, complexObject, complexObjectMemberName);
1037         }
1038 
1039         #endregion
1040 
1041         internal IEntityWrapper WrappedEntity
1042         {
1043             get
1044             {
1045                 return _wrappedEntity;
1046             }
1047         }
1048 
1049         /// <summary>
1050         /// Method called to complete the change tracking process on an entity property. The original property value
1051         /// is now saved in the original values record if there is not already an entry in the record for this property.
1052         /// The parameters to this method must have the same values as the parameter values passed to the last call to
1053         /// EntityValueChanging on this ObjectStateEntry.
1054         /// All inputs are in OSpace.
1055         /// </summary>
1056         /// <param name="entityMemberName">Name of the top-level entity property that has changed</param>
1057         /// <param name="complexObject">If entityMemberName refers to a complex property, this is the complex
1058         /// object that contains the change. Otherwise this is null.</param>
1059         /// <param name="complexObjectMemberName">If entityMemberName refers to a complex property, this is the name of
1060         /// the property that has changed on complexObject. Otherwise this is null.</param>
EntityMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)1061         private void EntityMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)
1062         {
1063             string changingMemberName;
1064             StateManagerTypeMetadata typeMetadata;
1065             object changingObject;
1066 
1067             // Get the metadata for the property that is changing, and verify that it is valid to change it for this entry
1068             // If something fails, we will clear out our cached values in the finally block, and require the user to submit another Changing notification
1069             try
1070             {
1071                 int changingOrdinal = this.GetAndValidateChangeMemberInfo(entityMemberName, complexObject, complexObjectMemberName,
1072                     out typeMetadata, out changingMemberName, out changingObject);
1073 
1074                 // if EntityKey is changing and is in a valid scenario for it to change, no further action is needed
1075                 if (changingOrdinal == -2)
1076                 {
1077                     return;
1078                 }
1079 
1080                 // Verify that the inputs to this call match the values we have cached
1081                 if ((changingObject != _cache.ChangingObject) ||
1082                     (changingMemberName != _cache.ChangingMember) ||
1083                     (entityMemberName != _cache.ChangingEntityMember))
1084                 {
1085                     throw EntityUtil.EntityValueChangedWithoutEntityValueChanging();
1086                 }
1087 
1088                 // check the state after the other values because if the other cached values have not been set and are null, it is more
1089                 // intuitive to the user to get an error that specifically points to that as the problem, and in that case, the state will
1090                 // also not be matched, so if we checked this first, it would cause a confusing error to be thrown.
1091                 if (this.State != _cache.ChangingState)
1092                 {
1093                     throw EntityUtil.ChangedInDifferentStateFromChanging(this.State, _cache.ChangingState);
1094                 }
1095 
1096                 object oldValue = _cache.ChangingOldValue;
1097                 object newValue = null;
1098                 StateManagerMemberMetadata memberMetadata = null;
1099                 if (_cache.SaveOriginalValues)
1100                 {
1101                     memberMetadata = typeMetadata.Member(changingOrdinal);
1102                     // Expand only non-null complex type values
1103                     if (memberMetadata.IsComplex && oldValue != null)
1104                     {
1105                         // devnote: Not using GetCurrentEntityValue here because change tracking can only be done on OSpace members,
1106                         //          so we don't need to worry about shadow state, and we don't want a CSpace representation of complex objects
1107                         newValue = memberMetadata.GetValue(changingObject);
1108 
1109                         ExpandComplexTypeAndAddValues(memberMetadata, oldValue, newValue, false);
1110                     }
1111                     else
1112                     {
1113                         AddOriginalValue(memberMetadata, changingObject, oldValue);
1114                     }
1115                 }
1116 
1117                 // if the property is a Foreign Key, let's clear out the appropriate EntityReference
1118                 // UNLESS we are applying FK changes as part of DetectChanges where we don't want to
1119                 // start changing references yet. If we are in the Align stage of DetectChanges, this is ok.
1120                 TransactionManager transManager = ObjectStateManager.TransactionManager;
1121                 List<Pair<string, string>> relationships;
1122                 if (complexObject == null &&  // check if property is a top-level property
1123                     (transManager.IsAlignChanges || !transManager.IsDetectChanges) &&
1124                     this.IsPropertyAForeignKey(entityMemberName, out relationships))
1125                 {
1126                     foreach (var relationship in relationships)
1127                     {
1128                         string relationshipName = relationship.First;
1129                         string targetRoleName = relationship.Second;
1130 
1131                         RelatedEnd relatedEnd = this.WrappedEntity.RelationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName);
1132                         Debug.Assert(relatedEnd != null, "relatedEnd should exist if property is a foreign key");
1133                         EntityReference reference = relatedEnd as EntityReference;
1134                         Debug.Assert(reference != null, "relatedEnd should be an EntityReference");
1135 
1136                         // Allow updating of other relationships that this FK property participates in except that
1137                         // if we're doing fixup by references as part of AcceptChanges then don't allow a ref to
1138                         // be changed.
1139                         if (!transManager.IsFixupByReference)
1140                         {
1141                             if (memberMetadata == null)
1142                             {
1143                                 memberMetadata = typeMetadata.Member(changingOrdinal);
1144                             }
1145                             if (newValue == null)
1146                             {
1147                                 newValue = memberMetadata.GetValue(changingObject);
1148                             }
1149 
1150                             bool hasConceptualNullFk = ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey);
1151                             if (!ByValueEqualityComparer.Default.Equals(oldValue, newValue) || hasConceptualNullFk)
1152                             {
1153                                 FixupEntityReferenceByForeignKey(reference);
1154                             }
1155                         }
1156                     }
1157                 }
1158 
1159                 // POCO: The state of the entry is not changed if the EntityMemberChanged method
1160                 // was called from ObjectStateEntry.OriginalValues property.
1161                 // The OriginalValues uses EntityMemberChanging/EntityMemberChanged to update snapshot of complex object in case
1162                 // complex object was changed (not a scalar value).
1163                 if (_cache != null && !_cache.TransactionManager.IsOriginalValuesGetter)
1164                 {
1165                     EntityState initialState = State;
1166                     if (State != EntityState.Added)
1167                     {
1168                         State = EntityState.Modified;
1169                     }
1170                     if (State == EntityState.Modified)
1171                     {
1172                         SetModifiedProperty(entityMemberName);
1173                     }
1174                     if (initialState != this.State)
1175                     {
1176                         _cache.ChangeState(this, initialState, this.State);
1177                     }
1178                 }
1179             }
1180             finally
1181             {
1182                 Debug.Assert(_cache != null, "Unexpected null state manager.");
1183                 SetCachedChangingValues(null, null, null, EntityState.Detached, null);
1184             }
1185         }
1186 
1187         // helper method used to set value of property
SetCurrentEntityValue(string memberName, object newValue)1188         internal void SetCurrentEntityValue(string memberName, object newValue)
1189         {
1190             int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName);
1191             SetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, newValue);
1192         }
1193 
SetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)1194         internal void SetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)
1195         {
1196             ValidateState();
1197             if (State == EntityState.Added)
1198             {
1199                 throw EntityUtil.OriginalValuesDoesNotExist();
1200             }
1201 
1202             EntityState initialState = State;
1203 
1204             object orgValue; // StateManagerValue
1205             object oldOriginalValue; // the actual value
1206 
1207             // Update original values list
1208             StateManagerMemberMetadata memberMetadata = metadata.Member(ordinal);
1209             if (FindOriginalValue(memberMetadata, userObject, out orgValue))
1210             {
1211                 _originalValues.Remove((StateManagerValue)orgValue);
1212             }
1213 
1214             if (memberMetadata.IsComplex)
1215             {
1216                 oldOriginalValue = memberMetadata.GetValue(userObject);
1217                 if (oldOriginalValue == null)
1218                 {
1219                     throw EntityUtil.NullableComplexTypesNotSupported(memberMetadata.CLayerName);
1220                 }
1221 
1222                 IExtendedDataRecord newValueRecord = newValue as IExtendedDataRecord;
1223                 if (newValueRecord != null)
1224                 {
1225                     // Requires materialization
1226                     newValue = _cache.ComplexTypeMaterializer.CreateComplex(newValueRecord, newValueRecord.DataRecordInfo, null);
1227                 }
1228 
1229                 // We only store scalar properties values in original values, so no need to search the list
1230                 // if the property being set is complex. Just get the value as an OSpace object.
1231                 ExpandComplexTypeAndAddValues(memberMetadata, oldOriginalValue, newValue, true);
1232             }
1233             else
1234             {
1235                 AddOriginalValue(memberMetadata, userObject, newValue);
1236             }
1237 
1238             if (initialState == EntityState.Unchanged)
1239             {
1240                 State = EntityState.Modified;
1241             }
1242         }
1243 
1244         /// <summary>
1245         /// Method called to start the change tracking process on an entity property. The current property value is cached at
1246         /// this stage in preparation for later storage in the original values record. Multiple successful calls to this method
1247         /// will overwrite the cached values.
1248         /// All inputs are in OSpace.
1249         /// </summary>
1250         /// <param name="entityMemberName">Name of the top-level entity property that is changing</param>
1251         /// <param name="complexObject">If entityMemberName refers to a complex property, this is the complex
1252         /// object that contains the change. Otherwise this is null.</param>
1253         /// <param name="complexObjectMemberName">If entityMemberName refers to a complex property, this is the name of
1254         /// the property that is changing on complexObject. Otherwise this is null.</param>
EntityMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)1255         private void EntityMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)
1256         {
1257             string changingMemberName;
1258             StateManagerTypeMetadata typeMetadata;
1259             object changingObject;
1260 
1261             // Get the metadata for the property that is changing, and verify that it is valid to change it for this entry
1262             int changingOrdinal = this.GetAndValidateChangeMemberInfo(entityMemberName, complexObject, complexObjectMemberName,
1263                 out typeMetadata, out changingMemberName, out changingObject);
1264 
1265             // if EntityKey is changing and is in a valid scenario for it to change, no further action is needed
1266             if (changingOrdinal == -2)
1267             {
1268                 return;
1269             }
1270 
1271             Debug.Assert(changingOrdinal != -1, "Expected GetAndValidateChangeMemberInfo to throw for a invalid property name");
1272 
1273             // Cache the current value for later storage in original values. If we are not in a state where we should update
1274             // the original values, we don't even need to bother saving the current value here. However, we will still cache
1275             // the other data regarding the change, so that we always require matching Changing and Changed calls, regardless of the state.
1276             StateManagerMemberMetadata memberMetadata = typeMetadata.Member(changingOrdinal);
1277 
1278             // POCO
1279             // Entities which don't implement IEntityWithChangeTracker entity can already have original values even in the Unchanged state.
1280             _cache.SaveOriginalValues = (State == EntityState.Unchanged || State == EntityState.Modified) &&
1281                                         !FindOriginalValue(memberMetadata, changingObject);
1282 
1283             // devnote: Not using GetCurrentEntityValue here because change tracking can only be done on OSpace members,
1284             //          so we don't need to worry about shadow state, and we don't want a CSpace representation of complex objects
1285             object oldValue = memberMetadata.GetValue(changingObject);
1286 
1287             Debug.Assert(this.State != EntityState.Detached, "Change tracking should not happen on detached entities.");
1288             SetCachedChangingValues(entityMemberName, changingObject, changingMemberName, this.State, oldValue);
1289         }
1290 
1291         // helper method used to get value of property
GetOriginalEntityValue(string memberName)1292         internal object GetOriginalEntityValue(string memberName)
1293         {
1294             int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName);
1295             return GetOriginalEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.OriginalReadonly);
1296         }
1297 
GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)1298         internal object GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)
1299         {
1300             Debug.Assert(updatableRecord != ObjectStateValueRecord.OriginalUpdatablePublic, "OriginalUpdatablePublic records must preserve complex type information, use the overload that takes parentEntityPropertyIndex");
1301             return GetOriginalEntityValue(metadata, ordinal, userObject, updatableRecord, s_EntityRoot);
1302         }
1303 
GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)1304         internal object GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)
1305         {
1306             // if original value is stored, then use it, otherwise use the current value from the entity
1307             ValidateState();
1308             object retValue;
1309             StateManagerMemberMetadata member = metadata.Member(ordinal);
1310             if (FindOriginalValue(member, userObject, out retValue))
1311             {
1312                 // If the object is null, return DBNull.Value to be consistent with GetCurrentEntityValue
1313                 return ((StateManagerValue)retValue).originalValue ?? DBNull.Value;
1314             }
1315             return GetCurrentEntityValue(metadata, ordinal, userObject, updatableRecord, parentEntityPropertyIndex);
1316         }
1317 
GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)1318         internal object GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)
1319         {
1320             Debug.Assert(updatableRecord != ObjectStateValueRecord.OriginalUpdatablePublic, "OriginalUpdatablePublic records must preserve complex type information, use the overload that takes parentEntityPropertyIndex");
1321             return GetCurrentEntityValue(metadata, ordinal, userObject, updatableRecord, s_EntityRoot);
1322         }
1323 
GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)1324         internal object GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)
1325         {
1326             ValidateState();
1327 
1328             object retValue = null;
1329             StateManagerMemberMetadata member = metadata.Member(ordinal);
1330             Debug.Assert(null != member, "didn't throw ArgumentOutOfRangeException");
1331 
1332             if (!metadata.IsMemberPartofShadowState(ordinal))
1333             { // if it is not shadow state
1334                 retValue = member.GetValue(userObject);
1335 
1336                 // Wrap the value in a record if it is a non-null complex type
1337                 if (member.IsComplex && retValue != null)
1338                 {
1339                     // need to get the new StateManagerTypeMetadata for nested /complext member
1340                     switch (updatableRecord)
1341                     {
1342                         case ObjectStateValueRecord.OriginalReadonly:
1343                             retValue = new ObjectStateEntryDbDataRecord(this,
1344                                 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue);
1345                             break;
1346                         case ObjectStateValueRecord.CurrentUpdatable:
1347                             retValue = new ObjectStateEntryDbUpdatableDataRecord(this,
1348                                 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue);
1349                             break;
1350                         case ObjectStateValueRecord.OriginalUpdatableInternal:
1351                             retValue = new ObjectStateEntryOriginalDbUpdatableDataRecord_Internal(this,
1352                                 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue);
1353                             break;
1354                         case ObjectStateValueRecord.OriginalUpdatablePublic:
1355                             retValue = new ObjectStateEntryOriginalDbUpdatableDataRecord_Public(this,
1356                                 _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue, parentEntityPropertyIndex);
1357                             break;
1358                         default:
1359                             Debug.Assert(false, "shouldn't happen");
1360                             break;
1361                     }
1362                     // we need to pass the toplevel ordinal
1363                 }
1364             }
1365 #if DEBUG // performance, don't do this work in retail until shadow state is supported
1366             else if (userObject == _wrappedEntity.Entity)
1367             {
1368                 Debug.Assert(false, "shadowstate not supported");
1369 #if SupportShadowState
1370                             Debug.Assert(null != _currentValues, "shadow state without values");
1371                             _currentValues.TryGetValue(member.CLayerName, out retValue); // try to get it from shadow state if exists
1372                             // we don't support CSpace only complex type
1373 #endif
1374             }
1375 #endif
1376             return retValue ?? DBNull.Value;
1377         }
1378 
FindOriginalValue(StateManagerMemberMetadata metadata, object instance)1379         private bool FindOriginalValue(StateManagerMemberMetadata metadata, object instance)
1380         {
1381             object tmp;
1382             return FindOriginalValue(metadata, instance, out tmp);
1383         }
1384 
FindOriginalValue(StateManagerMemberMetadata metadata, object instance, out object value)1385         internal bool FindOriginalValue(StateManagerMemberMetadata metadata, object instance, out object value)
1386         {
1387             bool found = false;
1388             object retValue = null;
1389             if (null != _originalValues)
1390             {
1391                 foreach (StateManagerValue cachevalue in _originalValues)   // this should include also shadow state
1392                 {
1393                     if (cachevalue.userObject == instance && cachevalue.memberMetadata == metadata)
1394                     {
1395                         found = true;
1396                         retValue = cachevalue;
1397                         break;
1398                     }
1399                 }
1400             }
1401             value = retValue;
1402             return found;
1403         }
1404 
1405         // Get AssociationEndMember of current entry of given relationship
1406         // Relationship must be related to the current entry.
GetAssociationEndMember(RelationshipEntry relationshipEntry)1407         internal AssociationEndMember GetAssociationEndMember(RelationshipEntry relationshipEntry)
1408         {
1409             Debug.Assert((object)this.EntityKey != null, "entry should have a not null EntityKey");
1410 
1411             ValidateState();
1412 
1413             AssociationEndMember endMember = relationshipEntry.RelationshipWrapper.GetAssociationEndMember(EntityKey);
1414             Debug.Assert(null != endMember, "should be one of the ends of the relationship");
1415             return endMember;
1416         }
1417 
1418         // Get entry which is on the other end of given relationship.
1419         // Relationship must be related to the current entry.
GetOtherEndOfRelationship(RelationshipEntry relationshipEntry)1420         internal EntityEntry GetOtherEndOfRelationship(RelationshipEntry relationshipEntry)
1421         {
1422             Debug.Assert((object)this.EntityKey != null, "entry should have a not null EntityKey");
1423 
1424             return _cache.GetEntityEntry(relationshipEntry.RelationshipWrapper.GetOtherEntityKey(this.EntityKey));
1425         }
1426 
1427 
1428         /// <summary>
1429         /// Helper method to recursively expand a complex object's values down to scalars for storage in the original values record.
1430         /// This method is used when a whole complex object is set on its parent object, instead of just setting
1431         /// individual scalar values on that object.
1432         /// </summary>
1433         /// <param name="memberMetadata">metadata for the complex property being expanded on the parent
1434         /// where the parent can be an entity or another complex object</param>
1435         /// <param name="oldComplexObject">Old value of the complex property. Scalar values from this object are stored in the original values record</param>
1436         /// <param name="newComplexObject">New value of the complex property. This object reference is used in the original value record and is
1437         /// associated with the scalar values for the same property on the oldComplexObject</param>
1438         /// <param name="useOldComplexObject">Whether or not to use the existing complex object in the original values or to use the original value that is already present </param>
ExpandComplexTypeAndAddValues(StateManagerMemberMetadata memberMetadata, object oldComplexObject, object newComplexObject, bool useOldComplexObject)1439         private void ExpandComplexTypeAndAddValues(StateManagerMemberMetadata memberMetadata, object oldComplexObject, object newComplexObject, bool useOldComplexObject)
1440         {
1441             Debug.Assert(memberMetadata.IsComplex, "Cannot expand non-complex objects");
1442             if (newComplexObject == null)
1443             {
1444                 throw EntityUtil.NullableComplexTypesNotSupported(memberMetadata.CLayerName);
1445             }
1446             Debug.Assert(oldComplexObject == null || (oldComplexObject.GetType() == newComplexObject.GetType()), "Cannot replace a complex object with an object of a different type, unless the original one was null");
1447 
1448             StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(memberMetadata.CdmMetadata.TypeUsage.EdmType);
1449             object retValue;
1450             for (int field = 0; field < typeMetadata.FieldCount; field++)
1451             {
1452                 StateManagerMemberMetadata complexMemberMetadata = typeMetadata.Member(field);
1453                 if (complexMemberMetadata.IsComplex)
1454                 {
1455                     object oldComplexMemberValue = null;
1456                     if (oldComplexObject != null)
1457                     {
1458                         oldComplexMemberValue = complexMemberMetadata.GetValue(oldComplexObject);
1459                         if (oldComplexMemberValue == null && FindOriginalValue(complexMemberMetadata, oldComplexObject, out retValue))
1460                         {
1461                             _originalValues.Remove((StateManagerValue)retValue);
1462                         }
1463                     }
1464                     ExpandComplexTypeAndAddValues(complexMemberMetadata, oldComplexMemberValue, complexMemberMetadata.GetValue(newComplexObject), useOldComplexObject);
1465                 }
1466                 else
1467                 {
1468                     object originalValue = null;
1469                     object complexObject = newComplexObject;
1470 
1471                     if (useOldComplexObject)
1472                     {
1473                         // Set the original values using the existing current value object
1474                         // complexObject --> the existing complex object
1475                         // originalValue --> the new value to set for this member
1476                         originalValue = complexMemberMetadata.GetValue(newComplexObject);
1477                         complexObject = oldComplexObject;
1478                     }
1479                     else
1480                     {
1481                         if (oldComplexObject != null)
1482                         {
1483                             // If we already have an entry for this property in the original values list, we need to remove it. We can't just
1484                             // update it because StateManagerValue is a struct and there is no way to get a reference to the entry in the list.
1485                             originalValue = complexMemberMetadata.GetValue(oldComplexObject);
1486                             if (FindOriginalValue(complexMemberMetadata, oldComplexObject, out retValue))
1487                             {
1488                                 StateManagerValue originalStateValue = ((StateManagerValue)retValue);
1489                                 _originalValues.Remove(originalStateValue);
1490                                 originalValue = originalStateValue.originalValue;
1491                             }
1492                             else
1493                             {
1494                                 Debug.Assert(this.Entity is IEntityWithChangeTracker, "for POCO objects the snapshot should contain all original values");
1495                             }
1496                         }
1497                         else
1498                         {
1499                             originalValue = complexMemberMetadata.GetValue(newComplexObject);
1500                         }
1501                     }
1502 
1503 
1504                     // Add the new entry. The userObject will reference the new complex object that is currently being set.
1505                     // If the value was in the list previously, we will still use the old value with the new object reference.
1506                     // That will ensure that we preserve the old value while still maintaining the link to the
1507                     // existing complex object that is attached to the entity or parent complex object. If an entry is already
1508                     // in the list this means that it was either explicitly set by the user or the entire complex type was previously
1509                     // set and expanded down to the individual properties.  In either case we do the same thing.
1510                     AddOriginalValue(complexMemberMetadata, complexObject, originalValue);
1511                 }
1512             }
1513         }
1514 
1515         /// <summary>
1516         /// Helper method to validate that the property names being reported as changing/changed are valid for this entity and that
1517         /// the entity is in a valid state for the change request. Also determines if this is a change on a complex object, and
1518         /// returns the appropriate metadata and object to be used for the rest of the changing and changed operations.
1519         /// </summary>
1520         /// <param name="entityMemberName">Top-level entity property name</param>
1521         /// <param name="complexObject">Complex object that contains the change, null if the change is on a top-level entity property</param>
1522         /// <param name="complexObjectMemberName">Name of the property that is changing on the complexObject, null for top-level entity properties</param>
1523         /// <param name="typeMetadata">Metadata for the type that contains the change, either for the entity itself or for the complex object</param>
1524         /// <param name="changingMemberName">Property name that is actually changing -- either entityMemberName for entities or
1525         /// complexObjectMemberName for complex objects</param>
1526         /// <param name="changingObject">Object reference that contains the change, either the entity or complex object
1527         /// as appropriate for the requested change</param>
1528         /// <returns>Ordinal of the property that is changing, or -2 if the EntityKey is changing in a valid scenario. This is relative
1529         /// to the returned typeMetadata. Throws exceptions if the requested property name(s) are invalid for this entity.</returns>
GetAndValidateChangeMemberInfo(string entityMemberName, object complexObject, string complexObjectMemberName, out StateManagerTypeMetadata typeMetadata, out string changingMemberName, out object changingObject)1530         internal int GetAndValidateChangeMemberInfo(string entityMemberName, object complexObject, string complexObjectMemberName,
1531             out StateManagerTypeMetadata typeMetadata, out string changingMemberName, out object changingObject)
1532         {
1533             typeMetadata = null;
1534             changingMemberName = null;
1535             changingObject = null;
1536 
1537             EntityUtil.CheckArgumentNull(entityMemberName, "entityMemberName");
1538             // complexObject and complexObjectMemberName are allowed to be null here for change tracking on top-level entity properties
1539 
1540             ValidateState();
1541 
1542             int changingOrdinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(entityMemberName);
1543             if (changingOrdinal == -1)
1544             {
1545                 if (entityMemberName == StructuralObject.EntityKeyPropertyName)
1546                 {
1547                     // Setting EntityKey property is only allowed from here when we are in the middle of relationship fixup.
1548                     if (!_cache.InRelationshipFixup)
1549                     {
1550                         throw EntityUtil.CantSetEntityKey();
1551                     }
1552                     else
1553                     {
1554                         // If we are in fixup, there is nothing more to do here with EntityKey, so just
1555                         // clear the saved changing values and return. This will ensure that we behave
1556                         // the same with the change notifications on EntityKey as with other properties.
1557                         // I.e. we still don't allow the following:
1558                         //     EntityMemberChanging("Property1")
1559                         //     EntityMemberChanging("EntityKey")
1560                         //     EntityMemberChanged("EntityKey")
1561                         //     EntityMemberChanged("Property1")
1562                         Debug.Assert(this.State != EntityState.Detached, "Change tracking should not happen on detached entities.");
1563                         SetCachedChangingValues(null, null, null, this.State, null);
1564                         return -2;
1565                     }
1566                 }
1567                 else
1568                 {
1569                     throw EntityUtil.ChangeOnUnmappedProperty(entityMemberName);
1570                 }
1571             }
1572             else
1573             {
1574                 StateManagerTypeMetadata tmpTypeMetadata;
1575                 string tmpChangingMemberName;
1576                 object tmpChangingObject;
1577 
1578                 // entityMemberName is a confirmed valid property on the Entity, but if this is a complex type we also need to validate its property
1579                 if (complexObject != null)
1580                 {
1581                     // a complex object was provided, but the top-level Entity property is not complex
1582                     if (!_cacheTypeMetadata.Member(changingOrdinal).IsComplex)
1583                     {
1584                         throw EntityUtil.ComplexChangeRequestedOnScalarProperty(entityMemberName);
1585                     }
1586 
1587                     tmpTypeMetadata = _cache.GetOrAddStateManagerTypeMetadata(complexObject.GetType(), (EntitySet)this.EntitySet);
1588                     changingOrdinal = tmpTypeMetadata.GetOrdinalforOLayerMemberName(complexObjectMemberName);
1589                     if (changingOrdinal == -1)
1590                     {
1591                         throw EntityUtil.ChangeOnUnmappedComplexProperty(complexObjectMemberName);
1592                     }
1593 
1594                     tmpChangingMemberName = complexObjectMemberName;
1595                     tmpChangingObject = complexObject;
1596                 }
1597                 else
1598                 {
1599                     tmpTypeMetadata = _cacheTypeMetadata;
1600                     tmpChangingMemberName = entityMemberName;
1601                     tmpChangingObject = this.Entity;
1602                     if (WrappedEntity.IdentityType != Entity.GetType() && // Is a proxy
1603                         Entity is IEntityWithChangeTracker && // Is a full proxy
1604                         IsPropertyAForeignKey(entityMemberName)) // Property is part of FK
1605                     {
1606                         // Set a flag so that we don't try to set FK properties while already in a setter.
1607                         _cache.EntityInvokingFKSetter = WrappedEntity.Entity;
1608                     }
1609                 }
1610 
1611                 VerifyEntityValueIsEditable(tmpTypeMetadata, changingOrdinal, tmpChangingMemberName);
1612 
1613                 typeMetadata = tmpTypeMetadata;
1614                 changingMemberName = tmpChangingMemberName;
1615                 changingObject = tmpChangingObject;
1616                 return changingOrdinal;
1617             }
1618         }
1619 
1620         /// <summary>
1621         /// Helper method to set the information needed for the change tracking cache. Ensures that all of these values get set together.
1622         /// </summary>
SetCachedChangingValues(string entityMemberName, object changingObject, string changingMember, EntityState changingState, object oldValue)1623         private void SetCachedChangingValues(string entityMemberName, object changingObject, string changingMember, EntityState changingState, object oldValue)
1624         {
1625             _cache.ChangingEntityMember = entityMemberName;
1626             _cache.ChangingObject = changingObject;
1627             _cache.ChangingMember = changingMember;
1628             _cache.ChangingState = changingState;
1629             _cache.ChangingOldValue = oldValue;
1630             if (changingState == EntityState.Detached)
1631             {
1632                 _cache.SaveOriginalValues = false;
1633             }
1634         }
1635 
1636         [DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
1637         internal OriginalValueRecord EditableOriginalValues
1638         {
1639             get
1640             {
1641                 Debug.Assert(!this.IsKeyEntry, "should not edit original key entry");
1642                 Debug.Assert(EntityState.Modified == State ||
1643                              EntityState.Deleted == State ||
1644                              EntityState.Unchanged == State,
1645                              "only expecting Modified or Deleted state");
1646 
1647                 return new ObjectStateEntryOriginalDbUpdatableDataRecord_Internal(this, _cacheTypeMetadata, _wrappedEntity.Entity);
1648             }
1649         }
1650 
DetachObjectStateManagerFromEntity()1651         internal void DetachObjectStateManagerFromEntity()
1652         {
1653             // This method can be called on relationship entries where there is no entity
1654             if (!this.IsKeyEntry) // _wrappedEntity.Entity is not null.
1655             {
1656                 _wrappedEntity.SetChangeTracker(null);
1657                 _wrappedEntity.DetachContext();
1658 
1659                 if (!this._cache.TransactionManager.IsAttachTracking ||
1660                      this._cache.TransactionManager.OriginalMergeOption != MergeOption.NoTracking)
1661                 {
1662                     // If AttachTo() failed while attaching graph retrieved with NoTracking option,
1663                     // we don't want to reset the EntityKey
1664 
1665                     //Entry's this._entityKey is set to null at the caller, maintaining consistency between entityWithKey.EntityKey and this.EntityKey
1666                     _wrappedEntity.EntityKey = null;
1667                 }
1668             }
1669         }
1670 
1671         // This method is used for entities which don't implement IEntityWithChangeTracker to store orignal values of properties
1672         // which are later used to detect changes in properties
TakeSnapshot(bool onlySnapshotComplexProperties)1673         internal void TakeSnapshot(bool onlySnapshotComplexProperties)
1674         {
1675             Debug.Assert(!this.IsKeyEntry);
1676 
1677             if (this.State != EntityState.Added)
1678             {
1679                 StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
1680 
1681                 int fieldCount = this.GetFieldCount(metadata);
1682                 object currentValue;
1683 
1684                 for (int i = 0; i < fieldCount; i++)
1685                 {
1686                     StateManagerMemberMetadata member = metadata.Member(i);
1687                     if (member.IsComplex)
1688                     {
1689                         // memberValue is a complex object
1690                         currentValue = member.GetValue(this._wrappedEntity.Entity);
1691                         this.AddComplexObjectSnapshot(this.Entity, i, currentValue);
1692                         this.TakeSnapshotOfComplexType(member, currentValue);
1693                     }
1694                     else if (!onlySnapshotComplexProperties)
1695                     {
1696                         currentValue = member.GetValue(this._wrappedEntity.Entity);
1697                         this.AddOriginalValue(member, this._wrappedEntity.Entity, currentValue);
1698                     }
1699                 }
1700             }
1701 
1702             this.TakeSnapshotOfForeignKeys();
1703         }
1704 
TakeSnapshotOfForeignKeys()1705         internal void TakeSnapshotOfForeignKeys()
1706         {
1707             Dictionary<RelatedEnd, HashSet<EntityKey>> keys;
1708             this.FindRelatedEntityKeysByForeignKeys(out keys, useOriginalValues: false);
1709             if (keys != null)
1710             {
1711                 foreach (var pair in keys)
1712                 {
1713                     EntityReference reference = pair.Key as EntityReference;
1714                     Debug.Assert(reference != null, "EntityReference expected");
1715                     Debug.Assert(pair.Value.Count == 1, "Unexpected number of keys");
1716 
1717                     if (!ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey))
1718                     {
1719                         reference.SetCachedForeignKey(pair.Value.First(), this);
1720                     }
1721                 }
1722             }
1723         }
1724 
TakeSnapshotOfComplexType(StateManagerMemberMetadata member, object complexValue)1725         private void TakeSnapshotOfComplexType(StateManagerMemberMetadata member, object complexValue)
1726         {
1727             Debug.Assert(member.IsComplex, "Cannot expand non-complex objects");
1728 
1729             // Skip null values
1730             if (complexValue == null)
1731                 return;
1732 
1733             StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType);
1734             for (int i = 0; i < typeMetadata.FieldCount; i++)
1735             {
1736                 StateManagerMemberMetadata complexMember = typeMetadata.Member(i);
1737                 object currentValue = complexMember.GetValue(complexValue);
1738                 if (complexMember.IsComplex)
1739                 {
1740                     // Recursive call for nested complex types
1741                     // For POCO objects we have to store a reference to the original complex object
1742                     this.AddComplexObjectSnapshot(complexValue, i, currentValue);
1743                     TakeSnapshotOfComplexType(complexMember, currentValue);
1744                 }
1745                 else
1746                 {
1747                     if (!FindOriginalValue(complexMember, complexValue))
1748                     {
1749                         AddOriginalValue(complexMember, complexValue, currentValue);
1750                     }
1751                 }
1752             }
1753         }
1754 
AddComplexObjectSnapshot(object userObject, int ordinal, object complexObject)1755         private void AddComplexObjectSnapshot(object userObject, int ordinal, object complexObject)
1756         {
1757             Debug.Assert(userObject != null, "null userObject");
1758             Debug.Assert(ordinal >= 0, "invalid ordinal");
1759 
1760             if (complexObject == null)
1761             {
1762                 return;
1763             }
1764 
1765             // Verify if the same complex object is not used multiple times.
1766             this.CheckForDuplicateComplexObjects(complexObject);
1767 
1768             if (this._originalComplexObjects == null)
1769             {
1770                 this._originalComplexObjects = new Dictionary<object, Dictionary<int, object>>();
1771             }
1772             Dictionary<int, object> ordinal2complexObject;
1773             if (!this._originalComplexObjects.TryGetValue(userObject, out ordinal2complexObject))
1774             {
1775                 ordinal2complexObject = new Dictionary<int, object>();
1776                 this._originalComplexObjects.Add(userObject, ordinal2complexObject);
1777             }
1778 
1779             Debug.Assert(!ordinal2complexObject.ContainsKey(ordinal), "shouldn't contain this ordinal yet");
1780             ordinal2complexObject.Add(ordinal, complexObject);
1781         }
1782 
CheckForDuplicateComplexObjects(object complexObject)1783         private void CheckForDuplicateComplexObjects(object complexObject)
1784         {
1785             if (this._originalComplexObjects == null || complexObject == null)
1786                 return;
1787 
1788             foreach (Dictionary<int, object> ordinal2complexObject in this._originalComplexObjects.Values)
1789             {
1790                 foreach (object oldComplexObject in ordinal2complexObject.Values)
1791                 {
1792                     if (Object.ReferenceEquals(complexObject, oldComplexObject))
1793                     {
1794                         throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_ComplexObjectUsedMultipleTimes(this.Entity.GetType().FullName, complexObject.GetType().FullName));
1795                     }
1796                 }
1797             }
1798         }
1799 
1800         /// <summary>
1801         /// Uses DetectChanges to determine whether or not the current value of the property with the given
1802         /// name is different from its original value. Note that this may be different from the property being
1803         /// marked as modified since a property which has not changed can still be marked as modified.
1804         /// </summary>
1805         /// <remarks>
1806         /// For complex properties, a new instance of the complex object which has all the same property
1807         /// values as the original instance is not considered to be different by this method.
1808         /// </remarks>
1809         /// <param name="propertyName">The name of the property.</param>
1810         /// <returns>True if the property has changed; false otherwise.</returns>
IsPropertyChanged(string propertyName)1811         public override bool IsPropertyChanged(string propertyName)
1812         {
1813             return DetectChangesInProperty(ValidateAndGetOrdinalForProperty(propertyName, "IsPropertyChanged"),
1814                                            detectOnlyComplexProperties: false, detectOnly: true);
1815         }
1816 
DetectChangesInProperty(int ordinal, bool detectOnlyComplexProperties, bool detectOnly)1817         private bool DetectChangesInProperty(int ordinal, bool detectOnlyComplexProperties, bool detectOnly)
1818         {
1819             bool changeDetected = false;
1820             StateManagerMemberMetadata member = _cacheTypeMetadata.Member(ordinal);
1821             var currentValue = member.GetValue(this._wrappedEntity.Entity);
1822             if (member.IsComplex)
1823             {
1824                 if (this.State != EntityState.Deleted)
1825                 {
1826                     var oldComplexValue = this.GetComplexObjectSnapshot(this.Entity, ordinal);
1827                     bool complexObjectInstanceChanged = this.DetectChangesInComplexType(member, member, currentValue, oldComplexValue, ref changeDetected, detectOnly);
1828                     if (complexObjectInstanceChanged)
1829                     {
1830                         // instance of complex object was changed
1831 
1832                         // Before updating the snapshot verify if the same complex object is not used multiple times.
1833                         this.CheckForDuplicateComplexObjects(currentValue);
1834 
1835                         if (!detectOnly)
1836                         {
1837                             // equivalent of EntityObject.ReportPropertyChanging()
1838                             ((IEntityChangeTracker)this).EntityMemberChanging(member.CLayerName);
1839 
1840                             Debug.Assert(_cache.SaveOriginalValues, "complex object instance was changed so the SaveOriginalValues flag should be set to true");
1841 
1842                             // Since the EntityMemberChanging method is called AFTER the complex object was changed, it means that
1843                             // the EntityMemberChanging method was unable to find the real oldValue.
1844                             // The real old value is stored for POCO objects in _originalComplexObjects dictionary.
1845                             // The cached changing oldValue has to be updated with the real oldValue.
1846                             _cache.ChangingOldValue = oldComplexValue;
1847 
1848                             // equivalent of EntityObject.ReportPropertyChanged()
1849                             ((IEntityChangeTracker)this).EntityMemberChanged(member.CLayerName);
1850                         }
1851 
1852                         // The _originalComplexObjects should always contain references to the values of complex objects which are "original"
1853                         // at the moment of calling GetComplexObjectSnapshot().  They are used to get original scalar values from _originalValues.
1854                         this.UpdateComplexObjectSnapshot(member, this.Entity, ordinal, currentValue);
1855 
1856                         if (!changeDetected)
1857                         {
1858                             // If we haven't already detected a change then we need to check the properties of the complex
1859                             // object to see if there are any changes so that IsPropertyChanged will not skip reporting the
1860                             // change just because the object reference has changed.
1861                             DetectChangesInComplexType(member, member, currentValue, oldComplexValue, ref changeDetected, detectOnly);
1862                         }
1863                     }
1864                 }
1865             }
1866             else if (!detectOnlyComplexProperties)
1867             {
1868                 object originalStateManagerValue;
1869                 var originalValueFound = this.FindOriginalValue(member, this._wrappedEntity.Entity, out originalStateManagerValue);
1870 
1871                 Debug.Assert(originalValueFound, "Original value not found even after snapshot.");
1872 
1873                 var originalValue = ((StateManagerValue)originalStateManagerValue).originalValue;
1874                 if (!Object.Equals(currentValue, originalValue))
1875                 {
1876                     changeDetected = true;
1877 
1878                     // Key property - throw if the actual byte values have changed, otherwise ignore the change
1879                     if (member.IsPartOfKey)
1880                     {
1881                         if (!ByValueEqualityComparer.Default.Equals(currentValue, originalValue))
1882                         {
1883                             throw EntityUtil.CannotModifyKeyProperty(member.CLayerName);
1884                         }
1885                     }
1886                     else
1887                     {
1888                         if (this.State != EntityState.Deleted && !detectOnly)
1889                         {
1890                             // equivalent of EntityObject.ReportPropertyChanging()
1891                             ((IEntityChangeTracker)this).EntityMemberChanging(member.CLayerName);
1892 
1893                             // equivalent of EntityObject.ReportPropertyChanged()
1894                             ((IEntityChangeTracker)this).EntityMemberChanged(member.CLayerName);
1895                         }
1896                     }
1897                 }
1898             }
1899 
1900             return changeDetected;
1901         }
1902 
1903         // This method uses original values stored in the ObjectStateEntry to detect changes in values of entity's properties
DetectChangesInProperties(bool detectOnlyComplexProperties)1904         internal void DetectChangesInProperties(bool detectOnlyComplexProperties)
1905         {
1906             Debug.Assert(!this.IsKeyEntry, "Entry should be an EntityEntry");
1907             Debug.Assert(this.State != EntityState.Added, "This method should not be called for entries in Added state");
1908 
1909             int fieldCount = GetFieldCount(_cacheTypeMetadata);
1910             for (int i = 0; i < fieldCount; i++)
1911             {
1912                 DetectChangesInProperty(i, detectOnlyComplexProperties, detectOnly: false);
1913             }
1914         }
1915 
DetectChangesInComplexType( StateManagerMemberMetadata topLevelMember, StateManagerMemberMetadata complexMember, object complexValue, object oldComplexValue, ref bool changeDetected, bool detectOnly)1916         private bool DetectChangesInComplexType(
1917             StateManagerMemberMetadata topLevelMember,
1918             StateManagerMemberMetadata complexMember,
1919             object complexValue,
1920             object oldComplexValue,
1921             ref bool changeDetected,
1922             bool detectOnly)
1923         {
1924             Debug.Assert(complexMember.IsComplex, "Cannot expand non-complex objects");
1925 
1926             if (complexValue == null)
1927             {
1928                 // If the values are just null, do not detect this as a change
1929                 if (oldComplexValue == null)
1930                 {
1931                     return false;
1932                 }
1933                 throw EntityUtil.NullableComplexTypesNotSupported(complexMember.CLayerName);
1934             }
1935 
1936             if (!Object.ReferenceEquals(oldComplexValue, complexValue))
1937             {
1938                 // Complex object instance was changed.  The calling method will update the snapshot of this object.
1939                 return true;
1940             }
1941 
1942             Debug.Assert(oldComplexValue != null, "original complex type value should not be null at this point");
1943 
1944             StateManagerTypeMetadata metadata = _cache.GetOrAddStateManagerTypeMetadata(complexMember.CdmMetadata.TypeUsage.EdmType);
1945             for (int i = 0; i < GetFieldCount(metadata); i++)
1946             {
1947                 StateManagerMemberMetadata member = metadata.Member(i);
1948                 object currentValue = null;
1949                 currentValue = member.GetValue(complexValue);
1950                 if (member.IsComplex)
1951                 {
1952                     if (this.State != EntityState.Deleted)
1953                     {
1954                         var oldNestedComplexValue = this.GetComplexObjectSnapshot(complexValue, i);
1955                         bool complexObjectInstanceChanged = DetectChangesInComplexType(topLevelMember, member, currentValue, oldNestedComplexValue, ref changeDetected, detectOnly);
1956                         if (complexObjectInstanceChanged)
1957                         {
1958                             // instance of complex object was changed
1959 
1960                             // Before updating the snapshot verify if the same complex object is not used multiple times.
1961                             this.CheckForDuplicateComplexObjects(currentValue);
1962 
1963                             if (!detectOnly)
1964                             {
1965                                 // equivalent of EntityObject.ReportComplexPropertyChanging()
1966                                 ((IEntityChangeTracker)this).EntityComplexMemberChanging(topLevelMember.CLayerName, complexValue, member.CLayerName);
1967 
1968                                 // Since the EntityComplexMemberChanging method is called AFTER the complex object was changed, it means that
1969                                 // the EntityComplexMemberChanging method was unable to find real oldValue.
1970                                 // The real old value is stored for POCO objects in _originalComplexObjects dictionary.
1971                                 // The cached changing oldValue has to be updated with the real oldValue.
1972                                 _cache.ChangingOldValue = oldNestedComplexValue;
1973 
1974                                 // equivalent of EntityObject.ReportComplexPropertyChanged()
1975                                 ((IEntityChangeTracker)this).EntityComplexMemberChanged(topLevelMember.CLayerName, complexValue, member.CLayerName);
1976                             }
1977                             // The _originalComplexObjects should always contain references to the values of complex objects which are "original"
1978                             // at the moment of calling GetComplexObjectSnapshot().  They are used to get original scalar values from _originalValues.
1979                             this.UpdateComplexObjectSnapshot(member, complexValue, i, currentValue);
1980 
1981                             if (!changeDetected)
1982                             {
1983                                 DetectChangesInComplexType(topLevelMember, member, currentValue, oldNestedComplexValue, ref changeDetected, detectOnly);
1984                             }
1985                         }
1986                     }
1987                 }
1988                 else
1989                 {
1990                     object originalStateManagerValue;
1991                     bool originalValueFound = FindOriginalValue(member, complexValue, out originalStateManagerValue);
1992 
1993                     // originalValueFound will be false if the complex value was initially null since then its original
1994                     // values will always be null, in which case all original scalar properties of the complex value are
1995                     // considered null.
1996                     if (!Object.Equals(currentValue, originalValueFound ? ((StateManagerValue)originalStateManagerValue).originalValue : null))
1997                     {
1998                         changeDetected = true;
1999 
2000                         Debug.Assert(!member.IsPartOfKey, "Found member of complex type that is part of a key");
2001 
2002                         if (!detectOnly)
2003                         {
2004                             // equivalent of EntityObject.ReportComplexPropertyChanging()
2005                             ((IEntityChangeTracker)this).EntityComplexMemberChanging(topLevelMember.CLayerName, complexValue, member.CLayerName);
2006 
2007                             // equivalent of EntityObject.ReportComplexPropertyChanged()
2008                             ((IEntityChangeTracker)this).EntityComplexMemberChanged(topLevelMember.CLayerName, complexValue, member.CLayerName);
2009                         }
2010                     }
2011                 }
2012             }
2013 
2014             // Scalar value in a complex object was changed
2015             return false;
2016         }
2017 
GetComplexObjectSnapshot(object parentObject, int parentOrdinal)2018         private object GetComplexObjectSnapshot(object parentObject, int parentOrdinal)
2019         {
2020             object oldComplexObject = null;
2021             if (this._originalComplexObjects != null)
2022             {
2023                 Dictionary<int, object> ordinal2complexObject;
2024                 if (this._originalComplexObjects.TryGetValue(parentObject, out ordinal2complexObject))
2025                 {
2026                     ordinal2complexObject.TryGetValue(parentOrdinal, out oldComplexObject);
2027                 }
2028             }
2029             return oldComplexObject;
2030         }
2031 
2032         // The _originalComplexObjects should always contain references to the values of complex objects which are "original"
2033         // at the moment of calling GetComplexObjectSnapshot().  They are used to get original scalar values from _originalValues
2034         // and to check if complex object instance was changed.
2035         // This method should be called after EntityMemberChanged in POCO case.
UpdateComplexObjectSnapshot(StateManagerMemberMetadata member, object userObject, int ordinal, object currentValue)2036         internal void UpdateComplexObjectSnapshot(StateManagerMemberMetadata member, object userObject, int ordinal, object currentValue)
2037         {
2038             bool requiresAdd = true;
2039             if (this._originalComplexObjects != null)
2040             {
2041                 Dictionary<int, object> ordinal2complexObject;
2042                 if (this._originalComplexObjects.TryGetValue(userObject, out ordinal2complexObject))
2043                 {
2044                     Debug.Assert(ordinal2complexObject != null, "value should already exists");
2045 
2046                     object oldValue;
2047                     ordinal2complexObject.TryGetValue(ordinal, out oldValue);
2048                     // oldValue may be null if the complex object was attached with a null value
2049                     ordinal2complexObject[ordinal] = currentValue;
2050 
2051                     // check nested complex objects (if they exist)
2052                     if (oldValue != null && this._originalComplexObjects.TryGetValue(oldValue, out ordinal2complexObject))
2053                     {
2054                         this._originalComplexObjects.Remove(oldValue);
2055                         this._originalComplexObjects.Add(currentValue, ordinal2complexObject);
2056 
2057                         StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType);
2058                         for (int i = 0; i < typeMetadata.FieldCount; i++)
2059                         {
2060                             StateManagerMemberMetadata complexMember = typeMetadata.Member(i);
2061                             if (complexMember.IsComplex)
2062                             {
2063                                 object nestedValue = complexMember.GetValue(currentValue);
2064                                 // Recursive call for nested complex objects
2065                                 UpdateComplexObjectSnapshot(complexMember, currentValue, i, nestedValue);
2066                             }
2067                         }
2068                     }
2069                     requiresAdd = false;
2070                 }
2071             }
2072             if(requiresAdd)
2073             {
2074                 AddComplexObjectSnapshot(userObject, ordinal, currentValue);
2075             }
2076         }
2077 
2078         /// <summary>
2079         /// Processes each dependent end of an FK relationship in this entity and determines if a nav
2080         /// prop is set to a principal.  If it is, and if the principal is Unchanged or Modified,
2081         /// then the primary key value is taken from the principal and used to fixup the FK value.
2082         /// This is called during AddObject so that references set from the added object will take
2083         /// precedence over FK values such that there is no need for the user to set FK values
2084         /// explicitly.  If a conflict in the FK value is encountered due to an overlapping FK
2085         /// that is tied to two different PK values, then an exception is thrown.
2086         /// Note that references to objects that are not yet tracked by the context are ignored, since
2087         /// they will ultimately be brought into the context as Added objects, at which point we would
2088         /// have skipped them anyway because the are not Unchanged or Modified.
2089         /// </summary>
FixupFKValuesFromNonAddedReferences()2090         internal void FixupFKValuesFromNonAddedReferences()
2091         {
2092             Debug.Assert(EntitySet is EntitySet, "Expect entity entries to have true entity sets.");
2093             if (!((EntitySet)EntitySet).HasForeignKeyRelationships)
2094             {
2095                 return;
2096             }
2097 
2098             // Keep track of all FK values that have already been set so that we can detect conflicts.
2099             var changedFKs = new Dictionary<int, object>();
2100             foreach (Tuple<AssociationSet, ReferentialConstraint> dependent in ForeignKeyDependents)
2101             {
2102                 var reference = RelationshipManager.GetRelatedEndInternal(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
2103                 Debug.Assert(reference != null, "Expected reference to exist and be an entity reference (not collection)");
2104 
2105                 if (reference.TargetAccessor.HasProperty)
2106                 {
2107                     var principal = WrappedEntity.GetNavigationPropertyValue(reference);
2108                     if (principal != null)
2109                     {
2110                         ObjectStateEntry principalEntry;
2111                         if (_cache.TryGetObjectStateEntry(principal, out principalEntry) &&
2112                             (principalEntry.State == EntityState.Modified || principalEntry.State == EntityState.Unchanged))
2113                         {
2114                             Debug.Assert(principalEntry is EntityEntry, "Existing entry for an entity must be an EntityEntry, not a RelationshipEntry");
2115                             reference.UpdateForeignKeyValues(WrappedEntity, ((EntityEntry)principalEntry).WrappedEntity, changedFKs, forceChange: false);
2116                         }
2117                     }
2118                 }
2119             }
2120         }
2121 
2122         // Method used for entities which don't implement IEntityWithRelationships
TakeSnapshotOfRelationships()2123         internal void TakeSnapshotOfRelationships()
2124         {
2125             Debug.Assert(this._wrappedEntity != null, "wrapped entity shouldn't be null");
2126             Debug.Assert(!(this._wrappedEntity.Entity is IEntityWithRelationships), "this method should be called only for entities which don't implement IEntityWithRelationships");
2127 
2128             RelationshipManager rm = this._wrappedEntity.RelationshipManager;
2129 
2130             StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
2131 
2132             ReadOnlyMetadataCollection<NavigationProperty> navigationProperties =
2133                 (metadata.CdmMetadata.EdmType as EntityType).NavigationProperties;
2134 
2135             foreach (NavigationProperty n in navigationProperties)
2136             {
2137                 RelatedEnd relatedEnd = rm.GetRelatedEndInternal(n.RelationshipType.FullName, n.ToEndMember.Name);
2138                 object val = this.WrappedEntity.GetNavigationPropertyValue(relatedEnd);
2139 
2140                 if (val != null)
2141                 {
2142                     if (n.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
2143                     {
2144                         // Collection
2145                         IEnumerable collection = val as IEnumerable;
2146                         if (collection == null)
2147                         {
2148                             throw new EntityException(System.Data.Entity.Strings.ObjectStateEntry_UnableToEnumerateCollection(n.Name, this.Entity.GetType().FullName));
2149                         }
2150 
2151                         foreach (object o in collection)
2152                         {
2153                             // Skip nulls in collections
2154                             if (o != null)
2155                             {
2156                                 this.TakeSnapshotOfSingleRelationship(relatedEnd, n, o);
2157                             }
2158                         }
2159                     }
2160                     else
2161                     {
2162                         // Reference
2163                         this.TakeSnapshotOfSingleRelationship(relatedEnd, n, val);
2164                     }
2165                 }
2166             }
2167         }
2168 
TakeSnapshotOfSingleRelationship(RelatedEnd relatedEnd, NavigationProperty n, object o)2169         private void TakeSnapshotOfSingleRelationship(RelatedEnd relatedEnd, NavigationProperty n, object o)
2170         {
2171             // Related entity can be already attached, so find the existing entry
2172             EntityEntry relatedEntry = this.ObjectStateManager.FindEntityEntry(o);
2173             IEntityWrapper relatedWrapper;
2174 
2175             if (relatedEntry != null)
2176             {
2177                 Debug.Assert(this.ObjectStateManager.TransactionManager.IsAddTracking ||
2178                     this.ObjectStateManager.TransactionManager.IsAttachTracking, "Should be inside Attach or Add");
2179 
2180                 //relatedEntry.VerifyOrUpdateRelatedEnd(n, this._wrappedEntity);
2181                 relatedWrapper = relatedEntry._wrappedEntity;
2182 
2183                 // In case of unidirectional relationships, it is possible that the other end of relationship was already added
2184                 // to the context but its relationship manager doesn't contain proper related end with the current entity.
2185                 // In OSM we treat all relationships as bidirectional so the related end has to be updated.
2186                 RelatedEnd otherRelatedEnd = relatedWrapper.RelationshipManager.GetRelatedEndInternal(n.RelationshipType.FullName, n.FromEndMember.Name);
2187                 if (!otherRelatedEnd.ContainsEntity(this._wrappedEntity))
2188                 {
2189                     Debug.Assert(relatedWrapper.ObjectStateEntry != null, "Expected related entity to be tracked in snapshot code.");
2190                     if (relatedWrapper.ObjectStateEntry.State == EntityState.Deleted)
2191                     {
2192                         throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
2193                     }
2194                     if (ObjectStateManager.TransactionManager.IsAttachTracking &&
2195                         (State & (EntityState.Modified | EntityState.Unchanged)) != 0 &&
2196                         (relatedWrapper.ObjectStateEntry.State & (EntityState.Modified | EntityState.Unchanged)) != 0)
2197                     {
2198                         EntityEntry principalEntry = null;
2199                         EntityEntry dependentEntry = null;
2200                         if (relatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
2201                         {
2202                             principalEntry = relatedWrapper.ObjectStateEntry;
2203                             dependentEntry = this;
2204                         }
2205                         else if (otherRelatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
2206                         {
2207                             principalEntry = this;
2208                             dependentEntry = relatedWrapper.ObjectStateEntry;
2209                         }
2210                         if (principalEntry != null)
2211                         {
2212                             var constraint = ((AssociationType)relatedEnd.RelationMetadata).ReferentialConstraints[0];
2213                             if (!RelatedEnd.VerifyRIConstraintsWithRelatedEntry(constraint, dependentEntry.GetCurrentEntityValue, principalEntry.EntityKey))
2214                             {
2215                                 throw EntityUtil.InconsistentReferentialConstraintProperties();
2216                             }
2217                         }
2218                     }
2219                     // Keep track of the fact that we aligned the related end here so that we can undo
2220                     // it in rollback without wiping the already existing nav properties.
2221                     EntityReference otherEndAsRef = otherRelatedEnd as EntityReference;
2222                     if (otherEndAsRef != null && otherEndAsRef.NavigationPropertyIsNullOrMissing())
2223                     {
2224                         ObjectStateManager.TransactionManager.AlignedEntityReferences.Add(otherEndAsRef);
2225                     }
2226                     otherRelatedEnd.AddToLocalCache(this._wrappedEntity, applyConstraints: true);
2227                     otherRelatedEnd.OnAssociationChanged(CollectionChangeAction.Add, _wrappedEntity.Entity);
2228                 }
2229             }
2230             else
2231             {
2232                 if (!this.ObjectStateManager.TransactionManager.WrappedEntities.TryGetValue(o, out relatedWrapper))
2233                 {
2234                     relatedWrapper = EntityWrapperFactory.WrapEntityUsingStateManager(o, this.ObjectStateManager);
2235                 }
2236             }
2237 
2238             if (!relatedEnd.ContainsEntity(relatedWrapper))
2239             {
2240                 relatedEnd.AddToLocalCache(relatedWrapper, true);
2241                 relatedEnd.OnAssociationChanged(CollectionChangeAction.Add, relatedWrapper.Entity);
2242             }
2243         }
2244 
DetectChangesInRelationshipsOfSingleEntity()2245         internal void DetectChangesInRelationshipsOfSingleEntity()
2246         {
2247             Debug.Assert(!this.IsKeyEntry, "Entry should be an EntityEntry");
2248             Debug.Assert(!(this.Entity is IEntityWithRelationships), "Entity shouldn't implement IEntityWithRelationships");
2249 
2250             StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
2251 
2252             ReadOnlyMetadataCollection<NavigationProperty> navigationProperties =
2253                 (metadata.CdmMetadata.EdmType as EntityType).NavigationProperties;
2254 
2255             foreach (NavigationProperty n in navigationProperties)
2256             {
2257                 RelatedEnd relatedEnd = this.WrappedEntity.RelationshipManager.GetRelatedEndInternal(n.RelationshipType.FullName, n.ToEndMember.Name);
2258                 Debug.Assert(relatedEnd != null, "relatedEnd is null");
2259 
2260                 object val = this.WrappedEntity.GetNavigationPropertyValue(relatedEnd);
2261 
2262                 HashSet<object> current = new HashSet<object>();
2263                 if (val != null)
2264                 {
2265                     if (n.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
2266                     {
2267                         // Collection
2268                         IEnumerable collection = val as IEnumerable;
2269                         if (collection == null)
2270                         {
2271                             throw new EntityException(System.Data.Entity.Strings.ObjectStateEntry_UnableToEnumerateCollection(n.Name, this.Entity.GetType().FullName));
2272                         }
2273                         foreach (object o in collection)
2274                         {
2275                             // Skip nulls in collections
2276                             if (o != null)
2277                             {
2278                                 current.Add(o);
2279                             }
2280                         }
2281                     }
2282                     else
2283                     {
2284                         // Reference
2285                         current.Add(val);
2286                     }
2287                 }
2288 
2289                 // find deleted entities
2290                 foreach (object o in relatedEnd.GetInternalEnumerable())
2291                 {
2292                     if (!current.Contains(o))
2293                     {
2294                         this.AddRelationshipDetectedByGraph(
2295                             this.ObjectStateManager.TransactionManager.DeletedRelationshipsByGraph, o, relatedEnd, verifyForAdd:false);
2296                     }
2297                     else
2298                     {
2299                         current.Remove(o);
2300                     }
2301                 }
2302 
2303                 // "current" contains now only added entities
2304                 foreach (object o in current)
2305                 {
2306                     this.AddRelationshipDetectedByGraph(
2307                         this.ObjectStateManager.TransactionManager.AddedRelationshipsByGraph, o, relatedEnd, verifyForAdd:true);
2308                 }
2309             }
2310         }
2311 
AddRelationshipDetectedByGraph( Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<IEntityWrapper>>> relationships, object relatedObject, RelatedEnd relatedEndFrom, bool verifyForAdd)2312         private void AddRelationshipDetectedByGraph(
2313             Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<IEntityWrapper>>> relationships,
2314             object relatedObject,
2315             RelatedEnd relatedEndFrom,
2316             bool verifyForAdd)
2317         {
2318             IEntityWrapper relatedWrapper = EntityWrapperFactory.WrapEntityUsingStateManager(relatedObject, this.ObjectStateManager);
2319 
2320             this.AddDetectedRelationship(relationships, relatedWrapper, relatedEndFrom);
2321 
2322             RelatedEnd relatedEndTo = relatedEndFrom.GetOtherEndOfRelationship(relatedWrapper);
2323 
2324             if (verifyForAdd &&
2325                 relatedEndTo is EntityReference &&
2326                 this.ObjectStateManager.FindEntityEntry(relatedObject) == null)
2327             {
2328                 // If the relatedObject is not tracked by the context, let's detect it before OSM.PerformAdd to avoid
2329                 // making RelatedEnd.Add() more complicated (it would have to know when the values in relatedEndTo can be overriden, and when not
2330                 relatedEndTo.VerifyNavigationPropertyForAdd(_wrappedEntity);
2331             }
2332 
2333             this.AddDetectedRelationship(relationships, _wrappedEntity, relatedEndTo);
2334         }
2335 
AddRelationshipDetectedByForeignKey( Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> relationships, Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> principalRelationships, EntityKey relatedKey, EntityEntry relatedEntry, RelatedEnd relatedEndFrom)2336         private void AddRelationshipDetectedByForeignKey(
2337             Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> relationships,
2338             Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> principalRelationships,
2339             EntityKey relatedKey,
2340             EntityEntry relatedEntry,
2341             RelatedEnd relatedEndFrom)
2342         {
2343             Debug.Assert(!relatedKey.IsTemporary, "the relatedKey was created by a method which returns only permaanent keys");
2344             this.AddDetectedRelationship(relationships, relatedKey, relatedEndFrom);
2345 
2346             if (relatedEntry != null)
2347             {
2348                 IEntityWrapper relatedWrapper = relatedEntry.WrappedEntity; ;
2349 
2350                 RelatedEnd relatedEndTo = relatedEndFrom.GetOtherEndOfRelationship(relatedWrapper);
2351 
2352                 EntityKey permanentKeyOwner = this.ObjectStateManager.GetPermanentKey(relatedEntry.WrappedEntity, relatedEndTo, this.WrappedEntity);
2353                 this.AddDetectedRelationship(principalRelationships, permanentKeyOwner, relatedEndTo);
2354             }
2355         }
2356 
2357         /// <summary>
2358         /// Designed to be used by Change Detection methods to insert
2359         /// Added/Deleted relationships into <see cref="TransactionManager"/>
2360         /// Creates new entries in the dictionaries if required
2361         /// </summary>
2362         /// <typeparam name="T">IEntityWrapper or EntityKey</typeparam>
2363         /// <param name="relationships">The set of detected relationships to add this entry to</param>
2364         /// <param name="relatedObject">The entity the relationship points to</param>
2365         /// <param name="relatedEnd">The related end the relationship originates from</param>
AddDetectedRelationship( Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<T>>> relationships, T relatedObject, RelatedEnd relatedEnd)2366         private void AddDetectedRelationship<T>(
2367             Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<T>>> relationships,
2368             T relatedObject,
2369             RelatedEnd relatedEnd)
2370         {
2371             // Update info about changes to this/from side of the relationship
2372             Dictionary<RelatedEnd, HashSet<T>> alreadyDetectedRelationshipsFrom;
2373             if (!relationships.TryGetValue(relatedEnd.WrappedOwner, out alreadyDetectedRelationshipsFrom))
2374             {
2375                 alreadyDetectedRelationshipsFrom = new Dictionary<RelatedEnd, HashSet<T>>();
2376                 relationships.Add(relatedEnd.WrappedOwner, alreadyDetectedRelationshipsFrom);
2377             }
2378 
2379             HashSet<T> objectsInRelatedEnd;
2380             if (!alreadyDetectedRelationshipsFrom.TryGetValue(relatedEnd, out objectsInRelatedEnd))
2381             {
2382                 objectsInRelatedEnd = new HashSet<T>();
2383                 alreadyDetectedRelationshipsFrom.Add(relatedEnd, objectsInRelatedEnd);
2384             }
2385             else
2386             {
2387                 if (relatedEnd is EntityReference)
2388                 {
2389                     Debug.Assert(objectsInRelatedEnd.Count() == 1, "unexpected number of entities for EntityReference");
2390                     T existingRelatedObject = objectsInRelatedEnd.First();
2391                     if (!Object.Equals(existingRelatedObject, relatedObject))
2392                     {
2393                         throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(
2394                             relatedEnd.RelationshipNavigation.To,
2395                             relatedEnd.RelationshipNavigation.RelationshipName);
2396                     }
2397                 }
2398             }
2399 
2400             objectsInRelatedEnd.Add(relatedObject);
2401         }
2402 
2403         /// <summary>
2404         /// Detaches an entry and create in its place key entry if necessary
2405         /// Removes relationships with another key entries and removes these key entries if necessary
2406         /// </summary>
Detach()2407         internal void Detach()
2408         {
2409             ValidateState();
2410 
2411             Debug.Assert(!this.IsKeyEntry);
2412 
2413             bool createKeyEntry = false;
2414 
2415             RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager;
2416             Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
2417             // Key entry should be created only when current entity is not in Added state
2418             // and if the entity is a "OneToOne" or "ZeroToOne" end of some existing relationship.
2419             createKeyEntry =
2420                     this.State != EntityState.Added &&
2421                     this.IsOneEndOfSomeRelationship();
2422 
2423             _cache.TransactionManager.BeginDetaching();
2424             try
2425             {
2426                 // Remove current entity from collections/references (on both ends of relationship)
2427                 // Relationship entries are removed from ObjectStateManager if current entity is in Added state
2428                 // or if current entity is a "Many" end of the relationship.
2429                 // NOTE In this step only relationship entries which have normal entity on the other end
2430                 //      can be detached.
2431                 // NOTE In this step no Deleted relationship entries are detached.
2432                 relationshipManager.DetachEntityFromRelationships(this.State);
2433             }
2434             finally
2435             {
2436                 _cache.TransactionManager.EndDetaching();
2437             }
2438 
2439             // Remove relationship entries which has a key entry on the other end.
2440             // If the key entry does not have any other relationship, it is removed from Object State Manager.
2441             // NOTE Relationship entries which have a normal entity on the other end are detached only if the relationship state is Deleted.
2442             this.DetachRelationshipsEntries(relationshipManager);
2443 
2444             IEntityWrapper existingWrappedEntity = _wrappedEntity;
2445             EntityKey key = _entityKey;
2446             EntityState state = State;
2447 
2448             if (createKeyEntry)
2449             {
2450                 this.DegradeEntry();
2451             }
2452             else
2453             {
2454                 // If entity is in state different than Added state, entityKey should not be set to null
2455                 // EntityKey is set to null in
2456                 //    ObjectStateManger.ChangeState() ->
2457                 //    ObjectStateEntry.Reset() ->
2458                 //    ObjectStateEntry.DetachObjectStateManagerFromEntity()
2459 
2460                 // Store data required to restore the entity key if needed.
2461                 _wrappedEntity.ObjectStateEntry = null;
2462 
2463                 _cache.ChangeState(this, this.State, EntityState.Detached);
2464             }
2465 
2466             // In case the detach event modifies the key.
2467             if (state != EntityState.Added)
2468             {
2469                 existingWrappedEntity.EntityKey = key;
2470             }
2471         }
2472 
2473         //"doFixup" equals to False is called from EntityCollection & Ref code only
Delete(bool doFixup)2474         internal void Delete(bool doFixup)
2475         {
2476             ValidateState();
2477 
2478             if (this.IsKeyEntry)
2479             {
2480                 throw EntityUtil.CannotCallDeleteOnKeyEntry();
2481             }
2482 
2483             if (doFixup && this.State != EntityState.Deleted)
2484             {
2485                 this.RelationshipManager.NullAllFKsInDependentsForWhichThisIsThePrincipal();
2486                 this.NullAllForeignKeys(); // May set conceptual nulls which will later be removed
2487                 this.FixupRelationships();
2488             }
2489 
2490             switch (State)
2491             {
2492                 case EntityState.Added:
2493                     Debug.Assert(EntityState.Added == State, "Expected ObjectStateEntry state is Added; make sure FixupRelationship did not corrupt cache entry state");
2494 
2495                     _cache.ChangeState(this, EntityState.Added, EntityState.Detached);
2496 
2497                     Debug.Assert(null == _modifiedFields, "There should not be any modified fields");
2498 
2499                     break;
2500                 case EntityState.Modified:
2501                     if (!doFixup)
2502                     {
2503                         // Even when we are not doing relationship fixup at the collection level, if the entry is not a relationship
2504                         // we need to check to see if there are relationships that are referencing keys that should be removed
2505                         // this mainly occurs in cascade delete scenarios
2506                         DeleteRelationshipsThatReferenceKeys(null, null);
2507                     }
2508                     Debug.Assert(EntityState.Modified == State, "Expected ObjectStateEntry state is Modified; make sure FixupRelationship did not corrupt cache entry state");
2509                     _cache.ChangeState(this, EntityState.Modified, EntityState.Deleted);
2510                     State = EntityState.Deleted;
2511 
2512                     break;
2513                 case EntityState.Unchanged:
2514                     if (!doFixup)
2515                     {
2516                         // Even when we are not doing relationship fixup at the collection level, if the entry is not a relationship
2517                         // we need to check to see if there are relationships that are referencing keys that should be removed
2518                         // this mainly occurs in cascade delete scenarios
2519                         DeleteRelationshipsThatReferenceKeys(null, null);
2520                     }
2521                     Debug.Assert(State == EntityState.Unchanged, "Unexpected state");
2522                     Debug.Assert(EntityState.Unchanged == State, "Expected ObjectStateEntry state is Unchanged; make sure FixupRelationship did not corrupt cache entry state");
2523                     _cache.ChangeState(this, EntityState.Unchanged, EntityState.Deleted);
2524                     Debug.Assert(null == _modifiedFields, "There should not be any modified fields");
2525                     State = EntityState.Deleted;
2526 
2527                     break;
2528                 case EntityState.Deleted:
2529                     // no-op
2530                     break;
2531             }
2532         }
2533 
2534         /// <summary>
2535         /// Nulls all FK values in this entity, or sets conceptual nulls if they are not nullable.
2536         /// </summary>
NullAllForeignKeys()2537         private void NullAllForeignKeys()
2538         {
2539             foreach (var dependent in ForeignKeyDependents)
2540             {
2541                 EntityReference relatedEnd = WrappedEntity.RelationshipManager.GetRelatedEndInternal(
2542                     dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
2543                 Debug.Assert(relatedEnd != null, "Expected non-null EntityReference to principal.");
2544                 relatedEnd.NullAllForeignKeys();
2545             }
2546         }
2547 
IsOneEndOfSomeRelationship()2548         private bool IsOneEndOfSomeRelationship()
2549         {
2550             foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey))
2551             {
2552                 RelationshipMultiplicity multiplicity = this.GetAssociationEndMember(relationshipEntry).RelationshipMultiplicity;
2553                 if (multiplicity == RelationshipMultiplicity.One ||
2554                     multiplicity == RelationshipMultiplicity.ZeroOrOne)
2555                 {
2556                     EntityKey targetKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(EntityKey);
2557                     EntityEntry relatedEntry = _cache.GetEntityEntry(targetKey);
2558                     // Relationships with KeyEntries don't count.
2559                     if (!relatedEntry.IsKeyEntry)
2560                     {
2561                         return true;
2562                     }
2563                 }
2564             }
2565             return false;
2566         }
2567 
2568         // Detaches related relationship entries if other ends of these relationships are key entries.
2569         // Detaches also related relationship entries if the entry is in Deleted state and the multiplicity is Many.
2570         // Key entry from the other side of the relationship is removed if is not related to other entries.
DetachRelationshipsEntries(RelationshipManager relationshipManager)2571         private void DetachRelationshipsEntries(RelationshipManager relationshipManager)
2572         {
2573             Debug.Assert(relationshipManager != null, "Unexpected null RelationshipManager");
2574             Debug.Assert(!this.IsKeyEntry, "Should only be detaching relationships with key entries if the source is not a key entry");
2575 
2576             foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(EntityKey))
2577             {
2578                 // Get state entry for other side of the relationship
2579                 EntityKey targetKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(EntityKey);
2580                 Debug.Assert((object)targetKey != null, "EntityKey not on either side of relationship as expected");
2581 
2582                 EntityEntry relatedEntry = _cache.GetEntityEntry(targetKey);
2583                 if (relatedEntry.IsKeyEntry)
2584                 {
2585                     // This must be an EntityReference, so set the DetachedEntityKey if the relationship is currently Added or Unchanged
2586                     // devnote: This assumes that we are in the middle of detaching the entity associated with this state entry, because
2587                     //          we don't always want to preserve the EntityKey for every detached relationship, if the source entity itself isn't being detached
2588                     if (relationshipEntry.State != EntityState.Deleted)
2589                     {
2590                         AssociationEndMember targetMember = relationshipEntry.RelationshipWrapper.GetAssociationEndMember(targetKey);
2591                         // devnote: Since we know the target end of this relationship is a key entry, it has to be a reference, so just cast
2592                         EntityReference entityReference = (EntityReference)relationshipManager.GetRelatedEndInternal(targetMember.DeclaringType.FullName, targetMember.Name);
2593                         entityReference.DetachedEntityKey = targetKey;
2594                     }
2595                     // else do nothing -- we can't null out the key for Deleted state, because there could be other relationships with this same source in a different state
2596 
2597                     // Remove key entry if necessary
2598                     relationshipEntry.DeleteUnnecessaryKeyEntries();
2599                     // Remove relationship entry
2600                     relationshipEntry.DetachRelationshipEntry();
2601                 }
2602                 else
2603                 {
2604                     // Detach deleted relationships
2605                     if (relationshipEntry.State == EntityState.Deleted)
2606                     {
2607                         RelationshipMultiplicity multiplicity = this.GetAssociationEndMember(relationshipEntry).RelationshipMultiplicity;
2608                         if (multiplicity == RelationshipMultiplicity.Many)
2609                         {
2610                             relationshipEntry.DetachRelationshipEntry();
2611                         }
2612                     }
2613                 }
2614             }
2615         }
2616 
FixupRelationships()2617         private void FixupRelationships()
2618         {
2619             RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager;
2620             Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
2621             relationshipManager.RemoveEntityFromRelationships();
2622             DeleteRelationshipsThatReferenceKeys(null, null);
2623         }
2624 
2625         /// <summary>
2626         /// see if there are any relationship entries that point to key entries
2627         /// if there are, remove the relationship entry
2628         /// This is called when one of the ends of a relationship is being removed
2629         /// </summary>
2630         /// <param name="relationshipSet">An option relationshipSet; deletes only relationships that are part of this set</param>
DeleteRelationshipsThatReferenceKeys(RelationshipSet relationshipSet, RelationshipEndMember endMember)2631         internal void DeleteRelationshipsThatReferenceKeys(RelationshipSet relationshipSet, RelationshipEndMember endMember)
2632         {
2633             if (State != EntityState.Detached)
2634             {
2635                 // devnote: Need to use a copy of the relationships list because we may be deleting Added
2636                 //          relationships, which will be removed from the list while we are still iterating
2637                 foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(EntityKey))
2638                 {
2639                     // Only delete the relationship entry if it is not already deleted (in which case we cannot access its values)
2640                     // and when the given (optionally) relationshipSet matches the one in teh relationship entry
2641                     if ((relationshipEntry.State != EntityState.Deleted) &&
2642                         (relationshipSet == null || relationshipSet == relationshipEntry.EntitySet))
2643                     {
2644                         EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry);
2645                         if (endMember == null || endMember == otherEnd.GetAssociationEndMember(relationshipEntry))
2646                         {
2647                             for (int i = 0; i < 2; i++)
2648                             {
2649                                 EntityKey entityKey = relationshipEntry.GetCurrentRelationValue(i) as EntityKey;
2650                                 if ((object)entityKey != null)
2651                                 {
2652                                     EntityEntry relatedEntry = _cache.GetEntityEntry(entityKey);
2653                                     if (relatedEntry.IsKeyEntry)
2654                                     {
2655                                         // remove the relationshipEntry
2656                                         relationshipEntry.Delete(false);
2657                                         break;
2658                                     }
2659                                 }
2660                             }
2661                         }
2662                     }
2663                 }
2664             }
2665         }
2666 
2667         // Retrieve referential constraint properties from Principal entities (possibly recursively)
2668         // and check referential constraint properties in the Dependent entities (1 level only)
2669         // This code does not check the constraints on FKs because that work is instead done by
2670         // the FK fixup code that is also called from AcceptChanges.
2671         // Returns true if any FK relationships were skipped so that they can be checked again after fixup
RetrieveAndCheckReferentialConstraintValuesInAcceptChanges()2672         private bool RetrieveAndCheckReferentialConstraintValuesInAcceptChanges()
2673         {
2674             RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager;
2675             Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
2676             // Find key property names which are part of referential integrity constraints
2677             List<string> propertiesToRetrieve;  // names of properties which should be retrieved from Principal entities
2678             bool propertiesToCheckExist;        // true iff there are properties which should be checked in dependent entities
2679 
2680             // Get RI property names from metadata
2681             bool skippedFKs = relationshipManager.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToCheckExist, skipFK: true);
2682 
2683             // Do not try to retrieve RI properties if entity doesn't participate in any RI Constraints
2684             if (propertiesToRetrieve != null)
2685             {
2686                 // Retrieve key values from related entities
2687                 Dictionary<string, KeyValuePair<object, IntBox>> properties;
2688 
2689                 // Create HashSet to store references to already visited entities, used to detect circular references
2690                 HashSet<object> visited = new HashSet<object>();
2691 
2692                 relationshipManager.RetrieveReferentialConstraintProperties(out properties, visited, includeOwnValues: false);
2693 
2694                 // Update properties
2695                 foreach (KeyValuePair<string, KeyValuePair<object, IntBox>> pair in properties)
2696                 {
2697                     this.SetCurrentEntityValue(pair.Key /*name*/, pair.Value.Key /*value*/);
2698                 }
2699             }
2700 
2701             if (propertiesToCheckExist)
2702             {
2703                 // Compare properties of current entity with properties of the dependent entities
2704                 this.CheckReferentialConstraintPropertiesInDependents();
2705             }
2706             return skippedFKs;
2707         }
2708 
2709 
RetrieveReferentialConstraintPropertiesFromKeyEntries(Dictionary<string, KeyValuePair<object, IntBox>> properties)2710         internal void RetrieveReferentialConstraintPropertiesFromKeyEntries(Dictionary<string, KeyValuePair<object, IntBox>> properties)
2711         {
2712             string thisRole;
2713             AssociationSet association;
2714 
2715             // Iterate through related relationship entries
2716             foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey))
2717             {
2718                 EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry);
2719 
2720                 // We only try to retrieve properties from key entries
2721                 if (otherEnd.IsKeyEntry)
2722                 {
2723                     association = (AssociationSet)relationshipEntry.EntitySet;
2724                     Debug.Assert(association != null, "relationship is not an association");
2725 
2726                     // Iterate through referential constraints of the association of the relationship
2727                     // NOTE PERFORMANCE This collection in current stack can have 0 or 1 elements
2728                     foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
2729                     {
2730                         thisRole = this.GetAssociationEndMember(relationshipEntry).Name;
2731 
2732                         // Check if curent entry is a dependent end of the referential constraint
2733                         if (constraint.ToRole.Name == thisRole)
2734                         {
2735                             Debug.Assert(!otherEnd.EntityKey.IsTemporary, "key of key entry can't be temporary");
2736                             IList<EntityKeyMember> otherEndKeyValues = otherEnd.EntityKey.EntityKeyValues;
2737                             Debug.Assert(otherEndKeyValues != null, "key entry must have key values");
2738 
2739                             // NOTE PERFORMANCE Number of key properties is supposed to be "small"
2740                             foreach (EntityKeyMember pair in otherEndKeyValues)
2741                             {
2742                                 for (int i = 0; i < constraint.FromProperties.Count; ++i)
2743                                 {
2744                                     if (constraint.FromProperties[i].Name == pair.Key)
2745                                     {
2746                                         EntityEntry.AddOrIncreaseCounter(properties, constraint.ToProperties[i].Name, pair.Value);
2747                                     }
2748                                 }
2749                             }
2750                         }
2751                     }
2752                 }
2753             }
2754         }
2755 
AddOrIncreaseCounter(Dictionary<string, KeyValuePair<object, IntBox>> properties, string propertyName, object propertyValue)2756         internal static void AddOrIncreaseCounter(Dictionary<string, KeyValuePair<object, IntBox>> properties, string propertyName, object propertyValue)
2757         {
2758             Debug.Assert(properties != null);
2759             Debug.Assert(propertyName != null);
2760             Debug.Assert(propertyValue != null);
2761 
2762             if (properties.ContainsKey(propertyName))
2763             {
2764                 // If this property already exists in the dictionary, check if value is the same then increase the counter
2765 
2766                 KeyValuePair<object, IntBox> valueCounterPair = properties[propertyName];
2767 
2768                 if (!ByValueEqualityComparer.Default.Equals(valueCounterPair.Key, propertyValue))
2769                     throw EntityUtil.InconsistentReferentialConstraintProperties();
2770                 else
2771                     valueCounterPair.Value.Value = valueCounterPair.Value.Value + 1;
2772             }
2773             else
2774             {
2775                 // If property doesn't exist in the dictionary - add new entry with pair<value, counter>
2776                 properties[propertyName] = new KeyValuePair<object, IntBox>(propertyValue, new IntBox(1));
2777             }
2778         }
2779 
2780         // Check if related dependent entities contain proper property values
2781         // Only entities in Unchanged and Modified state are checked (including KeyEntries)
CheckReferentialConstraintPropertiesInDependents()2782         private void CheckReferentialConstraintPropertiesInDependents()
2783         {
2784             string thisRole;
2785             AssociationSet association;
2786 
2787             // Iterate through related relationship entries
2788             foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey))
2789             {
2790                 EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry);
2791 
2792                 // We only check entries which are in Unchanged or Modified state
2793                 // (including KeyEntries which are always in Unchanged State)
2794                 if (otherEnd.State == EntityState.Unchanged || otherEnd.State == EntityState.Modified)
2795                 {
2796                     association = (AssociationSet)relationshipEntry.EntitySet;
2797                     Debug.Assert(association != null, "relationship is not an association");
2798 
2799                     // Iterate through referential constraints of the association of the relationship
2800                     // NOTE PERFORMANCE This collection in current stack can have 0 or 1 elements
2801                     foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
2802                     {
2803                         thisRole = this.GetAssociationEndMember(relationshipEntry).Name;
2804 
2805                         // Check if curent entry is a principal end of the referential constraint
2806                         if (constraint.FromRole.Name == thisRole)
2807                         {
2808                             Debug.Assert(!otherEnd.EntityKey.IsTemporary, "key of Unchanged or Modified entry can't be temporary");
2809                             IList<EntityKeyMember> otherEndKeyValues = otherEnd.EntityKey.EntityKeyValues;
2810                             // NOTE PERFORMANCE Number of key properties is supposed to be "small"
2811                             foreach (EntityKeyMember pair in otherEndKeyValues)
2812                             {
2813                                 for (int i = 0; i < constraint.FromProperties.Count; ++i)
2814                                 {
2815                                     if (constraint.ToProperties[i].Name == pair.Key)
2816                                     {
2817                                         if (!ByValueEqualityComparer.Default.Equals(GetCurrentEntityValue(constraint.FromProperties[i].Name), pair.Value))
2818                                         {
2819                                             throw EntityUtil.InconsistentReferentialConstraintProperties();
2820                                         }
2821                                     }
2822                                 }
2823                             }
2824                         }
2825                     }
2826                 }
2827             }
2828         }
2829 
PromoteKeyEntry(IEntityWrapper wrappedEntity, IExtendedDataRecord shadowValues, StateManagerTypeMetadata typeMetadata)2830         internal void PromoteKeyEntry(IEntityWrapper wrappedEntity, IExtendedDataRecord shadowValues, StateManagerTypeMetadata typeMetadata)
2831         {
2832             Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null.");
2833             Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null.");
2834             Debug.Assert(this.IsKeyEntry, "ObjectStateEntry should be a key.");
2835             Debug.Assert(typeMetadata != null, "typeMetadata cannot be null.");
2836 
2837             _wrappedEntity = wrappedEntity;
2838             _wrappedEntity.ObjectStateEntry = this;
2839 
2840             // Allow updating of cached metadata because the actual entity might be a derived type
2841             _cacheTypeMetadata = typeMetadata;
2842 
2843             SetChangeTrackingFlags();
2844 
2845 #if DEBUG   // performance, don't do this work in retail until shadow state is supported
2846             if (shadowValues != null)
2847             {
2848                 // shadowState always  coms from materializer, just copy the shadowstate values
2849                 Debug.Assert(shadowValues.DataRecordInfo.RecordType.EdmType.Equals(_cacheTypeMetadata.CdmMetadata.EdmType), "different cspace metadata instance");
2850                 for (int ordinal = 0; ordinal < _cacheTypeMetadata.FieldCount; ordinal++)
2851                 {
2852                     if (_cacheTypeMetadata.IsMemberPartofShadowState(ordinal))
2853                     {
2854                         Debug.Assert(false, "shadowstate not supported");
2855                     }
2856                 }
2857             }
2858 #endif
2859         }
2860 
2861         /// <summary>
2862         /// Turns this entry into a key entry (SPAN stub).
2863         /// </summary>
DegradeEntry()2864         internal void DegradeEntry()
2865         {
2866             Debug.Assert(!this.IsKeyEntry);
2867             Debug.Assert((object)_entityKey != null);
2868 
2869             _entityKey = EntityKey; //Performs validation.
2870 
2871             RemoveFromForeignKeyIndex();
2872 
2873             _wrappedEntity.SetChangeTracker(null);
2874 
2875             _modifiedFields = null;
2876             _originalValues = null;
2877             _originalComplexObjects = null;
2878 
2879             // we don't want temporary keys to exist outside of the context
2880             if (State == EntityState.Added)
2881             {
2882                 _wrappedEntity.EntityKey = null;
2883                 _entityKey = null;
2884             }
2885 
2886             if (State != EntityState.Unchanged)
2887             {
2888                 _cache.ChangeState(this, this.State, EntityState.Unchanged);
2889                 State = EntityState.Unchanged;
2890             }
2891 
2892             _cache.RemoveEntryFromKeylessStore(_wrappedEntity);
2893             _wrappedEntity.DetachContext();
2894             _wrappedEntity.ObjectStateEntry = null;
2895 
2896             object degradedEntity = _wrappedEntity.Entity;
2897             _wrappedEntity = EntityWrapperFactory.NullWrapper;
2898 
2899             SetChangeTrackingFlags();
2900 
2901             _cache.OnObjectStateManagerChanged(CollectionChangeAction.Remove, degradedEntity);
2902 
2903             Debug.Assert(this.IsKeyEntry);
2904         }
2905 
AttachObjectStateManagerToEntity()2906         internal void AttachObjectStateManagerToEntity()
2907         {
2908             // This method should only be called in cases where we really have an entity to attach to
2909             Debug.Assert(_wrappedEntity.Entity != null, "Cannot attach a null entity to the state manager");
2910             _wrappedEntity.SetChangeTracker(this);
2911             _wrappedEntity.TakeSnapshot(this);
2912         }
2913 
2914         // Get values of key properties which doesn't already exist in passed in 'properties'
GetOtherKeyProperties(Dictionary<string, KeyValuePair<object, IntBox>> properties)2915         internal void GetOtherKeyProperties(Dictionary<string, KeyValuePair<object, IntBox>> properties)
2916         {
2917             Debug.Assert(properties != null);
2918             Debug.Assert(_cacheTypeMetadata != null);
2919             Debug.Assert(_cacheTypeMetadata.DataRecordInfo != null);
2920             Debug.Assert(_cacheTypeMetadata.DataRecordInfo.RecordType != null);
2921 
2922             EntityType entityType = _cacheTypeMetadata.DataRecordInfo.RecordType.EdmType as EntityType;
2923             Debug.Assert(entityType != null, "EntityType == null");
2924 
2925             foreach (EdmMember member in entityType.KeyMembers)
2926             {
2927                 if (!properties.ContainsKey(member.Name))
2928                 {
2929                     properties[member.Name] = new KeyValuePair<object, IntBox>(this.GetCurrentEntityValue(member.Name), new IntBox(1));
2930                 }
2931             }
2932         }
2933 
AddOriginalValue(StateManagerMemberMetadata memberMetadata, object userObject, object value)2934         internal void AddOriginalValue(StateManagerMemberMetadata memberMetadata, object userObject, object value)
2935         {
2936             if (null == _originalValues)
2937             {
2938                 _originalValues = new List<StateManagerValue>();
2939             }
2940             _originalValues.Add(new StateManagerValue(memberMetadata, userObject, value));
2941         }
2942 
CompareKeyProperties(object changed)2943         internal void CompareKeyProperties(object changed)
2944         {
2945             Debug.Assert(changed != null, "changed object is null");
2946             Debug.Assert(!this.IsKeyEntry);
2947 
2948             StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
2949 
2950             int fieldCount = this.GetFieldCount(metadata);
2951             object currentValueNew;
2952             object currentValueOld;
2953 
2954             for (int i = 0; i < fieldCount; i++)
2955             {
2956                 StateManagerMemberMetadata member = metadata.Member(i);
2957                 if (member.IsPartOfKey)
2958                 {
2959                     Debug.Assert(!member.IsComplex);
2960 
2961                     currentValueNew = member.GetValue(changed);
2962                     currentValueOld = member.GetValue(_wrappedEntity.Entity);
2963 
2964                     if (!ByValueEqualityComparer.Default.Equals(currentValueNew, currentValueOld))
2965                     {
2966                         throw EntityUtil.CannotModifyKeyProperty(member.CLayerName);
2967                     }
2968                 }
2969             }
2970         }
2971 
2972         // helper method used to get value of property
GetCurrentEntityValue(string memberName)2973         internal object GetCurrentEntityValue(string memberName)
2974         {
2975             int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName);
2976             return GetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.CurrentUpdatable);
2977         }
2978 
2979         /// <summary>
2980         /// Verifies that the property with the given ordinal is editable.
2981         /// </summary>
2982         /// <exception cref="InvalidOperationException">the property is not editable</exception>
VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, int ordinal, string memberName)2983         internal void VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, int ordinal, string memberName)
2984         {
2985             if (this.State == EntityState.Deleted)
2986             {
2987                 throw EntityUtil.CantModifyDetachedDeletedEntries();
2988             }
2989 
2990             Debug.Assert(typeMetadata != null, "Cannot verify entity or complex object is editable if typeMetadata is null.");
2991             StateManagerMemberMetadata member = typeMetadata.Member(ordinal);
2992 
2993             Debug.Assert(member != null, "Member shouldn't be null.");
2994 
2995             // Key fields are only editable if the entry is the Added state.
2996             if (member.IsPartOfKey && State != EntityState.Added)
2997             {
2998                 throw EntityUtil.CannotModifyKeyProperty(memberName);
2999             }
3000         }
3001 
3002         // This API are mainly for DbDataRecord implementations to get and set the values
3003         // also for loadoptions, setoldvalue will be used.
3004         // we should handle just for C-space, we will not recieve a call from O-space for set
3005         // We will not also return any value in term of O-Layer. all set and gets for us is in terms of C-layer.
3006         // the only O-layer interaction we have is through delegates from entity.
SetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)3007         internal void SetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)
3008         {
3009             // required to validate state because entity could be detatched from this context and added to another context
3010             // and we want this to fail instead of setting the value which would redirect to the other context
3011             ValidateState();
3012 
3013             StateManagerMemberMetadata member = metadata.Member(ordinal);
3014             Debug.Assert(member != null, "StateManagerMemberMetadata was not found for the given ordinal.");
3015 
3016             if (member.IsComplex)
3017             {
3018                 if (newValue == null || newValue == DBNull.Value)
3019                 {
3020                     throw EntityUtil.NullableComplexTypesNotSupported(member.CLayerName);
3021                 }
3022 
3023                 IExtendedDataRecord newValueRecord = newValue as IExtendedDataRecord;
3024                 if (newValueRecord == null)
3025                 {
3026                     throw EntityUtil.InvalidTypeForComplexTypeProperty("value");
3027                 }
3028 
3029                 newValue = _cache.ComplexTypeMaterializer.CreateComplex(newValueRecord, newValueRecord.DataRecordInfo, null);
3030             }
3031 
3032             _wrappedEntity.SetCurrentValue(this, member, ordinal, userObject, newValue);
3033         }
3034 
TransitionRelationshipsForAdd()3035         private void TransitionRelationshipsForAdd()
3036         {
3037             foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(this.EntityKey))
3038             {
3039                 // Unchanged -> Added
3040                 if (relationshipEntry.State == EntityState.Unchanged)
3041                 {
3042                     this.ObjectStateManager.ChangeState(relationshipEntry, EntityState.Unchanged, EntityState.Added);
3043                     relationshipEntry.State = EntityState.Added;
3044                 }
3045                 // Deleted -> Detached
3046                 else if (relationshipEntry.State == EntityState.Deleted)
3047                 {
3048                     // Remove key entry if necessary
3049                     relationshipEntry.DeleteUnnecessaryKeyEntries();
3050                     // Remove relationship entry
3051                     relationshipEntry.DetachRelationshipEntry();
3052                 }
3053             }
3054         }
3055 
3056         [Conditional("DEBUG")]
VerifyIsNotRelated()3057         private void VerifyIsNotRelated()
3058         {
3059             Debug.Assert(!this.IsKeyEntry, "shouldn't be called for a key entry");
3060 
3061             this.WrappedEntity.RelationshipManager.VerifyIsNotRelated();
3062         }
3063 
ChangeObjectState(EntityState requestedState)3064         internal void ChangeObjectState(EntityState requestedState)
3065         {
3066             if (this.IsKeyEntry)
3067             {
3068                 if (requestedState == EntityState.Unchanged)
3069                 {
3070                     return; // No-op
3071                 }
3072                 throw EntityUtil.CannotModifyKeyEntryState();
3073             }
3074 
3075             switch (this.State)
3076             {
3077                 case EntityState.Added:
3078                     switch (requestedState)
3079                     {
3080                         case EntityState.Added:
3081                             // Relationship fixup: Unchanged -> Added,  Deleted -> Detached
3082                             this.TransitionRelationshipsForAdd();
3083                             break;
3084                         case EntityState.Unchanged:
3085                             // Relationship fixup: none
3086                             this.AcceptChanges();
3087                             break;
3088                         case EntityState.Modified:
3089                             // Relationship fixup: none
3090                             this.AcceptChanges();
3091                             this.SetModified();
3092                             this.SetModifiedAll();
3093                             break;
3094                         case EntityState.Deleted:
3095                             // Need to forget conceptual nulls so that AcceptChanges does not throw.
3096                             // Note that there should always be no conceptual nulls left when we get into the Deleted state.
3097                             _cache.ForgetEntryWithConceptualNull(this, resetAllKeys: true);
3098                             // Relationship fixup: Added -> Detached, Unchanged -> Deleted
3099                             this.AcceptChanges();
3100                             // NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled
3101                             this.Delete(true);
3102                             break;
3103                         case EntityState.Detached:
3104                             // Relationship fixup: * -> Detached
3105                             this.Detach();
3106                             break;
3107                         default:
3108                             throw EntityUtil.InvalidEntityStateArgument("state");
3109                     }
3110                     break;
3111                 case EntityState.Unchanged:
3112                     switch (requestedState)
3113                     {
3114                         case EntityState.Added:
3115                             this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this);
3116                             this._modifiedFields = null;
3117                             this._originalValues = null;
3118                             this._originalComplexObjects = null;
3119                             this.State = EntityState.Added;
3120                             // Relationship fixup: Unchanged -> Added,  Deleted -> Detached
3121                             this.TransitionRelationshipsForAdd();
3122                             break;
3123                         case EntityState.Unchanged:
3124                             // Relationship fixup: none
3125                             break;
3126                         case EntityState.Modified:
3127                             // Relationship fixup: none
3128                             this.SetModified();
3129                             this.SetModifiedAll();
3130                             break;
3131                         case EntityState.Deleted:
3132                             // Relationship fixup: Added -> Detached,  Unchanged -> Deleted
3133                             // NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled
3134                             this.Delete(true);
3135                             break;
3136                         case EntityState.Detached:
3137                             // Relationship fixup: * -> Detached
3138                             this.Detach();
3139                             break;
3140                         default:
3141                             throw EntityUtil.InvalidEntityStateArgument("state");
3142                     }
3143                     break;
3144                 case EntityState.Modified:
3145                     switch (requestedState)
3146                     {
3147                         case EntityState.Added:
3148                             this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this);
3149                             this._modifiedFields = null;
3150                             this._originalValues = null;
3151                             this._originalComplexObjects = null;
3152                             this.State = EntityState.Added;
3153                             // Relationship fixup: Unchanged -> Added,  Deleted -> Detached
3154                             this.TransitionRelationshipsForAdd();
3155                             break;
3156                         case EntityState.Unchanged:
3157                             this.AcceptChanges();
3158                             // Relationship fixup: none
3159                             break;
3160                         case EntityState.Modified:
3161                             // Relationship fixup: none
3162                             this.SetModified();
3163                             this.SetModifiedAll();
3164                             break;
3165                         case EntityState.Deleted:
3166                             // Relationship fixup: Added -> Detached,  Unchanged -> Deleted
3167                             // NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled
3168                             this.Delete(true);
3169                             break;
3170                         case EntityState.Detached:
3171                             // Relationship fixup: * -> Detached
3172                             this.Detach();
3173                             break;
3174                         default:
3175                             throw EntityUtil.InvalidEntityStateArgument("state");
3176                     }
3177                     break;
3178                 case EntityState.Deleted:
3179                     switch (requestedState)
3180                     {
3181                         case EntityState.Added:
3182                             // Throw if the entry has some not-Deleted relationships
3183                             this.VerifyIsNotRelated();
3184                             this.TransitionRelationshipsForAdd();
3185                             this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this);
3186                             this._modifiedFields = null;
3187                             this._originalValues = null;
3188                             this._originalComplexObjects = null;
3189                             this.State = EntityState.Added;
3190                             _cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set
3191                             _cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity);
3192                             break;
3193                         case EntityState.Unchanged:
3194                             // Throw if the entry has some not-Deleted relationship
3195                             this.VerifyIsNotRelated();
3196                             this._modifiedFields = null;
3197                             this._originalValues = null;
3198                             this._originalComplexObjects = null;
3199 
3200                             this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Unchanged);
3201                             this.State = EntityState.Unchanged;
3202 
3203                             _wrappedEntity.TakeSnapshot(this); // refresh snapshot
3204 
3205                             _cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set
3206                             _cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity);
3207 
3208                             // Relationship fixup: none
3209                             break;
3210                         case EntityState.Modified:
3211                             // Throw if the entry has some not-Deleted relationship
3212                             this.VerifyIsNotRelated();
3213                             // Relationship fixup: none
3214                             this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Modified);
3215                             this.State = EntityState.Modified;
3216                             this.SetModifiedAll();
3217 
3218                             _cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set
3219                             _cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity);
3220 
3221                             break;
3222                         case EntityState.Deleted:
3223                             // No-op
3224                             break;
3225                         case EntityState.Detached:
3226                             // Relationship fixup: * -> Detached
3227                             this.Detach();
3228                             break;
3229                         default:
3230                             throw EntityUtil.InvalidEntityStateArgument("state");
3231                     }
3232                     break;
3233                 case EntityState.Detached:
3234                     Debug.Fail("detached entry");
3235                     break;
3236             }
3237         }
3238 
UpdateOriginalValues(object entity)3239         internal void UpdateOriginalValues(object entity)
3240         {
3241             Debug.Assert(EntityState.Added != this.State, "Cannot change original values of an entity in the Added state");
3242 
3243             EntityState oldState = this.State;
3244 
3245             this.UpdateRecordWithSetModified(entity, this.EditableOriginalValues);
3246 
3247             if (oldState == EntityState.Unchanged && this.State == EntityState.Modified)
3248             {
3249                 // The UpdateRecord changes state but doesn't update ObjectStateManager's dictionaries.
3250                 this.ObjectStateManager.ChangeState(this, oldState, EntityState.Modified);
3251             }
3252         }
3253 
UpdateRecordWithoutSetModified(object value, DbUpdatableDataRecord current)3254         internal void UpdateRecordWithoutSetModified(object value, DbUpdatableDataRecord current)
3255         {
3256             UpdateRecord(value, current, UpdateRecordBehavior.WithoutSetModified, s_EntityRoot);
3257         }
3258 
UpdateRecordWithSetModified(object value, DbUpdatableDataRecord current)3259         internal void UpdateRecordWithSetModified(object value, DbUpdatableDataRecord current)
3260         {
3261             UpdateRecord(value, current, UpdateRecordBehavior.WithSetModified, s_EntityRoot);
3262         }
3263 
3264         private enum UpdateRecordBehavior
3265         {
3266             WithoutSetModified,
3267             WithSetModified
3268         }
3269 
3270         internal const int s_EntityRoot = -1;
3271 
UpdateRecord(object value, DbUpdatableDataRecord current, UpdateRecordBehavior behavior, int propertyIndex)3272         private void UpdateRecord(object value, DbUpdatableDataRecord current, UpdateRecordBehavior behavior, int propertyIndex)
3273         {
3274             Debug.Assert(null != value, "null value");
3275             Debug.Assert(null != current, "null CurrentValueRecord");
3276             Debug.Assert(!(value is IEntityWrapper));
3277             Debug.Assert(propertyIndex == s_EntityRoot ||
3278                          propertyIndex >= 0, "Unexpected index. Use -1 if the passed value is an entity, not a complex type object");
3279 
3280             // get Metadata for type
3281             StateManagerTypeMetadata typeMetadata = current._metadata;
3282             DataRecordInfo recordInfo = typeMetadata.DataRecordInfo;
3283 
3284             foreach (FieldMetadata field in recordInfo.FieldMetadata)
3285             {
3286                 int index = field.Ordinal;
3287 
3288                 var member = typeMetadata.Member(index);
3289                 object fieldValue = member.GetValue(value) ?? DBNull.Value;
3290 
3291                 if (Helper.IsComplexType(field.FieldType.TypeUsage.EdmType))
3292                 {
3293                     object existing = current.GetValue(index);
3294                     // Ensure that the existing ComplexType value is not null. This is not supported.
3295                     if (existing == DBNull.Value)
3296                     {
3297                         throw EntityUtil.NullableComplexTypesNotSupported(field.FieldType.Name);
3298                     }
3299                     else if (fieldValue != DBNull.Value)
3300                     {
3301                         // There is both an IExtendedDataRecord and an existing CurrentValueRecord
3302 
3303                         // This part is different than Shaper.UpdateRecord - we have to remember the name of property on the entity (for complex types)
3304                         // For property of a complex type the rootCLayerName is CLayerName of the complex property on the entity.
3305                         this.UpdateRecord(fieldValue, (DbUpdatableDataRecord)existing,
3306                             behavior,
3307                             propertyIndex == s_EntityRoot ? index : propertyIndex);
3308                     }
3309                 }
3310                 else
3311                 {
3312                     Debug.Assert(Helper.IsScalarType(field.FieldType.TypeUsage.EdmType), "Expected primitive or enum type.");
3313 
3314                     // Set the new value if it doesn't match the existing value or if the field is modified, not a primary key, and
3315                     // this entity has a conceptual null, since setting the field may then clear the conceptual null--see 640443.
3316                     if (HasRecordValueChanged(current, index, fieldValue) && !member.IsPartOfKey)
3317                     {
3318                         current.SetValue(index, fieldValue);
3319 
3320                         if (behavior == UpdateRecordBehavior.WithSetModified)
3321                         {
3322                             // This part is different than Shaper.UpdateRecord - we have to mark the field as modified.
3323                             // For property of a complex type the rootCLayerName is CLayerName of the complex property on the entity.
3324                             SetModifiedPropertyInternal(propertyIndex == s_EntityRoot ? index : propertyIndex);
3325                         }
3326                     }
3327                 }
3328             }
3329         }
3330 
HasRecordValueChanged(DbDataRecord record, int propertyIndex, object newFieldValue)3331         internal bool HasRecordValueChanged(DbDataRecord record, int propertyIndex, object newFieldValue)
3332         {
3333             object existing = record.GetValue(propertyIndex);
3334             return (existing != newFieldValue) &&
3335                 (((object)DBNull.Value == newFieldValue) ||
3336                  ((object)DBNull.Value == existing) ||
3337                  (!ByValueEqualityComparer.Default.Equals(existing, newFieldValue))) ||
3338                 (_cache.EntryHasConceptualNull(this) && _modifiedFields != null && _modifiedFields[propertyIndex]);
3339         }
3340 
ApplyCurrentValuesInternal(IEntityWrapper wrappedCurrentEntity)3341         internal void ApplyCurrentValuesInternal(IEntityWrapper wrappedCurrentEntity)
3342         {
3343             Debug.Assert(!IsKeyEntry, "Cannot apply values to a key KeyEntry.");
3344             Debug.Assert(wrappedCurrentEntity != null, "null entity wrapper");
3345 
3346             if (this.State != EntityState.Modified &&
3347                 this.State != EntityState.Unchanged)
3348             {
3349                 throw EntityUtil.EntityMustBeUnchangedOrModified(this.State);
3350             }
3351 
3352             if (this.WrappedEntity.IdentityType != wrappedCurrentEntity.IdentityType)
3353             {
3354                 throw EntityUtil.EntitiesHaveDifferentType(this.Entity.GetType().FullName, wrappedCurrentEntity.Entity.GetType().FullName);
3355             }
3356 
3357             this.CompareKeyProperties(wrappedCurrentEntity.Entity);
3358 
3359             UpdateCurrentValueRecord(wrappedCurrentEntity.Entity);
3360         }
3361 
UpdateCurrentValueRecord(object value)3362         internal void UpdateCurrentValueRecord(object value)
3363         {
3364             Debug.Assert(!(value is IEntityWrapper));
3365             _wrappedEntity.UpdateCurrentValueRecord(value, this);
3366         }
3367 
ApplyOriginalValuesInternal(IEntityWrapper wrappedOriginalEntity)3368         internal void ApplyOriginalValuesInternal(IEntityWrapper wrappedOriginalEntity)
3369         {
3370             Debug.Assert(!IsKeyEntry, "Cannot apply values to a key KeyEntry.");
3371             Debug.Assert(wrappedOriginalEntity != null, "null entity wrapper");
3372 
3373             if (this.State != EntityState.Modified &&
3374                 this.State != EntityState.Unchanged &&
3375                 this.State != EntityState.Deleted)
3376             {
3377                 throw EntityUtil.EntityMustBeUnchangedOrModifiedOrDeleted(this.State);
3378             }
3379 
3380             if (this.WrappedEntity.IdentityType != wrappedOriginalEntity.IdentityType)
3381             {
3382                 throw EntityUtil.EntitiesHaveDifferentType(this.Entity.GetType().FullName, wrappedOriginalEntity.Entity.GetType().FullName);
3383             }
3384 
3385             this.CompareKeyProperties(wrappedOriginalEntity.Entity);
3386 
3387             // The ObjectStateEntry.UpdateModifiedFields uses a variation of Shaper.UpdateRecord method
3388             // which additionaly marks properties as modified as necessary.
3389             this.UpdateOriginalValues(wrappedOriginalEntity.Entity);
3390         }
3391 
3392         /// <summary>
3393         /// For each FK contained in this entry, the entry is removed from the index maintained by
3394         /// the ObjectStateManager for that key.
3395         /// </summary>
RemoveFromForeignKeyIndex()3396         internal void RemoveFromForeignKeyIndex()
3397         {
3398             if (!this.IsKeyEntry)
3399             {
3400                 foreach (EntityReference relatedEnd in FindFKRelatedEnds())
3401                 {
3402                     foreach(EntityKey foreignKey in relatedEnd.GetAllKeyValues())
3403                     {
3404                         _cache.RemoveEntryFromForeignKeyIndex(foreignKey, this);
3405                     }
3406                 }
3407                 _cache.AssertEntryDoesNotExistInForeignKeyIndex(this);
3408             }
3409         }
3410 
3411         /// <summary>
3412         /// Looks at the foreign keys contained in this entry and performs fixup to the entities that
3413         /// they reference, or adds the key and this entry to the index of foreign keys that reference
3414         /// entities that we don't yet know about.
3415         /// </summary>
FixupReferencesByForeignKeys(bool replaceAddedRefs)3416         internal void FixupReferencesByForeignKeys(bool replaceAddedRefs)
3417         {
3418             Debug.Assert(_cache != null, "Attempt to fixup detached entity entry");
3419             _cache.TransactionManager.BeginGraphUpdate();
3420             bool setIsLoaded = !(_cache.TransactionManager.IsAttachTracking || _cache.TransactionManager.IsAddTracking);
3421             try
3422             {
3423                 foreach (var dependent in ForeignKeyDependents)
3424                 {
3425                     EntityReference relatedEnd = WrappedEntity.RelationshipManager.GetRelatedEndInternal(
3426                         dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
3427                     Debug.Assert(relatedEnd != null, "Expected non-null EntityReference to principal.");
3428                     // Prevent fixup using values that are effectivly null but aren't nullable.
3429                     if (!ForeignKeyFactory.IsConceptualNullKey(relatedEnd.CachedForeignKey))
3430                     {
3431                         FixupEntityReferenceToPrincipal(relatedEnd, null, setIsLoaded, replaceAddedRefs);
3432                     }
3433                 }
3434             }
3435             finally
3436             {
3437                 _cache.TransactionManager.EndGraphUpdate();
3438             }
3439         }
3440 
FixupEntityReferenceByForeignKey(EntityReference reference)3441         internal void FixupEntityReferenceByForeignKey(EntityReference reference)
3442         {
3443             // The FK is changing, so the reference is no longer loaded from the store, even if we do fixup
3444             reference.SetIsLoaded(false);
3445 
3446             // Remove the existing CachedForeignKey
3447             bool hasConceptualNullFk = ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey);
3448             if (hasConceptualNullFk)
3449             {
3450                 ObjectStateManager.ForgetEntryWithConceptualNull(this, resetAllKeys: false);
3451             }
3452 
3453             IEntityWrapper existingPrincipal = reference.ReferenceValue;
3454             EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, reference);
3455 
3456             // Check if the new FK matches the key of the entity already at the principal end.
3457             // If it does, then don't change the ref.
3458             bool needToSetRef;
3459             if ((object)foreignKey == null || existingPrincipal.Entity == null)
3460             {
3461                 needToSetRef = true;
3462             }
3463             else
3464             {
3465                 EntityKey existingPrincipalKey = existingPrincipal.EntityKey;
3466                 EntityEntry existingPrincipalEntry = existingPrincipal.ObjectStateEntry;
3467                 // existingPrincipalKey may be null if this fixup code is being called in the middle of
3468                 // adding an object.  This can happen when using change tracking proxies with fixup.
3469                 if ((existingPrincipalKey == null || existingPrincipalKey.IsTemporary) && existingPrincipalEntry != null)
3470                 {
3471                     // Build a temporary non-temp key for the added entity so we can see if it matches the new FK
3472                     existingPrincipalKey = new EntityKey((EntitySet)existingPrincipalEntry.EntitySet, (IExtendedDataRecord)existingPrincipalEntry.CurrentValues);
3473                 }
3474 
3475                 // If existingPrincipalKey is still a temp key here, then the equality check will fail
3476                 needToSetRef = !foreignKey.Equals(existingPrincipalKey);
3477             }
3478 
3479             if (_cache.TransactionManager.RelationshipBeingUpdated != reference)
3480             {
3481                 if (needToSetRef)
3482                 {
3483                     ObjectStateManager stateManager = _cache;
3484                     _cache.TransactionManager.BeginGraphUpdate();
3485                     // Keep track of this entity so that we don't try to delete/detach the entity while we're
3486                     // working with it.  This allows the FK to be set to some value without that entity being detached.
3487                     // However, if the FK is being set to null, then for an identifying relationship we will detach.
3488                     if ((object)foreignKey != null)
3489                     {
3490                         _cache.TransactionManager.EntityBeingReparented = Entity;
3491                     }
3492                     try
3493                     {
3494                         FixupEntityReferenceToPrincipal(reference, foreignKey, setIsLoaded: false, replaceExistingRef: true);
3495                     }
3496                     finally
3497                     {
3498                         Debug.Assert(_cache != null, "Unexpected null state manager.");
3499                         _cache.TransactionManager.EntityBeingReparented = null;
3500                         _cache.TransactionManager.EndGraphUpdate();
3501                     }
3502                 }
3503             }
3504             else
3505             {
3506                 // We only want to update the CachedForeignKey and not touch the EntityReference.Value/EntityKey
3507                 FixupEntityReferenceToPrincipal(reference, foreignKey, setIsLoaded: false, replaceExistingRef: false);
3508             }
3509         }
3510 
3511         /// <summary>
3512         /// Given a RelatedEnd that represents a FK from this dependent entity to the principal entity of the
3513         /// relationship, this method fixes up references between the two entities.
3514         /// </summary>
3515         /// <param name="relatedEnd">Represents a FK relationship to a principal</param>
3516         /// <param name="foreignKey">The foreign key, if it has already been computed</param>
3517         /// <param name="setIsLoaded">If true, then the IsLoaded flag for the relationship is set</param>
3518         /// <param name="replaceExistingRef">If true, then any existing references will be replaced</param>
FixupEntityReferenceToPrincipal(EntityReference relatedEnd, EntityKey foreignKey, bool setIsLoaded, bool replaceExistingRef)3519         internal void FixupEntityReferenceToPrincipal(EntityReference relatedEnd, EntityKey foreignKey, bool setIsLoaded, bool replaceExistingRef)
3520         {
3521             Debug.Assert(relatedEnd != null, "Found null RelatedEnd or EntityCollection to principal");
3522             if (foreignKey == null)
3523             {
3524                 foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, relatedEnd);
3525             }
3526             // Note that if we're not changing FKs directly, but rather as a result of fixup after a ref has changed,
3527             // and if the entity currently being pointed to is Added, then we shouldn't clobber it, because a ref to
3528             // an Added entity wins in this case.
3529             bool canModifyReference = _cache.TransactionManager.RelationshipBeingUpdated != relatedEnd &&
3530                                       (!_cache.TransactionManager.IsForeignKeyUpdate ||
3531                                        relatedEnd.ReferenceValue.ObjectStateEntry == null ||
3532                                        relatedEnd.ReferenceValue.ObjectStateEntry.State != EntityState.Added);
3533 
3534             //
3535 
3536             relatedEnd.SetCachedForeignKey(foreignKey, this);
3537             ObjectStateManager.ForgetEntryWithConceptualNull(this, resetAllKeys: false);
3538             if (foreignKey != null) // Implies no value is null or CreateKeyFromForeignKeyValues would have returned null
3539             {
3540                 // Lookup key in OSM.  If found, then we can do fixup.  If not, then need to add to index
3541                 // Should not overwrite a reference at this point since this might cause the graph to
3542                 // be shredded.  This allows us to correctly detect key violations or RIC violations later.
3543                 EntityEntry principalEntry;
3544                 if (_cache.TryGetEntityEntry(foreignKey, out principalEntry) &&
3545                     !principalEntry.IsKeyEntry &&
3546                     principalEntry.State != EntityState.Deleted &&
3547                     (replaceExistingRef || WillNotRefSteal(relatedEnd, principalEntry.WrappedEntity)) &&
3548                     relatedEnd.CanSetEntityType(principalEntry.WrappedEntity))
3549                 {
3550                     if (canModifyReference)
3551                     {
3552                         // We add both sides to the promoted EntityKeyRefs collection because it could be the dependent or
3553                         // the principal or both that are being added.  Having extra members in this index doesn't hurt.
3554                         if (_cache.TransactionManager.PopulatedEntityReferences != null)
3555                         {
3556                             Debug.Assert(_cache.TransactionManager.IsAddTracking || _cache.TransactionManager.IsAttachTracking,
3557                                 "PromotedEntityKeyRefs is non-null while not tracking add or attach");
3558                             _cache.TransactionManager.PopulatedEntityReferences.Add(relatedEnd);
3559                         }
3560 
3561                         // Set the EntityKey on the RelatedEnd--this will cause the reference to be set and fixup to happen.
3562                         relatedEnd.SetEntityKey(foreignKey, forceFixup: true);
3563 
3564                         if (_cache.TransactionManager.PopulatedEntityReferences != null)
3565                         {
3566                             EntityReference otherEnd = relatedEnd.GetOtherEndOfRelationship(principalEntry.WrappedEntity) as EntityReference;
3567                             if (otherEnd != null)
3568                             {
3569                                 _cache.TransactionManager.PopulatedEntityReferences.Add(otherEnd);
3570                             }
3571                         }
3572                     }
3573                     if (setIsLoaded && principalEntry.State != EntityState.Added)
3574                     {
3575                         relatedEnd.SetIsLoaded(true);
3576                     }
3577                 }
3578                 else
3579                 {
3580                     // Add an entry to the index for later fixup
3581                     _cache.AddEntryContainingForeignKeyToIndex(foreignKey, this);
3582                     if (canModifyReference && replaceExistingRef && relatedEnd.ReferenceValue.Entity != null)
3583                     {
3584                         relatedEnd.ReferenceValue = EntityWrapperFactory.NullWrapper;
3585                     }
3586                 }
3587             }
3588             else if(canModifyReference)
3589             {
3590                 if (replaceExistingRef && (relatedEnd.ReferenceValue.Entity != null || relatedEnd.EntityKey != null))
3591                 {
3592                     relatedEnd.ReferenceValue = EntityWrapperFactory.NullWrapper;
3593                 }
3594                 if (setIsLoaded)
3595                 {
3596                     // This is the case where a query comes from the database with a null FK value.
3597                     // We know that there is no related entity in the database and therefore the entity on the
3598                     // other end of the relationship is as loaded as it is possible to be.  Therefore, we
3599                     // set the IsLoaded flag so that if a user asks we will tell them that (based on last known
3600                     // state of the database) there is no need to do a load.
3601                     relatedEnd.SetIsLoaded(true);
3602                 }
3603             }
3604         }
3605 
3606         /// <summary>
3607         /// Determins whether or not setting a reference will cause implicit ref stealing as part of FK fixup.
3608         /// If it would, then an exception is thrown.  If it would not and we can safely overwrite the existing
3609         /// value, then true is returned.  If it would not but we should not overwrite the existing value,
3610         /// then false is returned.
WillNotRefSteal(EntityReference refToPrincipal, IEntityWrapper wrappedPrincipal)3611         private bool WillNotRefSteal(EntityReference refToPrincipal, IEntityWrapper wrappedPrincipal)
3612         {
3613             RelatedEnd dependentEnd = refToPrincipal.GetOtherEndOfRelationship(wrappedPrincipal);
3614             EntityReference refToDependent = dependentEnd as EntityReference;
3615             if ((refToPrincipal.ReferenceValue.Entity == null && refToPrincipal.NavigationPropertyIsNullOrMissing()) &&
3616                 (refToDependent == null || (refToDependent.ReferenceValue.Entity == null && refToDependent.NavigationPropertyIsNullOrMissing())))
3617             {
3618                 // Return true if the ref to principal is null and it's not 1:1 or it is 1:1 and the ref to dependent is also null.
3619                 return true;
3620             }
3621             else if (refToDependent != null &&
3622                      (Object.ReferenceEquals(refToDependent.ReferenceValue.Entity, refToPrincipal.WrappedOwner.Entity) ||
3623                       refToDependent.CheckIfNavigationPropertyContainsEntity(refToPrincipal.WrappedOwner)))
3624             {
3625                 return true;
3626             }
3627             else if (refToDependent == null ||
3628                      Object.ReferenceEquals(refToPrincipal.ReferenceValue.Entity, wrappedPrincipal.Entity) ||
3629                      refToPrincipal.CheckIfNavigationPropertyContainsEntity(wrappedPrincipal))
3630             {
3631                 // Return false if the ref to principal is non-null and it's not 1:1
3632                 return false;
3633             }
3634             else
3635             {
3636                 // Else it is 1:1 and one side or the other is non-null => reference steal!
3637                 throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(
3638                     refToDependent.RelationshipNavigation.To,
3639                     refToDependent.RelationshipNavigation.RelationshipName);
3640             }
3641         }
3642 
3643         /// <summary>
3644         /// Given that this entry represents an entity on the dependent side of a FK, this method attempts to return the key of the
3645         /// entity on the principal side of the FK.  If the two entities both exist in the context, then the primary key of
3646         /// the principal entity is found and returned.  If the principal entity does not exist in the context, then a key
3647         /// for it is built up from the foreign key values contained in the dependent entity.
3648         /// </summary>
3649         /// <param name="principalRole">The role indicating the FK to navigate</param>
3650         /// <param name="principalKey">Set to the principal key or null on return</param>
3651         /// <returns>True if the principal key was found or built; false if it could not be found or built</returns>
TryGetReferenceKey(AssociationEndMember principalRole, out EntityKey principalKey)3652         internal bool TryGetReferenceKey(AssociationEndMember principalRole, out EntityKey principalKey)
3653         {
3654             EntityReference relatedEnd = (RelatedEnd)RelationshipManager.GetRelatedEnd(principalRole.DeclaringType.FullName, principalRole.Name) as EntityReference;
3655             Debug.Assert(relatedEnd != null, "Expected there to be a non null EntityReference to the principal");
3656             if (relatedEnd.CachedValue.Entity == null || relatedEnd.CachedValue.ObjectStateEntry == null)
3657             {
3658                 principalKey = null;
3659                 return false;
3660             }
3661             //
3662             principalKey = relatedEnd.EntityKey ?? relatedEnd.CachedValue.ObjectStateEntry.EntityKey;
3663             return principalKey != null;
3664         }
3665 
3666         /// <summary>
3667         /// Performs fixuyup of foreign keys based on referencesd between objects.  This should only be called
3668         /// for Added objects since this is the only time that references take precedence over FKs in fixup.
3669         /// </summary>
FixupForeignKeysByReference()3670         internal void FixupForeignKeysByReference()
3671         {
3672             Debug.Assert(_cache != null, "Attempt to fixup detached entity entry");
3673             _cache.TransactionManager.BeginFixupKeysByReference();
3674             try
3675             {
3676                 FixupForeignKeysByReference(null);
3677             }
3678             finally
3679             {
3680                 _cache.TransactionManager.EndFixupKeysByReference();
3681             }
3682         }
3683 
3684         /// <summary>
3685         /// Fixup the FKs by the current reference values
3686         /// Do this in the order of fixing up values from the principal ends first, and then propogate those values to the dependents
3687         /// </summary>
3688         /// <param name="visited"></param>
FixupForeignKeysByReference(List<EntityEntry> visited)3689         private void FixupForeignKeysByReference(List<EntityEntry> visited)
3690         {
3691             EntitySet entitySet = EntitySet as EntitySet;
3692 
3693             // Perf optimization to avoid all this work if the entity doesn't participate in any FK relationships
3694             if (!entitySet.HasForeignKeyRelationships)
3695             {
3696                 return;
3697             }
3698 
3699             foreach (var dependent in ForeignKeyDependents)
3700             {
3701                 // Added dependent.  Make sure we traverse all the way to the top-most principal before beginging fixup.
3702                 EntityReference reference = RelationshipManager.GetRelatedEndInternal(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
3703                 Debug.Assert(reference != null, "Expected reference to exist and be an entity reference (not collection)");
3704                 IEntityWrapper existingPrincipal = reference.ReferenceValue;
3705                 if (existingPrincipal.Entity != null)
3706                 {
3707                     EntityEntry principalEntry = existingPrincipal.ObjectStateEntry;
3708                     bool? isOneToMany = null;
3709                     if (principalEntry != null && principalEntry.State == EntityState.Added &&
3710                         (principalEntry != this || (isOneToMany = reference.GetOtherEndOfRelationship(existingPrincipal) is EntityReference).Value))
3711                     {
3712                         visited = visited ?? new List<EntityEntry>();
3713                         if (visited.Contains(this))
3714                         {
3715                             if (!isOneToMany.HasValue)
3716                             {
3717                                 isOneToMany = reference.GetOtherEndOfRelationship(existingPrincipal) is EntityReference;
3718                             }
3719                             if (isOneToMany.Value)
3720                             {
3721                                 // Cycles in constraints are dissallowed except for 1:* self references
3722                                 throw EntityUtil.CircularRelationshipsWithReferentialConstraints();
3723                             }
3724                         }
3725                         else
3726                         {
3727                             visited.Add(this);
3728                             principalEntry.FixupForeignKeysByReference(visited);
3729                             visited.Remove(this);
3730                         }
3731                     }
3732                     // "forceChange" is false because we don't want to actually set the property values
3733                     // here if they are aready set to the same thing--we don't want the events and setting
3734                     // the modified flag is irrelavent during AcceptChanges.
3735                     reference.UpdateForeignKeyValues(this.WrappedEntity, existingPrincipal, changedFKs: null, forceChange: false);
3736                 }
3737                 else
3738                 {
3739                     EntityKey principalKey = reference.EntityKey;
3740                     if (principalKey != null && !principalKey.IsTemporary)
3741                     {
3742                         reference.UpdateForeignKeyValues(this.WrappedEntity, principalKey);
3743                     }
3744                 }
3745             }
3746 
3747             foreach (var principal in ForeignKeyPrincipals)
3748             {
3749                 // Added prinipal end.  Fixup FKs on all dependents.
3750                 // This is necessary because of the case where a PK in an added entity is changed after it and its dependnents
3751                 // are added to the context--see bug 628752.
3752                 bool fkOverlapsPk = false; // Set to true if we find out that the FK overlaps the dependent PK
3753                 bool dependentPropsChecked = false; // Set to true once we have checked whether or not the FK overlaps the PK
3754                 EntityKey principalKey = WrappedEntity.EntityKey;
3755                 RelatedEnd principalEnd = RelationshipManager.GetRelatedEndInternal(principal.Item1.ElementType.FullName, principal.Item2.ToRole.Name);
3756                 foreach (IEntityWrapper dependent in principalEnd.GetWrappedEntities())
3757                 {
3758                     EntityEntry dependentEntry = dependent.ObjectStateEntry;
3759                     Debug.Assert(dependentEntry != null, "Should have fully tracked graph at this point.");
3760                     if (dependentEntry.State != EntityState.Added && !dependentPropsChecked)
3761                     {
3762                         dependentPropsChecked = true;
3763                         foreach (EdmProperty dependentProp in principal.Item2.ToProperties)
3764                         {
3765                             int dependentOrdinal = dependentEntry._cacheTypeMetadata.GetOrdinalforOLayerMemberName(dependentProp.Name);
3766                             StateManagerMemberMetadata member = dependentEntry._cacheTypeMetadata.Member(dependentOrdinal);
3767                             if (member.IsPartOfKey)
3768                             {
3769                                 // If the FK overlpas the PK then we can't set it for non-Added entities.
3770                                 // In this situation we just continue with the next one and if the conflict
3771                                 // may then be flagged later as a RIC check.
3772                                 fkOverlapsPk = true;
3773                                 break;
3774                             }
3775                         }
3776                     }
3777                     // This code relies on the fact that a dependent referenced to an Added principal must be either Added or
3778                     // Modified since we cannpt trust thestate of the principal PK and therefore the dependent FK must also
3779                     // be considered not completely trusted--it may need to be updated.
3780                     if (dependentEntry.State == EntityState.Added || (dependentEntry.State == EntityState.Modified && !fkOverlapsPk))
3781                     {
3782                         EntityReference principalRef = principalEnd.GetOtherEndOfRelationship(dependent) as EntityReference;
3783                         Debug.Assert(principalRef != null, "Expected reference to exist and be an entity reference (not collection)");
3784                         // "forceChange" is false because we don't want to actually set the property values
3785                         // here if they are aready set to the same thing--we don't want the events and setting
3786                         // the modified flag is irrelavent during AcceptChanges.
3787                         principalRef.UpdateForeignKeyValues(dependent, WrappedEntity, changedFKs: null, forceChange: false);
3788                     }
3789                 }
3790             }
3791         }
3792 
IsPropertyAForeignKey(string propertyName)3793         private bool IsPropertyAForeignKey(string propertyName)
3794         {
3795             foreach (var dependent in ForeignKeyDependents)
3796             {
3797                 foreach (EdmProperty property in dependent.Item2.ToProperties)
3798                 {
3799                     if (property.Name == propertyName)
3800                     {
3801                         return true;
3802                     }
3803                 }
3804             }
3805             return false;
3806         }
3807 
IsPropertyAForeignKey(string propertyName, out List<Pair<string, string>> relationships)3808         private bool IsPropertyAForeignKey(string propertyName, out List<Pair<string, string>> relationships)
3809         {
3810             relationships = null;
3811 
3812             foreach (var dependent in ForeignKeyDependents)
3813             {
3814                 foreach (EdmProperty property in dependent.Item2.ToProperties)
3815                 {
3816                     if (property.Name == propertyName)
3817                     {
3818                         if (relationships == null)
3819                         {
3820                             relationships = new List<Pair<string, string>>();
3821                         }
3822                         relationships.Add(new Pair<string, string>(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name));
3823                         break;
3824                     }
3825                 }
3826             }
3827 
3828             return relationships != null;
3829         }
3830 
FindRelatedEntityKeysByForeignKeys( out Dictionary<RelatedEnd, HashSet<EntityKey>> relatedEntities, bool useOriginalValues)3831         internal void FindRelatedEntityKeysByForeignKeys(
3832             out Dictionary<RelatedEnd, HashSet<EntityKey>> relatedEntities,
3833             bool useOriginalValues)
3834         {
3835             relatedEntities = null;
3836 
3837             foreach (var dependent in ForeignKeyDependents)
3838             {
3839                 AssociationSet associationSet = dependent.Item1;
3840                 ReferentialConstraint constraint = dependent.Item2;
3841                 // Get association end members for the dependent and the principal ends
3842                 string dependentId = constraint.ToRole.Identity;
3843                 var setEnds = associationSet.AssociationSetEnds;
3844                 Debug.Assert(associationSet.AssociationSetEnds.Count == 2, "Expected an association set with only two ends.");
3845                 AssociationEndMember dependentEnd;
3846                 AssociationEndMember principalEnd;
3847                 if (setEnds[0].CorrespondingAssociationEndMember.Identity == dependentId)
3848                 {
3849                     dependentEnd = setEnds[0].CorrespondingAssociationEndMember;
3850                     principalEnd = setEnds[1].CorrespondingAssociationEndMember;
3851                 }
3852                 else
3853                 {
3854                     dependentEnd = setEnds[1].CorrespondingAssociationEndMember;
3855                     principalEnd = setEnds[0].CorrespondingAssociationEndMember;
3856                 }
3857 
3858                 EntitySet principalEntitySet = MetadataHelper.GetEntitySetAtEnd(associationSet, principalEnd);
3859                 EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, constraint, principalEntitySet, useOriginalValues);
3860                 if (foreignKey != null) // Implies no value is null or CreateKeyFromForeignKeyValues would have returned null
3861                 {
3862                     EntityReference reference = RelationshipManager.GetRelatedEndInternal(
3863                         associationSet.ElementType.FullName, constraint.FromRole.Name) as EntityReference;
3864 
3865                     // only for deleted relationships the hashset can have > 1 elements
3866                     HashSet<EntityKey> entityKeys;
3867                     relatedEntities = relatedEntities != null ? relatedEntities : new Dictionary<RelatedEnd, HashSet<EntityKey>>();
3868                     if (!relatedEntities.TryGetValue(reference, out entityKeys))
3869                     {
3870                         entityKeys = new HashSet<EntityKey>();
3871                         relatedEntities.Add(reference, entityKeys);
3872                     }
3873                     entityKeys.Add(foreignKey);
3874                 }
3875             }
3876         }
3877 
3878         /// <summary>
3879         /// Returns a list of all RelatedEnds for this entity
3880         /// that are the dependent end of an FK Association
3881         /// </summary>
FindFKRelatedEnds()3882         internal IEnumerable<EntityReference> FindFKRelatedEnds()
3883         {
3884             HashSet<EntityReference> relatedEnds = new HashSet<EntityReference>();
3885 
3886             foreach (var dependent in ForeignKeyDependents)
3887             {
3888                 EntityReference reference = RelationshipManager.GetRelatedEndInternal(
3889                     dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
3890                 relatedEnds.Add(reference);
3891             }
3892             return relatedEnds;
3893         }
3894 
3895         /// <summary>
3896         /// Identifies any changes in FK's and creates entries in;
3897         /// - TransactionManager.AddedRelationshipsByForeignKey
3898         /// - TransactionManager.DeletedRelationshipsByForeignKey
3899         ///
3900         /// If the FK change will result in fix-up then two entries
3901         /// are added to TransactionManager.AddedRelationshipsByForeignKey
3902         /// (one for each direction of the new realtionship)
3903         /// </summary>
DetectChangesInForeignKeys()3904         internal void DetectChangesInForeignKeys()
3905         {
3906             //DetectChangesInProperties should already have marked this entity as dirty
3907             Debug.Assert(this.State == EntityState.Added || this.State == EntityState.Modified, "unexpected state");
3908 
3909             //We are going to be adding data to the TransactionManager
3910             TransactionManager tm = this.ObjectStateManager.TransactionManager;
3911 
3912             foreach (EntityReference entityReference in this.FindFKRelatedEnds())
3913             {
3914                 EntityKey currentKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, entityReference);
3915                 EntityKey originalKey = entityReference.CachedForeignKey;
3916                 bool originalKeyIsConceptualNull = ForeignKeyFactory.IsConceptualNullKey(originalKey);
3917 
3918                 //If both keys are null there is nothing to check
3919                 if (originalKey != null || currentKey != null)
3920                 {
3921                     if (originalKey == null)
3922                     {
3923                         //If original is null then we are just adding a relationship
3924                         EntityEntry entry;
3925                         this.ObjectStateManager.TryGetEntityEntry(currentKey, out entry);
3926                         this.AddRelationshipDetectedByForeignKey(tm.AddedRelationshipsByForeignKey, tm.AddedRelationshipsByPrincipalKey, currentKey, entry, entityReference);
3927                     }
3928                     else if (currentKey == null)
3929                     {
3930                         //If current is null we are just deleting a relationship
3931                         Debug.Assert(!originalKeyIsConceptualNull, "If FK is nullable there shouldn't be a conceptual null set");
3932                         this.AddDetectedRelationship(tm.DeletedRelationshipsByForeignKey, originalKey, entityReference);
3933                     }
3934                     //If there is a Conceptual Null set we need to check if the current values
3935                     //are different from the values when the Conceptual Null was created
3936                     else if (!currentKey.Equals(originalKey)
3937                         && (!originalKeyIsConceptualNull || ForeignKeyFactory.IsConceptualNullKeyChanged(originalKey, currentKey)))
3938                     {
3939                         //If keys don't match then we are always adding
3940                         EntityEntry entry;
3941                         this.ObjectStateManager.TryGetEntityEntry(currentKey, out entry);
3942                         this.AddRelationshipDetectedByForeignKey(tm.AddedRelationshipsByForeignKey, tm.AddedRelationshipsByPrincipalKey, currentKey, entry, entityReference);
3943 
3944                         //And if the original key wasn't a conceptual null we are also deleting
3945                         if (!originalKeyIsConceptualNull)
3946                         {
3947                             this.AddDetectedRelationship(tm.DeletedRelationshipsByForeignKey, originalKey, entityReference);
3948                         }
3949                     }
3950                 }
3951             }
3952         }
3953 
3954         /// <summary>
3955         /// True if the underlying entity is not capable of tracking changes to complex types such that
3956         /// DetectChanges is required to do this.
3957         /// </summary>
3958         internal bool RequiresComplexChangeTracking
3959         {
3960             get { return _requiresComplexChangeTracking; }
3961         }
3962 
3963         /// <summary>
3964         /// True if the underlying entity is not capable of tracking changes to scalars such that
3965         /// DetectChanges is required to do this.
3966         /// </summary>
3967         internal bool RequiresScalarChangeTracking
3968         {
3969             get { return _requiresScalarChangeTracking; }
3970         }
3971 
3972         /// <summary>
3973         /// True if the underlying entity is not capable of performing full change tracking such that
3974         /// it must be considered by at least some parts of DetectChanges.
3975         /// </summary>
3976         internal bool RequiresAnyChangeTracking
3977         {
3978             get { return _requiresAnyChangeTracking; }
3979         }
3980     }
3981 }
3982