1 //---------------------------------------------------------------------
2 // <copyright file="ObjectStateManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.Objects
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.ComponentModel;
15 using System.Data.Common;
16 using System.Data.Common.Utils;
17 using System.Data.Mapping;
18 using System.Data.Metadata.Edm;
19 using System.Data.Objects.DataClasses;
20 using System.Data.Objects.Internal;
21 using System.Diagnostics;
22 using System.Globalization;
24 using System.Linq.Expressions;
27 /// implementation of ObjectStateManager class
29 public class ObjectStateManager : IEntityStateManager
31 // This is the initial capacity used for lists of entries. We use this rather than the default because
32 // perf testing showed we were almost always increasing the capacity which can be quite a slow operation.
33 private const int _initialListSize = 16;
35 // dictionaries (one for each entity state) that store cache entries that represent entities
36 // these are only non-null when there is an entity in respective state, must always check for null before using
37 private Dictionary<EntityKey, EntityEntry> _addedEntityStore;
38 private Dictionary<EntityKey, EntityEntry> _modifiedEntityStore;
39 private Dictionary<EntityKey, EntityEntry> _deletedEntityStore;
40 private Dictionary<EntityKey, EntityEntry> _unchangedEntityStore;
41 private Dictionary<object, EntityEntry> _keylessEntityStore;
43 // dictionaries (one for each entity state) that store cache entries that represent relationships
44 // these are only non-null when there is an relationship in respective state, must always check for null before using
45 private Dictionary<RelationshipWrapper, RelationshipEntry> _addedRelationshipStore;
46 private Dictionary<RelationshipWrapper, RelationshipEntry> _deletedRelationshipStore;
47 private Dictionary<RelationshipWrapper, RelationshipEntry> _unchangedRelationshipStore;
49 // mapping from EdmType or EntitySetQualifiedType to StateManagerTypeMetadata
50 private readonly Dictionary<EdmType, StateManagerTypeMetadata> _metadataStore;
51 private readonly Dictionary<EntitySetQualifiedType, StateManagerTypeMetadata> _metadataMapping;
53 private readonly MetadataWorkspace _metadataWorkspace;
55 // delegate for notifying changes in collection
56 private CollectionChangeEventHandler onObjectStateManagerChangedDelegate;
57 private CollectionChangeEventHandler onEntityDeletedDelegate;
59 // Flag to indicate if we are in the middle of relationship fixup.
60 // This is set and cleared only during ResetEntityKey, because only in that case
61 // do we allow setting a value on a non-null EntityKey property
62 private bool _inRelationshipFixup;
64 private bool _isDisposed;
66 private ComplexTypeMaterializer _complexTypeMaterializer; // materializer instance that can be used to create complex types with just a metadata workspace
68 private readonly Dictionary<EntityKey, HashSet<EntityEntry>> _danglingForeignKeys = new Dictionary<EntityKey, HashSet<EntityEntry>>();
69 private HashSet<EntityEntry> _entriesWithConceptualNulls;
71 #region Private Fields for ObjectStateEntry change tracking
74 /// The object on which the change was made, could be an entity or a complex object
75 /// Also see comments for _changingOldValue
77 private object _changingObject;
80 /// _changingMember and _changingEntityMember should be the same for changes to
81 /// top-level entity properties. They are only different for complex members.
82 /// Also see comments for _changingOldValue.
84 private string _changingMember;
87 /// The top-level entity property that is changing. This could be the actual property
88 /// that is changing, or the change could be occurring one or more levels down in the hierarchy
89 /// on a complex object.
90 /// Also see comments for _changingOldValue
92 private string _changingEntityMember;
95 /// EntityState of the entry during the changing notification. Used to detect
96 /// if the state has changed between the changing and changed notifications, which we do not allow.
97 /// Also see comments for _changingOldValue
99 private EntityState _changingState;
102 /// True if we are in a state where original values are to be stored during a changed notification
103 /// This flags gets set during the changing notification and is used during changed to determine
104 /// if we need to save to original values or not.
105 /// Also see comments for _changingOldValue
106 private bool _saveOriginalValues;
109 /// Cache entity property/changing object/original value here between changing and changed events
112 /// Only single threading is supported and changing/changed calls cannot be nested. Consecutive calls to Changing
113 /// overwrite previously cached values. Calls to Changed must have been preceded by a Changing call on the same property
115 /// a) user starts property value change with a call to
116 /// IEntityChangeTracker.EntityMemberChanging or IEntityChangeTracker.EntityComplexMemberChanging
117 /// b) The public interface methods call EntityValueChanging, which caches the original value
118 /// c) new property value is stored on object
119 /// d) users completes the property value change with a call to
120 /// IEntityChangeTracker.EntityMemberChanged or IEntityChangeTracker.EntityComplexMemberChanged
121 /// e} The public interface methods call EntityValueChanged, which saves the cached value in the original values record
123 private object _changingOldValue;
125 private bool _detectChangesNeeded;
130 /// ObjectStateManager constructor.
132 /// <param name="metadataWorkspace"></param>
133 [CLSCompliant(false)]
134 public ObjectStateManager(MetadataWorkspace metadataWorkspace)
136 EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
137 _metadataWorkspace = metadataWorkspace;
139 _metadataStore = new Dictionary<EdmType, StateManagerTypeMetadata>();
140 _metadataMapping = new Dictionary<EntitySetQualifiedType, StateManagerTypeMetadata>(EntitySetQualifiedType.EqualityComparer);
142 TransactionManager = new TransactionManager();
145 #region Internal Properties for ObjectStateEntry change tracking
147 internal object ChangingObject
149 get { return _changingObject; }
150 set { _changingObject = value; }
153 internal string ChangingEntityMember
155 get { return _changingEntityMember; }
156 set { _changingEntityMember = value; }
159 internal string ChangingMember
161 get { return _changingMember; }
162 set { _changingMember = value; }
165 internal EntityState ChangingState
167 get { return _changingState; }
168 set { _changingState = value; }
171 internal bool SaveOriginalValues
173 get { return _saveOriginalValues; }
174 set { _saveOriginalValues = value; }
177 internal object ChangingOldValue
179 get { return _changingOldValue; }
180 set { _changingOldValue = value; }
183 // Used by ObjectStateEntry to determine if it's safe to set a value
184 // on a non-null IEntity.EntityKey property
185 internal bool InRelationshipFixup
187 get { return _inRelationshipFixup; }
190 internal ComplexTypeMaterializer ComplexTypeMaterializer
194 if (_complexTypeMaterializer == null)
196 _complexTypeMaterializer = new ComplexTypeMaterializer(this.MetadataWorkspace);
198 return _complexTypeMaterializer;
204 internal TransactionManager TransactionManager
211 /// MetadataWorkspace property
213 /// <returns>MetadataWorkspace</returns>
214 [CLSCompliant(false)]
215 public MetadataWorkspace MetadataWorkspace
219 return _metadataWorkspace;
223 #region events ObjectStateManagerChanged / EntityDeleted
225 /// Event to notify changes in the collection.
227 public event CollectionChangeEventHandler ObjectStateManagerChanged
231 onObjectStateManagerChangedDelegate += value;
235 onObjectStateManagerChangedDelegate -= value;
239 internal event CollectionChangeEventHandler EntityDeleted
243 onEntityDeletedDelegate += value;
247 onEntityDeletedDelegate -= value;
251 internal void OnObjectStateManagerChanged(CollectionChangeAction action, object entity)
253 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
254 if (onObjectStateManagerChangedDelegate != null)
256 onObjectStateManagerChangedDelegate(this, new CollectionChangeEventArgs(action, entity));
260 private void OnEntityDeleted(CollectionChangeAction action, object entity)
262 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
263 if (onEntityDeletedDelegate != null)
265 onEntityDeletedDelegate(this, new CollectionChangeEventArgs(action, entity));
271 /// Adds an object stub to the cache.
273 /// <param name="entityKey">the key of the object to add</param>
274 /// <param name="entitySet">the entity set of the given object</param>
276 internal EntityEntry AddKeyEntry(EntityKey entityKey, EntitySet entitySet)
278 Debug.Assert((object)entityKey != null, "entityKey cannot be null.");
279 Debug.Assert(entitySet != null, "entitySet must be non-null.");
281 // We need to determine if an equivalent entry already exists;
282 // this is illegal in certain cases.
283 EntityEntry entry = FindEntityEntry(entityKey);
286 throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
289 // Get a StateManagerTypeMetadata for the entity type.
290 StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(entitySet.ElementType);
292 // Create a cache entry.
293 entry = new EntityEntry(entityKey, entitySet, this, typeMetadata);
295 // A new entity is being added.
296 AddEntityEntryToDictionary(entry, entry.State);
302 /// Validates that the proxy type being attached to the context matches the proxy type
303 /// that would be generated for the given CLR type for the currently loaded metadata.
304 /// This prevents a proxy for one set of metadata being incorrectly loaded into a context
305 /// which has different metadata.
307 private void ValidateProxyType(IEntityWrapper wrappedEntity)
309 var identityType = wrappedEntity.IdentityType;
310 var actualType = wrappedEntity.Entity.GetType();
311 if (identityType != actualType)
313 var entityType = MetadataWorkspace.GetItem<ClrEntityType>(identityType.FullName, DataSpace.OSpace);
314 var proxyTypeInfo = EntityProxyFactory.GetProxyType(entityType);
315 if (proxyTypeInfo == null || proxyTypeInfo.ProxyType != actualType)
317 throw EntityUtil.DuplicateTypeForProxyType(identityType);
323 /// Adds an object to the ObjectStateManager.
325 /// <param name="dataObject">the object to add</param>
326 /// <param name="entitySet">the entity set of the given object</param>
327 /// <param name="argumentName">Name of the argument passed to a public method, for use in exceptions.</param>
328 /// <param name="isAdded">Indicates whether the entity is added or unchanged.</param>
329 internal EntityEntry AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, string argumentName, bool isAdded)
331 Debug.Assert(wrappedObject != null, "entity wrapper cannot be null.");
332 Debug.Assert(wrappedObject.Entity != null, "entity cannot be null.");
333 Debug.Assert(wrappedObject.Context != null, "the context should be already set");
334 Debug.Assert(entitySet != null, "entitySet must be non-null.");
335 // shadowValues is allowed to be null
336 Debug.Assert(argumentName != null, "argumentName cannot be null.");
338 EntityKey entityKey = passedKey;
340 // Get a StateManagerTypeMetadata for the entity type.
341 StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(wrappedObject.IdentityType, entitySet);
343 ValidateProxyType(wrappedObject);
345 // dataObject's type should match to type that can be contained by the entityset
346 EdmType entityEdmType = typeMetadata.CdmMetadata.EdmType;
347 //OC Mapping will make sure that non-abstract type in O space is always mapped to a non-abstract type in C space
348 Debug.Assert(!entityEdmType.Abstract, "non-abstract type in O space is mapped to abstract type in C space");
349 if ((isAdded) && !entitySet.ElementType.IsAssignableFrom(entityEdmType))
351 throw EntityUtil.EntityTypeDoesNotMatchEntitySet(wrappedObject.Entity.GetType().Name, TypeHelpers.GetFullName(entitySet), argumentName);
354 EntityKey dataObjectEntityKey = null;
357 dataObjectEntityKey = wrappedObject.GetEntityKeyFromEntity();
361 dataObjectEntityKey = wrappedObject.EntityKey;
364 if ((object)dataObjectEntityKey != null && (object)entityKey != null)
366 Debug.Assert(dataObjectEntityKey == entityKey, "The passed key and the key on dataObject must match.");
369 if (null != (object)dataObjectEntityKey)
371 entityKey = dataObjectEntityKey;
372 // These two checks verify that entityWithKey.EntityKey implemented by the user on a (I)POCO entity returns what it was given.
373 EntityUtil.CheckEntityKeyNull(entityKey);
374 EntityUtil.CheckEntityKeysMatch(wrappedObject, entityKey);
377 if ((object)entityKey != null && !entityKey.IsTemporary && !isAdded)
379 // If the entity already has a permanent key, and we were invoked
380 // from the materializer, check that the key is correct. We don't check
381 // for temporary keys because temporary keys don't contain values.
382 CheckKeyMatchesEntity(wrappedObject, entityKey, entitySet, /*forAttach*/ false);
385 // We need to determine if an equivalent entry already exists; this is illegal
387 EntityEntry existingEntry;
389 ((dataObjectEntityKey == null && (null != (existingEntry = FindEntityEntry(wrappedObject.Entity)))) ||
390 (dataObjectEntityKey != null && (null != (existingEntry = FindEntityEntry(dataObjectEntityKey))))))
392 if (existingEntry.Entity != wrappedObject.Entity)
394 throw EntityUtil.ObjectStateManagerContainsThisEntityKey();
396 // key does exist but entity is the same, it is being re-added ;
397 // no-op when Add(entity)
398 // NOTE we don't want to re-add entities in other then Added state
399 if (existingEntry.State != EntityState.Added) // (state == DataRowState.Unchanged && state == DataRowState.Modified)
401 throw EntityUtil.ObjectStateManagerDoesnotAllowToReAddUnchangedOrModifiedOrDeletedEntity(existingEntry.State);
409 // Neither entityWithKey.EntityKey nor the passed entityKey were non-null, or
410 // If the entity doesn't already exist in the state manager
411 // and we intend to put the entity in the Added state (i.e.,
412 // AddEntry() was invoked from ObjectContext.AddObject()),
413 // the entity's key must be set to a new temp key.
414 if ((object)entityKey == null || (isAdded && !entityKey.IsTemporary))
416 // If the entity does not have a key, create and add a temporary key.
417 entityKey = new EntityKey(entitySet);
418 wrappedObject.EntityKey = entityKey;
421 if (!wrappedObject.OwnsRelationshipManager)
423 // When a POCO instance is added or attached, we need to ignore the contents
424 // of the RelationshipManager as it is out-of-date with the POCO nav props
425 wrappedObject.RelationshipManager.ClearRelatedEndWrappers();
428 // Create a cache entry.
429 EntityEntry newEntry = new EntityEntry(wrappedObject, entityKey, entitySet, this, typeMetadata, isAdded ? EntityState.Added : EntityState.Unchanged);
431 //Verify that the entityKey is set correctly--also checks entry.EK and entity.EK internally
432 Debug.Assert(entityKey == newEntry.EntityKey, "The key on the new entry was not set correctly");
434 // ObjectMaterializer will have already determined the existing entry doesn't exist
435 Debug.Assert(null == FindEntityEntry(entityKey), "should not have existing entry");
437 // A new entity is being added.
438 newEntry.AttachObjectStateManagerToEntity();
439 AddEntityEntryToDictionary(newEntry, newEntry.State);
441 // fire ColectionChanged event only when a new entity is added to cache
442 OnObjectStateManagerChanged(CollectionChangeAction.Add, newEntry.Entity);
444 // When adding, we do this in AddSingleObject since we don't want to do it before the context is attached.
447 FixupReferencesByForeignKeys(newEntry);
454 internal void FixupReferencesByForeignKeys(EntityEntry newEntry, bool replaceAddedRefs = false)
456 // Perf optimization to avoid all this work if the entity doesn't participate in any FK relationships
457 if (!((EntitySet)newEntry.EntitySet).HasForeignKeyRelationships)
462 // Look at the foreign keys contained in this entry and perform fixup to the entities that
463 // they reference, or add the key and this entry to the index of foreign keys that reference
464 // entities that we don't yet know about.
465 newEntry.FixupReferencesByForeignKeys(replaceAddedRefs);
466 // Lookup the key for this entry and find all other entries that reference this entry using
467 // foreign keys. Perform fixup between the two entries.
468 foreach (EntityEntry foundEntry in GetNonFixedupEntriesContainingForeignKey(newEntry.EntityKey))
470 foundEntry.FixupReferencesByForeignKeys(replaceAddedRefs: false);
472 // Once we have done fixup for this entry we don't need the entries in the index anymore
473 RemoveForeignKeyFromIndex(newEntry.EntityKey);
477 /// Adds an entry to the index of foreign keys that reference entities that we don't yet know about.
479 /// <param name="foreignKey">The foreign key found in the entry</param>
480 /// <param name="entry">The entry that contains the foreign key that was found</param>
481 internal void AddEntryContainingForeignKeyToIndex(EntityKey foreignKey, EntityEntry entry)
483 HashSet<EntityEntry> danglingEntries;
484 if (!_danglingForeignKeys.TryGetValue(foreignKey, out danglingEntries))
486 danglingEntries = new HashSet<EntityEntry>();
487 _danglingForeignKeys.Add(foreignKey, danglingEntries);
489 Debug.Assert(entry.ObjectStateManager != null, "Attempt to add detached state entry to dangling keys");
490 danglingEntries.Add(entry);
493 [ConditionalAttribute("DEBUG")]
494 internal void AssertEntryDoesNotExistInForeignKeyIndex(EntityEntry entry)
496 foreach (var dFkEntry in _danglingForeignKeys.SelectMany(kv => kv.Value))
498 if (!(dFkEntry.State == EntityState.Detached || entry.State == EntityState.Detached))
500 Debug.Assert(dFkEntry.EntityKey == null || entry.EntityKey == null ||
501 (dFkEntry.EntityKey != entry.EntityKey && dFkEntry != entry),
502 string.Format(CultureInfo.InvariantCulture, "The entry references {0} equal. dFkEntry={1}, entry={2}", dFkEntry == entry ? "are" : "are not", dFkEntry.EntityKey.ConcatKeyValue(), entry.EntityKey.ConcatKeyValue()));
507 [ConditionalAttribute("DEBUG")]
508 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification = "This method is compiled only when the compilation symbol DEBUG is defined")]
509 internal void AssertAllForeignKeyIndexEntriesAreValid()
511 if (_danglingForeignKeys.Count == 0)
516 HashSet<ObjectStateEntry> validEntries = new HashSet<ObjectStateEntry>(GetObjectStateEntriesInternal(~EntityState.Detached));
517 foreach (var entry in _danglingForeignKeys.SelectMany(kv => kv.Value))
519 Debug.Assert(entry._cache != null, "found an entry in the _danglingForeignKeys collection that has been nulled out");
520 Debug.Assert(validEntries.Contains(entry), "The entry in the dangling foreign key store is no longer in the ObjectStateManager. Key=" + (entry.State == EntityState.Detached ? "detached" : entry.EntityKey != null ? "null" : entry.EntityKey.ConcatKeyValue()));
521 Debug.Assert(entry.State == EntityState.Detached || !ForeignKeyFactory.IsConceptualNullKey(entry.EntityKey), "Found an entry with conceptual null Key=" + entry.EntityKey.ConcatKeyValue());
526 /// Removes an entry to the index of foreign keys that reference entities that we don't yet know about.
527 /// This is typically done when the entity is detached from the context.
529 /// <param name="foreignKey">The foreign key found in the entry</param>
530 /// <param name="entry">The entry that contains the foreign key that was found</param>
531 internal void RemoveEntryFromForeignKeyIndex(EntityKey foreignKey, EntityEntry entry)
533 HashSet<EntityEntry> danglingEntries;
534 if (_danglingForeignKeys.TryGetValue(foreignKey, out danglingEntries))
536 danglingEntries.Remove(entry);
541 /// Removes the foreign key from the index of those keys that have been found in entries
542 /// but for which it was not possible to do fixup because the entity that the foreign key
543 /// referenced was not in the state manager.
545 /// <param name="foreignKey">The key to lookup and remove</param>
546 internal void RemoveForeignKeyFromIndex(EntityKey foreignKey)
548 _danglingForeignKeys.Remove(foreignKey);
552 /// Gets all state entries that contain the given foreign key for which we have not performed
553 /// fixup because the state manager did not contain the entity to which the foreign key pointed.
555 /// <param name="foreignKey">The key to lookup</param>
556 /// <returns>The state entries that contain the key</returns>
557 internal IEnumerable<EntityEntry> GetNonFixedupEntriesContainingForeignKey(EntityKey foreignKey)
559 HashSet<EntityEntry> foundEntries;
560 if (_danglingForeignKeys.TryGetValue(foreignKey, out foundEntries))
562 // these entries will be updated by the code consuming them, so
563 // create a stable container to iterate over.
564 return foundEntries.ToList();
566 return Enumerable.Empty<EntityEntry>();
570 /// Adds to index of currently tracked entities that have FK values that are conceptually
571 /// null but not actually null because the FK properties are not nullable.
572 /// If this index is non-empty in AcceptAllChanges or SaveChanges, then we throw.
573 /// If AcceptChanges is called on an entity and that entity is in the index, then
575 /// Note that the index is keyed by EntityEntry reference because it's only ever used
576 /// when we have the EntityEntry and this makes it slightly faster than using key lookup.
578 internal void RememberEntryWithConceptualNull(EntityEntry entry)
580 if (_entriesWithConceptualNulls == null)
582 _entriesWithConceptualNulls = new HashSet<EntityEntry>();
584 _entriesWithConceptualNulls.Add(entry);
588 /// Checks whether or not there is some entry in the context that has any conceptually but not
589 /// actually null FK values.
591 internal bool SomeEntryWithConceptualNullExists()
593 return _entriesWithConceptualNulls != null && _entriesWithConceptualNulls.Count != 0;
597 /// Checks whether the given entry has conceptually but not actually null FK values.
599 internal bool EntryHasConceptualNull(EntityEntry entry)
601 return _entriesWithConceptualNulls != null && _entriesWithConceptualNulls.Contains(entry);
605 /// Stops keeping track of an entity with conceptual nulls because the FK values have been
606 /// really set or because the entity is leaving the context or becoming deleted.
608 internal void ForgetEntryWithConceptualNull(EntityEntry entry, bool resetAllKeys)
610 if (!entry.IsKeyEntry && _entriesWithConceptualNulls != null && _entriesWithConceptualNulls.Remove(entry))
612 if (entry.RelationshipManager.HasRelationships)
614 foreach (RelatedEnd end in entry.RelationshipManager.Relationships)
616 EntityReference reference = end as EntityReference;
617 if (reference != null && ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey))
621 reference.SetCachedForeignKey(null, null);
625 // This means that we thought we could remove because one FK was no longer conceptually
626 // null, but in fact we have to add the entry back because another FK is still conceptually null
627 _entriesWithConceptualNulls.Add(entry);
636 // devnote: see comment to SQLBU 555615 in ObjectContext.AttachSingleObject()
637 internal void PromoteKeyEntryInitialization(ObjectContext contextToAttach,
638 EntityEntry keyEntry,
639 IEntityWrapper wrappedEntity,
640 IExtendedDataRecord shadowValues,
643 Debug.Assert(keyEntry != null, "keyEntry must be non-null.");
644 Debug.Assert(wrappedEntity != null, "entity cannot be null.");
645 // shadowValues is allowed to be null
647 // Future Enhancement: Fixup already has this information, don't rediscover it
648 StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(wrappedEntity.IdentityType, (EntitySet)keyEntry.EntitySet);
649 ValidateProxyType(wrappedEntity);
650 keyEntry.PromoteKeyEntry(wrappedEntity, shadowValues, typeMetadata);
651 AddEntryToKeylessStore(keyEntry);
655 // if we are replacing an existing entry, then clean the entity's change tracker
656 // so that it can be reset to this newly promoted entry
657 wrappedEntity.SetChangeTracker(null);
659 // A new entity is being added.
660 wrappedEntity.SetChangeTracker(keyEntry);
662 if (contextToAttach != null)
664 // The ObjectContext needs to be attached to the wrapper here because we need it to be attached to
665 // RelatedEnds for the snapshot change tracking that happens in TakeSnapshot. However, it
666 // cannot be attached in ObjectContext.AttachSingleObject before calling this method because this
667 // would attach it to RelatedEnds before SetChangeTracker is called, thereby breaking a legacy
668 // case for entities derived from EntityObject--see AttachSingleObject for details.
669 wrappedEntity.AttachContext(contextToAttach, (EntitySet)keyEntry.EntitySet, MergeOption.AppendOnly);
672 wrappedEntity.TakeSnapshot(keyEntry);
674 OnObjectStateManagerChanged(CollectionChangeAction.Add, keyEntry.Entity);
678 /// Upgrades an entity key entry in the cache to a a regular entity
680 /// <param name="keyEntry">the key entry that exists in the state manager</param>
681 /// <param name="entity">the object to add</param>
682 /// <param name="shadowValues">a data record representation of the entity's values, including any values in shadow state</param>
683 /// <param name="replacingEntry">True if this promoted key entry is replacing an existing detached entry</param>
684 /// <param name="setIsLoaded">Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds</param>
685 /// <param name="argumentName">Name of the argument passed to a public method, for use in exceptions.</param>
686 internal void PromoteKeyEntry(EntityEntry keyEntry,
687 IEntityWrapper wrappedEntity,
688 IExtendedDataRecord shadowValues,
691 bool keyEntryInitialized,
694 Debug.Assert(keyEntry != null, "keyEntry must be non-null.");
695 Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null.");
696 Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null.");
697 Debug.Assert(wrappedEntity.Context != null, "the context should be already set");
698 // shadowValues is allowed to be null
699 Debug.Assert(argumentName != null, "argumentName cannot be null.");
701 if (!keyEntryInitialized)
703 // We pass null as the context here because, as asserted above, the context is already attached
704 // to the wrapper when it comes down this path.
705 this.PromoteKeyEntryInitialization(null, keyEntry, wrappedEntity, shadowValues, replacingEntry);
708 bool doCleanup = true;
711 // We don't need to worry about the KeyEntry <-- Relationship --> KeyEntry because a key entry must
712 // reference a non-key entry. Fix up their other side of the relationship.
713 // Get all the relationships that currently exist for this key entry
714 foreach (RelationshipEntry relationshipEntry in CopyOfRelationshipsByKey(keyEntry.EntityKey))
716 if (relationshipEntry.State != EntityState.Deleted)
718 // Find the association ends that correspond to the source and target
719 AssociationEndMember sourceMember = keyEntry.GetAssociationEndMember(relationshipEntry);
720 AssociationEndMember targetMember = MetadataHelper.GetOtherAssociationEnd(sourceMember);
722 // Find the other end of the relationship
723 EntityEntry targetEntry = keyEntry.GetOtherEndOfRelationship(relationshipEntry);
725 // Here we are promoting based on a non-db retrieval so we use Append rules
726 AddEntityToCollectionOrReference(
727 MergeOption.AppendOnly,
730 targetEntry.WrappedEntity,
732 /*setIsLoaded*/ setIsLoaded,
733 /*relationshipAlreadyExists*/ true,
734 /*inKeyEntryPromotion*/ true);
737 FixupReferencesByForeignKeys(keyEntry);
744 keyEntry.DetachObjectStateManagerFromEntity();
745 RemoveEntryFromKeylessStore(wrappedEntity);
746 keyEntry.DegradeEntry();
750 if (this.TransactionManager.IsAttachTracking)
752 this.TransactionManager.PromotedKeyEntries.Add(wrappedEntity.Entity, keyEntry);
756 internal void TrackPromotedRelationship(RelatedEnd relatedEnd, IEntityWrapper wrappedEntity)
758 Debug.Assert(relatedEnd != null);
759 Debug.Assert(wrappedEntity != null);
760 Debug.Assert(wrappedEntity.Entity != null);
761 Debug.Assert(this.TransactionManager.IsAttachTracking || this.TransactionManager.IsAddTracking, "This method should be called only from ObjectContext.AttachTo/AddObject (indirectly)");
763 IList<IEntityWrapper> entities;
764 if (!this.TransactionManager.PromotedRelationships.TryGetValue(relatedEnd, out entities))
766 entities = new List<IEntityWrapper>();
767 this.TransactionManager.PromotedRelationships.Add(relatedEnd, entities);
769 entities.Add(wrappedEntity);
772 internal void DegradePromotedRelationships()
774 Debug.Assert(this.TransactionManager.IsAttachTracking || this.TransactionManager.IsAddTracking, "This method should be called only from the cleanup code");
776 foreach (var pair in this.TransactionManager.PromotedRelationships)
778 foreach (IEntityWrapper wrappedEntity in pair.Value)
780 if (pair.Key.RemoveFromCache(wrappedEntity, /*resetIsLoaded*/ false, /*preserveForeignKey*/ false))
782 pair.Key.OnAssociationChanged(CollectionChangeAction.Remove, wrappedEntity.Entity);
789 /// Performs non-generic collection or reference fixup between two entities
790 /// This method should only be used in scenarios where we are automatically hooking up relationships for
791 /// the user, and not in cases where they are manually setting relationships.
793 /// <param name="mergeOption">The MergeOption to use to decide how to resolve EntityReference conflicts</param>
794 /// <param name="sourceEntity">The entity instance on the source side of the relationship</param>
795 /// <param name="sourceMember">The AssociationEndMember that contains the metadata for the source entity</param>
796 /// <param name="targetEntity">The entity instance on the source side of the relationship</param>
797 /// <param name="targetMember">The AssociationEndMember that contains the metadata for the target entity</param>
798 /// <param name="setIsLoaded">Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds</param>
799 /// <param name="relationshipAlreadyExists">Whether or not the relationship entry already exists in the cache for these entities</param>
800 /// <param name="inKeyEntryPromotion">Whether this method is used in key entry promotion</param>
801 internal static void AddEntityToCollectionOrReference(
802 MergeOption mergeOption,
803 IEntityWrapper wrappedSource,
804 AssociationEndMember sourceMember,
805 IEntityWrapper wrappedTarget,
806 AssociationEndMember targetMember,
808 bool relationshipAlreadyExists,
809 bool inKeyEntryPromotion)
811 // Call GetRelatedEnd to retrieve the related end on the source entity that points to the target entity
812 RelatedEnd relatedEnd = wrappedSource.RelationshipManager.GetRelatedEndInternal(sourceMember.DeclaringType.FullName, targetMember.Name);
814 // EntityReference can only have one value
815 if (targetMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
817 Debug.Assert(relatedEnd is EntityReference, "If end is not Many multiplicity, then the RelatedEnd should be an EntityReference.");
818 var relatedReference = (EntityReference)relatedEnd;
822 case MergeOption.NoTracking:
823 // if using NoTracking, we have no way of determining identity resolution.
824 // Throw an exception saying the EntityReference is already populated and to try using
825 // a different MergeOption
826 Debug.Assert(relatedEnd.IsEmpty(), "This can occur when objects are loaded using a NoTracking merge option. Try using a different merge option when loading objects.");
828 case MergeOption.AppendOnly:
830 // In key entry promotion case, detect that sourceEntity is already related to some entity in the context,
831 // so it cannot be related to another entity being attached (relation 1-1).
832 // Without this check we would throw exception from RelatedEnd.Add() but the exception message couldn't
833 // properly describe what has happened.
834 if (inKeyEntryPromotion &&
835 !relatedReference.IsEmpty() &&
836 !Object.ReferenceEquals(relatedReference.ReferenceValue.Entity, wrappedTarget.Entity))
838 throw EntityUtil.EntityConflictsWithKeyEntry();
842 case MergeOption.PreserveChanges:
843 case MergeOption.OverwriteChanges:
844 // Retrieve the current target entity and the relationship
845 var currentWrappedTarget = relatedReference.ReferenceValue;
847 // currentWrappedTarget may already be correct because we may already have done FK fixup as part of
848 // accepting changes in the overwrite code.
849 if (currentWrappedTarget != null && currentWrappedTarget.Entity != null && currentWrappedTarget != wrappedTarget)
851 // The source entity is already related to a different target, so before we hook it up to the new target,
852 // disconnect the existing related ends and detach the relationship entry
853 RelationshipEntry relationshipEntry = relatedEnd.FindRelationshipEntryInObjectStateManager(currentWrappedTarget);
854 Debug.Assert(relationshipEntry != null || relatedEnd.IsForeignKey, "Could not find relationship entry for LAT relationship.");
856 relatedEnd.RemoveAll();
858 if (relationshipEntry != null)
860 Debug.Assert(relationshipEntry != null, "Could not find relationship entry.");
861 // If the relationship was Added prior to the above RemoveAll, it will have already been detached
862 // If it was Unchanged, it is now Deleted and should be detached
863 // It should never have been Deleted before now, because we just got currentTargetEntity from the related end
864 if (relationshipEntry.State == EntityState.Deleted)
866 relationshipEntry.AcceptChanges();
869 Debug.Assert(relationshipEntry.State == EntityState.Detached, "relationshipEntry should be Detached");
876 RelatedEnd targetRelatedEnd = null;
877 if (mergeOption == MergeOption.NoTracking)
879 targetRelatedEnd = relatedEnd.GetOtherEndOfRelationship(wrappedTarget);
880 if (targetRelatedEnd.IsLoaded)
882 // The EntityCollection has already been loaded as part of the query and adding additional
883 // entities would cause duplicate entries
884 throw EntityUtil.CannotFillTryDifferentMergeOption(targetRelatedEnd.SourceRoleName, targetRelatedEnd.RelationshipName);
888 // we may have already retrieved the target end above, but if not, just get it now
889 if (targetRelatedEnd == null)
891 targetRelatedEnd = relatedEnd.GetOtherEndOfRelationship(wrappedTarget);
894 // Add the target entity
895 relatedEnd.Add(wrappedTarget,
896 applyConstraints: true,
897 addRelationshipAsUnchanged: true,
898 relationshipAlreadyExists: relationshipAlreadyExists,
899 allowModifyingOtherEndOfRelationship: true,
900 forceForeignKeyChanges: true);
902 Debug.Assert(!(inKeyEntryPromotion && wrappedSource.Context == null),
903 "sourceEntity has been just attached to the context in PromoteKeyEntry, so Context shouldn't be null");
905 !(inKeyEntryPromotion &&
906 wrappedSource.Context.ObjectStateManager.TransactionManager.IsAttachTracking &&
907 (setIsLoaded || mergeOption == MergeOption.NoTracking)),
908 "This verifies that UpdateRelatedEnd is a no-op in a keyEntryPromotion case when the method is called indirectly from ObjectContext.AttachTo");
910 // If either end is an EntityReference, we may need to set IsLoaded or the DetachedEntityKey
911 UpdateRelatedEnd(relatedEnd, wrappedSource, wrappedTarget, setIsLoaded, mergeOption);
912 UpdateRelatedEnd(targetRelatedEnd, wrappedTarget, wrappedSource, setIsLoaded, mergeOption);
914 // In case the method was called from ObjectContext.AttachTo, we have to track relationships which were "promoted"
915 // Tracked relationships are used in recovery code of AttachTo.
916 if (inKeyEntryPromotion && wrappedSource.Context.ObjectStateManager.TransactionManager.IsAttachTracking)
918 wrappedSource.Context.ObjectStateManager.TrackPromotedRelationship(relatedEnd, wrappedTarget);
919 wrappedSource.Context.ObjectStateManager.TrackPromotedRelationship(targetRelatedEnd, wrappedSource);
923 // devnote: This method should only be used in scenarios where we are automatically hooking up relationships for
924 // the user, and not in cases where they are manually setting relationships.
925 private static void UpdateRelatedEnd(RelatedEnd relatedEnd, IEntityWrapper wrappedEntity, IEntityWrapper wrappedRelatedEntity, bool setIsLoaded, MergeOption mergeOption)
927 AssociationEndMember endMember = (AssociationEndMember)(relatedEnd.ToEndMember);
929 if ((endMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
930 endMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne))
934 relatedEnd.SetIsLoaded(true);
936 // else we just want to leave IsLoaded alone, not set it to false
938 // In NoTracking cases, we want to enable the EntityReference.EntityKey property, so we have to set the key
939 if (mergeOption == MergeOption.NoTracking)
941 EntityKey targetKey = wrappedRelatedEntity.EntityKey;
942 EntityUtil.CheckEntityKeyNull(targetKey);
944 // since endMember is not Many, relatedEnd must be an EntityReference
945 ((EntityReference)relatedEnd).DetachedEntityKey = targetKey;
951 /// Updates the relationships between a given source entity and a collection of target entities.
952 /// Used for full span and related end Load methods, where the following may be true:
953 /// (a) both sides of each relationship are always full entities and not stubs
954 /// (b) there could be multiple entities to process at once
955 /// (c) NoTracking queries are possible.
956 /// Not used for relationship span because although some of the logic is similar, the above are not true.
958 /// <param name="context">ObjectContext to use to look up existing relationships. Using the context here instead of ObjectStateManager because for NoTracking queries
959 /// we shouldn't even touch the state manager at all, so we don't want to access it until we know we are not using NoTracking.</param>
960 /// <param name="mergeOption">MergeOption to use when updating existing relationships</param>
961 /// <param name="associationSet">AssociationSet for the relationships</param>
962 /// <param name="sourceMember">Role of sourceEntity in associationSet</param>
963 /// <param name="sourceKey">EntityKey for sourceEntity</param>
964 /// <param name="sourceEntity">Source entity in the relationship</param>
965 /// <param name="targetMember">Role of each targetEntity in associationSet</param>
966 /// <param name="targetEntities">List of target entities to use to create relationships with sourceEntity</param>
967 /// <param name="setIsLoaded">Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds</param>
968 internal static int UpdateRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet, AssociationEndMember sourceMember, EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember, IList targets, bool setIsLoaded)
972 context.ObjectStateManager.TransactionManager.BeginGraphUpdate();
977 if (mergeOption == MergeOption.NoTracking)
979 RelatedEnd relatedEnd = wrappedSource.RelationshipManager.GetRelatedEndInternal(sourceMember.DeclaringType.FullName, targetMember.Name);
980 if (!relatedEnd.IsEmpty())
982 // The RelatedEnd has already been filled as part of the query and adding additional
983 // entities would cause duplicate entries
984 throw EntityUtil.CannotFillTryDifferentMergeOption(relatedEnd.SourceRoleName, relatedEnd.RelationshipName);
988 foreach (object someTarget in targets)
990 IEntityWrapper wrappedTarget = someTarget as IEntityWrapper;
991 if (wrappedTarget == null)
993 wrappedTarget = EntityWrapperFactory.WrapEntityUsingContext(someTarget, context);
997 // If there is an existing relationship entry, update it based on its current state and the MergeOption, otherwise add a new one
998 EntityState newEntryState;
999 if (mergeOption == MergeOption.NoTracking)
1001 // For NoTracking, we shouldn't touch the state manager, so no need to look for existing relationships to handle, just connect the two entities.
1002 // We don't care if the relationship already exists in the state manager or not, so just pass relationshipAlreadyExists=true so it won't look for it
1003 AddEntityToCollectionOrReference(
1004 MergeOption.NoTracking,
1010 /*relationshipAlreadyExists*/ true,
1011 /*inKeyEntryPromotion*/ false);
1015 ObjectStateManager manager = context.ObjectStateManager;
1016 EntityKey targetKey = wrappedTarget.EntityKey;
1017 if (!TryUpdateExistingRelationships(context, mergeOption, associationSet, sourceMember, sourceKey, wrappedSource, targetMember, targetKey, setIsLoaded, out newEntryState))
1019 bool needNewRelationship = true;
1020 switch (sourceMember.RelationshipMultiplicity)
1022 case RelationshipMultiplicity.ZeroOrOne:
1023 case RelationshipMultiplicity.One:
1024 // The other end of the relationship might already be related to something else, in which case we need to fix it up.
1025 // devnote1: In some cases we can let relationship span do this, but there are cases, like EntityCollection.Attach, where there is no query
1026 // and thus no relationship span to help us. So, for now, this is redundant because relationship span will make another pass over these
1027 // entities, but unless I add a flag or something to indicate when I have to do it and when I don't, this is necessary.
1028 // devnote2: The target and source arguments are intentionally reversed in the following call, because we already know there isn't a relationship
1029 // between the two entities we are current processing, but we want to see if there is one between the target and another source
1030 needNewRelationship = !TryUpdateExistingRelationships(context, mergeOption, associationSet, targetMember,
1031 targetKey, wrappedTarget, sourceMember, sourceKey, setIsLoaded, out newEntryState);
1033 case RelationshipMultiplicity.Many:
1034 // we always need a new relationship with Many-To-Many, if there was no exact match between these two entities, so do nothing
1037 Debug.Assert(false, "Unexpected sourceMember.RelationshipMultiplicity");
1040 if (needNewRelationship)
1042 if (newEntryState != EntityState.Deleted)
1044 AddEntityToCollectionOrReference(
1051 /*relationshipAlreadyExists*/ false,
1052 /*inKeyEntryPromotion*/ false);
1056 // Add a Deleted relationship between the source entity and the target entity
1057 // No end fixup is necessary since the relationship is Deleted
1058 RelationshipWrapper wrapper = new RelationshipWrapper(associationSet, sourceMember.Name, sourceKey, targetMember.Name, targetKey);
1059 manager.AddNewRelation(wrapper, EntityState.Deleted);
1062 // else there is nothing else for us to do, the relationship has been handled already
1064 // else there is nothing else for us to do, the relationship has been handled already
1070 // If we didn't put anything into the collection, then at least make sure that it is empty
1071 // rather than null.
1072 EnsureCollectionNotNull(sourceMember, wrappedSource, targetMember);
1077 context.ObjectStateManager.TransactionManager.EndGraphUpdate();
1080 // devnote: Don't set IsLoaded on the target related end here -- the caller can do this more efficiently than we can here in some cases.
1083 // Checks if the target end is a collection and, if so, ensures that it is not
1084 // null by creating an empty collection if necessary.
1085 private static void EnsureCollectionNotNull(AssociationEndMember sourceMember, IEntityWrapper wrappedSource, AssociationEndMember targetMember)
1087 RelatedEnd relatedEnd = wrappedSource.RelationshipManager.GetRelatedEndInternal(sourceMember.DeclaringType.FullName, targetMember.Name);
1088 AssociationEndMember endMember = (AssociationEndMember)(relatedEnd.ToEndMember);
1089 if (endMember != null && endMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
1091 if (relatedEnd.TargetAccessor.HasProperty)
1093 wrappedSource.EnsureCollectionNotNull(relatedEnd);
1099 /// Removes relationships if necessary when a query determines that the source entity has no relationships on the server
1101 /// <param name="context">ObjectContext that contains the client relationships</param>
1102 /// <param name="mergeOption">MergeOption to use when updating existing relationships</param>
1103 /// <param name="associationSet">AssociationSet for the incoming relationship</param>
1104 /// <param name="sourceKey">EntityKey of the source entity in the relationship</param>
1105 /// <param name="sourceMember">Role of the source entity in the relationship</param>
1106 internal static void RemoveRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet,
1107 EntityKey sourceKey, AssociationEndMember sourceMember)
1109 Debug.Assert(mergeOption == MergeOption.PreserveChanges || mergeOption == MergeOption.OverwriteChanges, "Unexpected MergeOption");
1110 // Initial capacity is set to avoid an almost immediate resizing, which was causing a perf hit.
1111 List<RelationshipEntry> deletedRelationships = new List<RelationshipEntry>(_initialListSize);
1113 // This entity has no related entities on the server for the given associationset and role. If it has related
1114 // entities on the client, we may need to update those relationships, depending on the MergeOption
1115 if (mergeOption == MergeOption.OverwriteChanges)
1117 foreach (RelationshipEntry relationshipEntry in context.ObjectStateManager.FindRelationshipsByKey(sourceKey))
1119 // We only care about the relationships that match the incoming associationset and role for the source entity
1120 if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceMember, sourceKey))
1122 deletedRelationships.Add(relationshipEntry);
1126 else if (mergeOption == MergeOption.PreserveChanges)
1128 // Leave any Added relationships for this entity, but remove Unchanged and Deleted ones
1129 foreach (RelationshipEntry relationshipEntry in context.ObjectStateManager.FindRelationshipsByKey(sourceKey))
1131 // We only care about the relationships that match the incoming associationset and role for the source entity
1132 if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceMember, sourceKey) &&
1133 relationshipEntry.State != EntityState.Added)
1135 deletedRelationships.Add(relationshipEntry);
1139 // else we do nothing. We never expect any other states here, and already Assert this condition at the top of the method
1141 foreach (RelationshipEntry deletedEntry in deletedRelationships)
1143 ObjectStateManager.RemoveRelatedEndsAndDetachRelationship(deletedEntry, true);
1147 /// Tries to updates one or more existing relationships for an entity, based on a given MergeOption and a target entity.
1149 /// <param name="context">ObjectContext to use to look up existing relationships for sourceEntity</param>
1150 /// <param name="mergeOption">MergeOption to use when updating existing relationships</param>
1151 /// <param name="associationSet">AssociationSet for the relationship we are looking for</param>
1152 /// <param name="sourceMember">AssociationEndMember for the source role of the relationship</param>
1153 /// <param name="sourceKey">EntityKey for the source entity in the relationship (passed here so we don't have to look it up again)</param>
1154 /// <param name="sourceEntity">Source entity in the relationship</param>
1155 /// <param name="targetMember">AssociationEndMember for the target role of the relationship</param>
1156 /// <param name="targetKey">EntityKey for the target entity in the relationship</param>
1157 /// <param name="setIsLoaded">Tells whether we should allow the IsLoaded flag to be set to true for RelatedEnds</param>
1158 /// <param name="newEntryState">[out] EntityState to be used for in scenarios where we need to add a new relationship after this method has returned</param>
1160 /// true if an existing relationship is found and updated, and no further action is needed
1161 /// false if either no relationship was found, or if one was found and updated, but a new one still needs to be added
1163 internal static bool TryUpdateExistingRelationships(ObjectContext context, MergeOption mergeOption, AssociationSet associationSet, AssociationEndMember sourceMember, EntityKey sourceKey, IEntityWrapper wrappedSource, AssociationEndMember targetMember, EntityKey targetKey, bool setIsLoaded, out EntityState newEntryState)
1165 Debug.Assert(mergeOption != MergeOption.NoTracking, "Existing relationships should not be updated with NoTracking");
1167 // New relationships are always added as Unchanged except in specific scenarios. If there are multiple relationships being updated, and
1168 // at least one of those requests the new relationship to be Deleted, it should always be added as Deleted, even if there are other
1169 // relationships being updated that don't specify a state. Adding as Unchanged is just the default unless a scenario needs it to be Deleted to
1170 // achieve a particular result.
1171 newEntryState = EntityState.Unchanged;
1172 // FK full span for tracked entities is handled entirely by FK fix up in the state manager.
1173 // Therefore, if the relationship is a FK, we just return indicating that nothing is to be done.
1174 if (associationSet.ElementType.IsForeignKey)
1178 // Unless we find a case below where we explicitly do not want a new relationship, we should always add one to match the server.
1179 bool needNewRelationship = true;
1181 ObjectStateManager manager = context.ObjectStateManager;
1182 List<RelationshipEntry> entriesToDetach = null;
1183 List<RelationshipEntry> entriesToUpdate = null;
1184 foreach (RelationshipEntry relationshipEntry in manager.FindRelationshipsByKey(sourceKey))
1186 // We only care about relationships for the same AssociationSet and where the source entity is in the same role as it is in the incoming relationship.
1187 if (relationshipEntry.IsSameAssociationSetAndRole(associationSet, sourceMember, sourceKey))
1189 // If the other end of this relationship matches our current target entity, this relationship entry matches the server
1190 if (targetKey == relationshipEntry.RelationshipWrapper.GetOtherEntityKey(sourceKey))
1192 if (entriesToUpdate == null)
1194 // Initial capacity is set to avoid an almost immediate resizing, which was causing a perf hit.
1195 entriesToUpdate = new List<RelationshipEntry>(_initialListSize);
1197 entriesToUpdate.Add(relationshipEntry);
1201 // We found an existing relationship where the reference side is different on the server than what the client has.
1203 // This relationship is between the same source entity and a different target, so we may need to take steps to fix up the
1204 // relationship to ensure that the client state is correct based on the requested MergeOption.
1205 // The only scenario we care about here is one where the target member has zero or one multiplicity (0..1 or 1..1), because those
1206 // are the only cases where it is meaningful to say that the relationship is different on the server and the client. In scenarios
1207 // where the target member has a many (*) multiplicity, it is possible to have multiple relationships between the source key
1208 // and other entities, and we don't want to touch those here.
1209 switch (targetMember.RelationshipMultiplicity)
1211 case RelationshipMultiplicity.One:
1212 case RelationshipMultiplicity.ZeroOrOne:
1213 switch (mergeOption)
1215 case MergeOption.AppendOnly:
1216 if (relationshipEntry.State != EntityState.Deleted)
1218 Debug.Assert(relationshipEntry.State == EntityState.Added || relationshipEntry.State == EntityState.Unchanged, "Unexpected relationshipEntry state");
1219 needNewRelationship = false; // adding a new relationship would conflict with the existing one
1222 case MergeOption.OverwriteChanges:
1223 if (entriesToDetach == null)
1225 // Initial capacity is set to avoid an almost immediate resizing, which was causing a perf hit.
1226 entriesToDetach = new List<RelationshipEntry>(_initialListSize);
1228 entriesToDetach.Add(relationshipEntry);
1230 case MergeOption.PreserveChanges:
1231 switch (relationshipEntry.State)
1233 case EntityState.Added:
1234 newEntryState = EntityState.Deleted;
1236 case EntityState.Unchanged:
1237 if (entriesToDetach == null)
1239 // Initial capacity is set to avoid an almost immediate resizing, which was causing a perf hit.
1240 entriesToDetach = new List<RelationshipEntry>(_initialListSize);
1242 entriesToDetach.Add(relationshipEntry);
1244 case EntityState.Deleted:
1245 newEntryState = EntityState.Deleted;
1246 if (entriesToDetach == null)
1248 // Initial capacity is set to avoid an almost immediate resizing, which was causing a perf hit.
1249 entriesToDetach = new List<RelationshipEntry>(_initialListSize);
1251 entriesToDetach.Add(relationshipEntry);
1254 Debug.Assert(false, "Unexpected relationship entry state");
1259 Debug.Assert(false, "Unexpected MergeOption");
1263 case RelationshipMultiplicity.Many:
1264 // do nothing because its okay for this source entity to have multiple different targets, so there is nothing for us to fixup
1267 Debug.Assert(false, "Unexpected targetMember.RelationshipMultiplicity");
1274 // Detach all of the entries that we have collected above
1275 if (entriesToDetach != null)
1277 foreach (RelationshipEntry entryToDetach in entriesToDetach)
1279 // the entry may have already been detached by another operation. If not, detach it now.
1280 if (entryToDetach.State != EntityState.Detached)
1282 RemoveRelatedEndsAndDetachRelationship(entryToDetach, setIsLoaded);
1287 // Update all of the matching entries that we have collectioned above
1288 if (entriesToUpdate != null)
1290 foreach (RelationshipEntry relationshipEntry in entriesToUpdate)
1292 // Don't need new relationship to be added to match the server, since we already have a match
1293 needNewRelationship = false;
1295 // We have an existing relationship entry that matches exactly to the incoming relationship from the server, but
1296 // we may need to update it on the client based on the MergeOption and the state of the relationship entry.
1297 switch (mergeOption)
1299 case MergeOption.AppendOnly:
1300 // AppendOnly and NoTracking shouldn't affect existing relationships, so do nothing
1302 case MergeOption.OverwriteChanges:
1303 if (relationshipEntry.State == EntityState.Added)
1305 relationshipEntry.AcceptChanges();
1307 else if (relationshipEntry.State == EntityState.Deleted)
1309 // targetEntry should always exist in this scenario because it would have
1310 // at least been created when the relationship entry was created
1311 EntityEntry targetEntry = manager.GetEntityEntry(targetKey);
1313 // If the target entity is deleted, we don't want to bring the relationship entry back.
1314 if (targetEntry.State != EntityState.Deleted)
1316 // If the targetEntry is a KeyEntry, there are no ends to fix up.
1317 if (!targetEntry.IsKeyEntry)
1319 ObjectStateManager.AddEntityToCollectionOrReference(
1323 targetEntry.WrappedEntity,
1325 /*setIsLoaded*/ setIsLoaded,
1326 /*relationshipAlreadyExists*/ true,
1327 /*inKeyEntryPromotion*/ false);
1329 relationshipEntry.RevertDelete();
1332 // else it's already Unchanged so we don't need to do anything
1334 case MergeOption.PreserveChanges:
1335 if (relationshipEntry.State == EntityState.Added)
1337 // The client now matches the server, so just move the relationship to unchanged.
1338 // If we don't do this and left the state Added, we will get a concurrency exception when trying to save
1339 relationshipEntry.AcceptChanges();
1341 // else if it's already Unchanged we don't need to do anything
1342 // else if it's Deleted we want to preserve that state so do nothing
1345 Debug.Assert(false, "Unexpected MergeOption");
1350 return !needNewRelationship;
1353 // Helper method to disconnect two related ends and detach their associated relationship entry
1354 internal static void RemoveRelatedEndsAndDetachRelationship(RelationshipEntry relationshipToRemove, bool setIsLoaded)
1356 // If we are allowed to set the IsLoaded flag, then we can consider unloading these relationships
1359 // If the relationship needs to be deleted, then we should unload the related ends
1360 UnloadReferenceRelatedEnds(relationshipToRemove);
1363 // Delete the relationship entry and disconnect the related ends
1364 if (relationshipToRemove.State != EntityState.Deleted)
1366 relationshipToRemove.Delete();
1369 // Detach the relationship entry
1370 // Entries that were in the Added state prior to the Delete above will have already been Detached
1371 if (relationshipToRemove.State != EntityState.Detached)
1373 relationshipToRemove.AcceptChanges();
1377 private static void UnloadReferenceRelatedEnds(RelationshipEntry relationshipEntry)
1379 //Find two ends of the relationship
1380 ObjectStateManager cache = relationshipEntry.ObjectStateManager;
1381 ReadOnlyMetadataCollection<AssociationEndMember> endMembers = relationshipEntry.RelationshipWrapper.AssociationEndMembers;
1383 UnloadReferenceRelatedEnds(cache, relationshipEntry, relationshipEntry.RelationshipWrapper.GetEntityKey(0), endMembers[1].Name);
1384 UnloadReferenceRelatedEnds(cache, relationshipEntry, relationshipEntry.RelationshipWrapper.GetEntityKey(1), endMembers[0].Name);
1387 private static void UnloadReferenceRelatedEnds(ObjectStateManager cache, RelationshipEntry relationshipEntry, EntityKey sourceEntityKey, string targetRoleName)
1389 EntityEntry entry = cache.GetEntityEntry(sourceEntityKey);
1391 if (entry.WrappedEntity.Entity != null)
1393 EntityReference reference = entry.WrappedEntity.RelationshipManager.GetRelatedEndInternal(((AssociationSet)relationshipEntry.EntitySet).ElementType.FullName, targetRoleName) as EntityReference;
1394 if (reference != null)
1396 reference.SetIsLoaded(false);
1402 /// Attach entity in unchanged state (skip Added state, don't create temp key)
1403 /// It is equal (but faster) to call AddEntry(); AcceptChanges().
1405 /// <param name="entity"></param>
1406 /// <param name="entitySet"></param>
1407 /// <param name="argumentName"></param>
1408 internal EntityEntry AttachEntry(EntityKey entityKey, IEntityWrapper wrappedObject, EntitySet entitySet, string argumentName)
1410 Debug.Assert(wrappedObject != null, "entity wrapper cannot be null.");
1411 Debug.Assert(wrappedObject.Entity != null, "entity cannot be null.");
1412 Debug.Assert(wrappedObject.Context != null, "the context should be already set");
1413 Debug.Assert(entitySet != null, "entitySet must be non-null.");
1414 Debug.Assert(argumentName != null, "argumentName cannot be null.");
1415 Debug.Assert(entityKey != null, "argumentName cannot be null.");
1417 // Get a StateManagerTypeMetadata for the entity type.
1418 StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(wrappedObject.IdentityType, entitySet);
1420 ValidateProxyType(wrappedObject);
1422 CheckKeyMatchesEntity(wrappedObject, entityKey, entitySet, /*forAttach*/ true);
1424 if (!wrappedObject.OwnsRelationshipManager)
1426 // When a POCO instance is added or attached, we need to ignore the contents
1427 // of the RelationshipManager as it is out-of-date with the POCO nav props
1428 wrappedObject.RelationshipManager.ClearRelatedEndWrappers();
1431 // Create a cache entry.
1432 EntityEntry newEntry = new EntityEntry(wrappedObject, entityKey, entitySet, this, typeMetadata, EntityState.Unchanged);
1434 // The property EntityKey on newEntry validates that the entry and the entity on the entry have the same key.
1435 Debug.Assert(entityKey == newEntry.EntityKey, "newEntry.EntityKey should match entityKey");
1437 // A entity is being attached.
1438 newEntry.AttachObjectStateManagerToEntity();
1439 AddEntityEntryToDictionary(newEntry, newEntry.State);
1441 // fire ColectionChanged event only when a new entity is added to cache
1442 OnObjectStateManagerChanged(CollectionChangeAction.Add, newEntry.Entity);
1448 /// Checks that the EntityKey attached to the given entity
1449 /// appropriately matches the given entity.
1451 /// <param name="entity">The entity whose key must be verified</param>
1452 /// <param name="entitySetForType">The entity set corresponding to the type of the given entity.</param>
1453 /// <param name="forAttach">If true, then the exception message will reflect a bad key to attach, otherwise it will reflect a general inconsistency</param>
1454 private void CheckKeyMatchesEntity(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySetForType, bool forAttach)
1456 Debug.Assert(wrappedEntity != null, "Cannot verify key for null entity wrapper.");
1457 Debug.Assert(wrappedEntity.Entity != null, "Cannot verify key for null entity.");
1459 Debug.Assert((object)entityKey != null, "Cannot verify a null EntityKey.");
1460 Debug.Assert(!entityKey.IsTemporary, "Verifying a temporary EntityKey doesn't make sense because the key doesn't contain any values.");
1461 Debug.Assert(entitySetForType != null, "Cannot verify against a null entity set.");
1463 EntitySet entitySetForKey = entityKey.GetEntitySet(this.MetadataWorkspace);
1464 if (entitySetForKey == null)
1466 throw EntityUtil.InvalidKey();
1469 // Checks that the entity's key matches its type.
1470 Debug.Assert(entitySetForType.Name == entitySetForKey.Name &&
1471 entitySetForType.EntityContainer.Name == entitySetForKey.EntityContainer.Name,
1472 "The object cannot be attached because its EntityType belongs to a different EntitySet than the one specified in its key.");
1474 // Verify that the entity key contains the correct members for the entity set
1475 entityKey.ValidateEntityKey(_metadataWorkspace, entitySetForKey);
1477 // Checks that the key values in the entity match the key values
1478 // within its EntityKey.
1479 StateManagerTypeMetadata typeMetadata = GetOrAddStateManagerTypeMetadata(wrappedEntity.IdentityType, entitySetForType);
1480 for (int i = 0; i < entitySetForKey.ElementType.KeyMembers.Count; ++i)
1482 EdmMember keyField = entitySetForKey.ElementType.KeyMembers[i];
1483 int ordinal = typeMetadata.GetOrdinalforCLayerMemberName(keyField.Name);
1486 throw EntityUtil.InvalidKey();
1489 object entityValue = typeMetadata.Member(ordinal).GetValue(wrappedEntity.Entity);
1490 object keyValue = entityKey.FindValueByName(keyField.Name);
1492 // Use EntityKey.ValueComparer to perform the correct equality comparison for entity key values.
1493 if (!ByValueEqualityComparer.Default.Equals(entityValue, keyValue))
1495 throw EntityUtil.KeyPropertyDoesntMatchValueInKey(forAttach);
1500 internal RelationshipEntry AddNewRelation(RelationshipWrapper wrapper, EntityState desiredState)
1502 Debug.Assert(null == FindRelationship(wrapper), "relationship should not exist, caller verifies");
1504 RelationshipEntry entry = new RelationshipEntry(this, desiredState, wrapper);
1505 AddRelationshipEntryToDictionary(entry, desiredState);
1506 AddRelationshipToLookup(entry);
1510 internal RelationshipEntry AddRelation(RelationshipWrapper wrapper, EntityState desiredState)
1512 Debug.Assert(EntityState.Added == desiredState || // result entry should be added or left alone
1513 EntityState.Unchanged == desiredState || // result entry should be that state
1514 EntityState.Deleted == desiredState, // result entry should be in that state
1515 "unexpected state");
1517 RelationshipEntry entry = FindRelationship(wrapper);
1518 Debug.Assert(null == entry || (EntityState.Modified != entry.State), "relationship should never be modified");
1522 entry = AddNewRelation(wrapper, desiredState);
1524 else if (EntityState.Deleted != entry.State)
1526 // you can have a deleted and non-deleted relation between two entities
1527 // SQL BU DT 449757: for now no-op in case if it exists. ideally need to throw
1528 if (EntityState.Unchanged == desiredState)
1530 entry.AcceptChanges();
1532 else if (EntityState.Deleted == desiredState)
1534 entry.AcceptChanges();
1535 entry.Delete(false);
1537 // else Added and leave entry alone
1539 else if (EntityState.Deleted != desiredState)
1541 Debug.Assert(EntityState.Deleted == entry.State, "should be deleted state");
1542 entry.RevertDelete();
1544 // else entry already Deleted or if desired state is Added then left alone
1546 Debug.Assert(desiredState == entry.State ||
1547 EntityState.Added == desiredState,
1548 "unexpected end state");
1549 Debug.Assert(entry is RelationshipEntry, "unexpected type of entry");
1550 return (RelationshipEntry)entry;
1554 /// Adds the given relationship cache entry to the mapping from each of its endpoint keys.
1556 private void AddRelationshipToLookup(RelationshipEntry relationship)
1558 Debug.Assert(relationship != null, "relationship can't be null");
1560 AddRelationshipEndToLookup(relationship.RelationshipWrapper.Key0, relationship);
1561 if (!relationship.RelationshipWrapper.Key0.Equals(relationship.RelationshipWrapper.Key1))
1563 AddRelationshipEndToLookup(relationship.RelationshipWrapper.Key1, relationship);
1568 /// Adds the given relationship cache entry to the mapping from the given endpoint key.
1570 private void AddRelationshipEndToLookup(EntityKey key, RelationshipEntry relationship)
1572 Debug.Assert(null != FindEntityEntry(key), "EntityEntry doesn't exist");
1574 EntityEntry entry = GetEntityEntry(key);
1575 Debug.Assert(key.Equals(entry.EntityKey), "EntityKey mismatch");
1576 entry.AddRelationshipEnd(relationship);
1580 /// Deletes the given relationship cache entry from the mapping from each of its endpoint keys.
1582 private void DeleteRelationshipFromLookup(RelationshipEntry relationship)
1584 // The relationship is stored in the lookup indexed by both keys, so we need to remove it twice.
1585 DeleteRelationshipEndFromLookup(relationship.RelationshipWrapper.Key0, relationship);
1586 if (!relationship.RelationshipWrapper.Key0.Equals(relationship.RelationshipWrapper.Key1))
1588 DeleteRelationshipEndFromLookup(relationship.RelationshipWrapper.Key1, relationship);
1593 /// Deletes the given relationship cache entry from the mapping from the given endpoint key.
1595 private void DeleteRelationshipEndFromLookup(EntityKey key, RelationshipEntry relationship)
1597 Debug.Assert(relationship.State != EntityState.Detached, "Cannot remove a detached cache entry.");
1598 Debug.Assert(null != FindEntityEntry(key), "EntityEntry doesn't exist");
1600 EntityEntry entry = GetEntityEntry(key);
1601 Debug.Assert(key.Equals(entry.EntityKey), "EntityKey mismatch");
1602 entry.RemoveRelationshipEnd(relationship);
1605 internal RelationshipEntry FindRelationship(RelationshipSet relationshipSet,
1606 KeyValuePair<string, EntityKey> roleAndKey1,
1607 KeyValuePair<string, EntityKey> roleAndKey2)
1609 if ((null == (object)roleAndKey1.Value) || (null == (object)roleAndKey2.Value))
1613 return FindRelationship(new RelationshipWrapper((AssociationSet)relationshipSet, roleAndKey1, roleAndKey2));
1616 internal RelationshipEntry FindRelationship(RelationshipWrapper relationshipWrapper)
1618 RelationshipEntry entry = null;
1619 bool result = (((null != _unchangedRelationshipStore) && _unchangedRelationshipStore.TryGetValue(relationshipWrapper, out entry)) ||
1620 ((null != _deletedRelationshipStore) && _deletedRelationshipStore.TryGetValue(relationshipWrapper, out entry)) ||
1621 ((null != _addedRelationshipStore) && _addedRelationshipStore.TryGetValue(relationshipWrapper, out entry)));
1622 Debug.Assert(result == (null != entry), "found null entry");
1627 /// DeleteRelationship
1629 /// <returns>The deleted entry</returns>
1630 internal RelationshipEntry DeleteRelationship(RelationshipSet relationshipSet,
1631 KeyValuePair<string, EntityKey> roleAndKey1,
1632 KeyValuePair<string, EntityKey> roleAndKey2)
1634 RelationshipEntry entry = FindRelationship(relationshipSet, roleAndKey1, roleAndKey2);
1637 entry.Delete(/*doFixup*/ false);
1646 internal void DeleteKeyEntry(EntityEntry keyEntry)
1648 if (keyEntry != null && keyEntry.IsKeyEntry)
1650 ChangeState(keyEntry, keyEntry.State, EntityState.Detached);
1656 /// Finds all relationships with the given key at one end.
1658 internal RelationshipEntry[] CopyOfRelationshipsByKey(EntityKey key)
1660 return FindRelationshipsByKey(key).ToArray();
1664 /// Finds all relationships with the given key at one end.
1665 /// Do not use the list to add elements
1667 internal EntityEntry.RelationshipEndEnumerable FindRelationshipsByKey(EntityKey key)
1669 return new EntityEntry.RelationshipEndEnumerable((EntityEntry)FindEntityEntry(key));
1672 IEnumerable<IEntityStateEntry> IEntityStateManager.FindRelationshipsByKey(EntityKey key)
1674 return FindRelationshipsByKey(key);
1677 //Verify that all entities in the _keylessEntityStore are also in the other dictionaries.
1678 //Verify that all the entries in the _keylessEntityStore don't implement IEntityWithKey.
1679 //Verify that there no entries in the other dictionaries that don't implement IEntityWithKey and aren't in _keylessEntityStore
1680 [ConditionalAttribute("DEBUG")]
1681 private void ValidateKeylessEntityStore()
1683 // Future Enhancement : Check each entry in _keylessEntityStore to make sure it has a corresponding entry in one of the other stores.
1684 if (null != _keylessEntityStore)
1686 foreach (EntityEntry entry in _keylessEntityStore.Values)
1688 Debug.Assert(!(entry.Entity is IEntityWithKey), "_keylessEntityStore contains an entry that implement IEntityWithKey");
1690 bool result = false;
1691 if (null != _addedEntityStore)
1693 result = _addedEntityStore.TryGetValue(entry.EntityKey, out entrya);
1695 if (null != _modifiedEntityStore)
1697 result |= _modifiedEntityStore.TryGetValue(entry.EntityKey, out entrya);
1699 if (null != _deletedEntityStore)
1701 result |= _deletedEntityStore.TryGetValue(entry.EntityKey, out entrya);
1703 if (null != _unchangedEntityStore)
1705 result |= _unchangedEntityStore.TryGetValue(entry.EntityKey, out entrya);
1707 Debug.Assert(result, "entry in _keylessEntityStore doesn't exist in one of the other stores");
1711 //Check each entry in the other stores to make sure that each non-IEntityWithKey entry is also in _keylessEntityStore
1712 Dictionary<EntityKey, EntityEntry>[] stores = { _unchangedEntityStore, _modifiedEntityStore, _addedEntityStore, _deletedEntityStore };
1713 foreach (Dictionary<EntityKey, EntityEntry> store in stores)
1717 foreach (EntityEntry entry in store.Values)
1719 if (null != entry.Entity && //Skip span stub key entry
1720 !(entry.Entity is IEntityWithKey))
1722 EntityEntry keylessEntry;
1723 Debug.Assert(null != _keylessEntityStore, "There should be a store that keyless entries are in");
1724 if (_keylessEntityStore.TryGetValue(entry.Entity, out keylessEntry))
1726 Debug.Assert(object.ReferenceEquals(entry, keylessEntry), "keylessEntry and entry from stores do not match");
1730 Debug.Assert(false, "The entry containing an entity not implementing IEntityWithKey is not in the _keylessEntityStore");
1738 /// Find the ObjectStateEntry from _keylessEntityStore for an entity that doesn't implement IEntityWithKey.
1740 /// <param name="entity"></param>
1741 /// <returns></returns>
1742 private bool TryGetEntryFromKeylessStore(object entity, out EntityEntry entryRef)
1744 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
1745 Debug.Assert(!(entity is IEntityWithKey));
1747 ValidateKeylessEntityStore();
1753 if (null != _keylessEntityStore)
1755 if (_keylessEntityStore.TryGetValue(entity, out entryRef))
1766 /// Returns all CacheEntries in the given state.
1768 /// <exception cref="ArgumentException">if EntityState.Detached flag is set in state</exception>
1769 public IEnumerable<ObjectStateEntry> GetObjectStateEntries(EntityState state)
1771 if ((EntityState.Detached & state) != 0)
1773 throw EntityUtil.DetachedObjectStateEntriesDoesNotExistInObjectStateManager();
1775 return GetObjectStateEntriesInternal(state);
1779 /// Returns all CacheEntries in the given state.
1781 /// <exception cref="ArgumentException">if EntityState.Detached flag is set in state</exception>
1782 IEnumerable<IEntityStateEntry> IEntityStateManager.GetEntityStateEntries(EntityState state)
1784 Debug.Assert((EntityState.Detached & state) == 0, "Cannot get state entries for detached entities");
1785 foreach (ObjectStateEntry stateEntry in GetObjectStateEntriesInternal(state))
1787 yield return (IEntityStateEntry)stateEntry;
1791 internal int GetObjectStateEntriesCount(EntityState state)
1794 if ((EntityState.Added & state) != 0)
1796 size += ((null != _addedRelationshipStore) ? _addedRelationshipStore.Count : 0);
1797 size += ((null != _addedEntityStore) ? _addedEntityStore.Count : 0);
1799 if ((EntityState.Modified & state) != 0)
1801 size += ((null != _modifiedEntityStore) ? _modifiedEntityStore.Count : 0);
1803 if ((EntityState.Deleted & state) != 0)
1805 size += ((null != _deletedRelationshipStore) ? _deletedRelationshipStore.Count : 0);
1806 size += ((null != _deletedEntityStore) ? _deletedEntityStore.Count : 0);
1808 if ((EntityState.Unchanged & state) != 0)
1810 size += ((null != _unchangedRelationshipStore) ? _unchangedRelationshipStore.Count : 0);
1811 size += ((null != _unchangedEntityStore) ? _unchangedEntityStore.Count : 0);
1816 private int GetMaxEntityEntriesForDetectChanges()
1819 if (_addedEntityStore != null)
1821 size += _addedEntityStore.Count;
1823 if (_modifiedEntityStore != null)
1825 size += _modifiedEntityStore.Count;
1827 if (_deletedEntityStore != null)
1829 size += _deletedEntityStore.Count;
1831 if (_unchangedEntityStore != null)
1833 size += _unchangedEntityStore.Count;
1838 private ObjectStateEntry[] GetObjectStateEntriesInternal(EntityState state)
1840 Debug.Assert((EntityState.Detached & state) == 0, "Cannot get state entries for detached entities");
1842 int size = GetObjectStateEntriesCount(state);
1843 ObjectStateEntry[] entries = new ObjectStateEntry[size];
1845 size = 0; // size is now used as an offset
1846 if (((EntityState.Added & state) != 0) && (null != _addedRelationshipStore))
1848 foreach (var e in _addedRelationshipStore)
1850 entries[size++] = e.Value;
1853 if (((EntityState.Deleted & state) != 0) && (null != _deletedRelationshipStore))
1855 foreach (var e in _deletedRelationshipStore)
1857 entries[size++] = e.Value;
1860 if (((EntityState.Unchanged & state) != 0) && (null != _unchangedRelationshipStore))
1862 foreach (var e in _unchangedRelationshipStore)
1864 entries[size++] = e.Value;
1867 if (((EntityState.Added & state) != 0) && (null != _addedEntityStore))
1869 foreach (var e in _addedEntityStore)
1871 entries[size++] = e.Value;
1874 if (((EntityState.Modified & state) != 0) && (null != _modifiedEntityStore))
1876 foreach (var e in _modifiedEntityStore)
1878 entries[size++] = e.Value;
1881 if (((EntityState.Deleted & state) != 0) && (null != _deletedEntityStore))
1883 foreach (var e in _deletedEntityStore)
1885 entries[size++] = e.Value;
1888 if (((EntityState.Unchanged & state) != 0) && (null != _unchangedEntityStore))
1890 foreach (var e in _unchangedEntityStore)
1892 entries[size++] = e.Value;
1898 private IList<EntityEntry> GetEntityEntriesForDetectChanges()
1900 // This flag is set whenever an entity that may need snapshot change tracking
1901 // becomes tracked by the context. Entities that may need snapshot change tracking
1902 // are those for which any of the following are true:
1903 // a) Entity does not implement IEntityWithRelationships
1904 // b) Entity does not implement IEntityWithChangeTracker
1905 // b) Entity has a complex property.
1906 if (!_detectChangesNeeded)
1911 List<EntityEntry> entries = null; // Will be lazy initialized if needed.
1912 GetEntityEntriesForDetectChanges(_addedEntityStore, ref entries);
1913 GetEntityEntriesForDetectChanges(_modifiedEntityStore, ref entries);
1914 GetEntityEntriesForDetectChanges(_deletedEntityStore, ref entries);
1915 GetEntityEntriesForDetectChanges(_unchangedEntityStore, ref entries);
1917 // If the flag was set, but we don't find anything to do, then reset the flag again
1918 // since it means that there were some entities that needed DetectChanges, but now they
1919 // have been detached.
1920 if (entries == null)
1922 _detectChangesNeeded = false;
1928 private void GetEntityEntriesForDetectChanges(Dictionary<EntityKey, EntityEntry> entityStore, ref List<EntityEntry> entries)
1930 if (entityStore != null)
1932 foreach (var entry in entityStore.Values)
1934 if (entry.RequiresAnyChangeTracking)
1936 if (entries == null)
1938 entries = new List<EntityEntry>(GetMaxEntityEntriesForDetectChanges());
1946 #region temporary (added state) to permanent (deleted, modified, unchanged state) EntityKey fixup
1949 /// Performs key-fixup on the given entry, by creating a (permanent) EntityKey
1950 /// based on the current key values within the associated entity and fixing up
1951 /// all associated relationship entries.
1954 /// Will promote EntityEntry.IsKeyEntry and leave in _unchangedStore
1955 /// otherwise will move EntityEntry from _addedStore to _unchangedStore.
1957 internal void FixupKey(EntityEntry entry)
1959 Debug.Assert(entry != null, "entry should not be null.");
1960 Debug.Assert(entry.State == EntityState.Added, "Cannot do key fixup for an entry not in the Added state.");
1961 Debug.Assert(entry.Entity != null, "must have entity, can't be entity stub in added state");
1963 EntityKey oldKey = entry.EntityKey;
1964 Debug.Assert(entry == _addedEntityStore[oldKey], "not the same EntityEntry");
1965 Debug.Assert((object)oldKey != null, "Cannot fixup a cache entry with a null key.");
1966 Debug.Assert(oldKey.IsTemporary, "Cannot fixup an entry with a non-temporary key.");
1967 Debug.Assert(null != _addedEntityStore, "missing added store");
1969 var entitySet = (EntitySet)entry.EntitySet;
1970 bool performFkSteps = entitySet.HasForeignKeyRelationships;
1971 bool performNonFkSteps = entitySet.HasIndependentRelationships;
1975 // Do fixup based on reference first for added objects.
1976 // This must be done before creating a new key or the key will have old values.
1977 entry.FixupForeignKeysByReference();
1983 // Construct an EntityKey based on the current, fixed-up values of the entry.
1984 newKey = new EntityKey((EntitySet)entry.EntitySet, (IExtendedDataRecord)entry.CurrentValues);
1986 catch (ArgumentException ex)
1988 // ArgumentException is not the best choice here but anything else would be a breaking change.
1989 throw new ArgumentException(System.Data.Entity.Strings.ObjectStateManager_ChangeStateFromAddedWithNullKeyIsInvalid, ex);
1992 EntityEntry existingEntry = FindEntityEntry(newKey);
1993 if (existingEntry != null)
1995 if (!existingEntry.IsKeyEntry)
1997 // If the fixed-up key conflicts with an existing entry, we throw.
1998 throw EntityUtil.CannotFixUpKeyToExistingValues();
2000 newKey = existingEntry.EntityKey; // reuse existing reference
2003 RelationshipEntry[] relationshipEnds = null;
2004 if (performNonFkSteps)
2006 // remove the relationships based on the temporary key
2007 relationshipEnds = entry.GetRelationshipEnds().ToArray();
2008 foreach (RelationshipEntry relationshipEntry in relationshipEnds)
2010 RemoveObjectStateEntryFromDictionary(relationshipEntry, relationshipEntry.State);
2014 // Remove ObjectStateEntry with old Key and add it back or promote with new key.
2015 RemoveObjectStateEntryFromDictionary(entry, EntityState.Added);
2017 // This is the only scenario where we are allowed to set the EntityKey if it's already non-null
2018 // If entry.EntityKey is IEntityWithKey, user code will be called
2019 ResetEntityKey(entry, newKey);
2021 if (performNonFkSteps)
2023 // Fixup all relationships for which this key was a participant.
2024 entry.UpdateRelationshipEnds(oldKey, existingEntry);
2026 // add all the relationships back on the new entity key
2027 foreach (RelationshipEntry relationshipEntry in relationshipEnds)
2029 AddRelationshipEntryToDictionary(relationshipEntry, relationshipEntry.State);
2033 // Now promote the key entry to a full entry by adding entities to the related ends
2034 if (existingEntry != null)
2036 // two ObjectStateEntry exist for same newKey, the entity stub must exist in unchanged state
2037 Debug.Assert(existingEntry.State == EntityState.Unchanged, "entity stub must be in unchanged state");
2038 Debug.Assert(existingEntry.IsKeyEntry, "existing entry must be a key entry to promote");
2039 Debug.Assert(Object.ReferenceEquals(newKey, existingEntry.EntityKey), "should be same key reference");
2040 PromoteKeyEntry(existingEntry, entry.WrappedEntity, null, true, /*setIsLoaded*/ false, /*keyEntryInitialized*/ false, "AcceptChanges");
2042 // leave the entity stub in the unchanged state
2043 // the existing entity stub wins
2044 entry = existingEntry;
2048 // change the state to "Unchanged"
2049 AddEntityEntryToDictionary(entry, EntityState.Unchanged);
2054 FixupReferencesByForeignKeys(entry);
2057 Debug.Assert((null == _addedEntityStore) || !_addedEntityStore.ContainsKey(oldKey), "EntityEntry exists with OldKey");
2058 Debug.Assert((null != _unchangedEntityStore) && _unchangedEntityStore.ContainsKey(newKey), "EntityEntry does not exist with NewKey");
2060 // FEATURE_CHANGE: once we support equality constraints (SQL PT DB 300002154), do recursive fixup.
2064 /// Replaces permanent EntityKey with a temporary key. Used in N-Tier API.
2066 internal void ReplaceKeyWithTemporaryKey(EntityEntry entry)
2068 Debug.Assert(entry != null, "entry should not be null.");
2069 Debug.Assert(entry.State != EntityState.Added, "Cannot replace key with a temporary key if the entry is in Added state.");
2070 Debug.Assert(!entry.IsKeyEntry, "Cannot replace a key of a KeyEntry");
2072 EntityKey oldKey = entry.EntityKey;
2073 Debug.Assert(!oldKey.IsTemporary, "Entity is not in the Added state but has a temporary key.");
2075 // Construct an temporary EntityKey.
2076 EntityKey newKey = new EntityKey(entry.EntitySet);
2078 Debug.Assert(FindEntityEntry(newKey) == null, "no entry should exist with the new temporary key");
2080 // remove the relationships based on the permanent key
2081 RelationshipEntry[] relationshipEnds = entry.GetRelationshipEnds().ToArray();
2082 foreach (RelationshipEntry relationshipEntry in relationshipEnds)
2084 RemoveObjectStateEntryFromDictionary(relationshipEntry, relationshipEntry.State);
2087 // Remove ObjectStateEntry with old Key and add it back or promote with new key.
2088 RemoveObjectStateEntryFromDictionary(entry, entry.State);
2090 // This is the only scenario where we are allowed to set the EntityKey if it's already non-null
2091 // If entry.EntityKey is IEntityWithKey, user code will be called
2092 ResetEntityKey(entry, newKey);
2094 // Fixup all relationships for which this key was a participant.
2095 entry.UpdateRelationshipEnds(oldKey, null); // null PromotedEntry
2097 // add all the relationships back on the new entity key
2098 foreach (RelationshipEntry relationshipEntry in relationshipEnds)
2100 AddRelationshipEntryToDictionary(relationshipEntry, relationshipEntry.State);
2103 AddEntityEntryToDictionary(entry, EntityState.Added);
2107 /// Resets the EntityKey for this entry. This method is called
2108 /// as part of temporary key fixup and permanent key un-fixup. This method is necessary because it is the only
2109 /// scenario where we allow a new value to be set on a non-null EntityKey. This
2110 /// is the only place where we should be setting and clearing _inRelationshipFixup.
2112 private void ResetEntityKey(EntityEntry entry, EntityKey value)
2114 Debug.Assert((object)entry.EntityKey != null, "Cannot reset an entry's key if it hasn't been set in the first place.");
2115 Debug.Assert(!_inRelationshipFixup, "already _inRelationshipFixup");
2116 Debug.Assert(!entry.EntityKey.Equals(value), "the keys should not be equal");
2118 EntityKey entityKey = entry.WrappedEntity.EntityKey;
2119 if (entityKey == null || value.Equals(entityKey))
2121 throw EntityUtil.AcceptChangesEntityKeyIsNotValid();
2125 _inRelationshipFixup = true;
2126 entry.WrappedEntity.EntityKey = value; // user will have control
2127 EntityUtil.CheckEntityKeysMatch(entry.WrappedEntity, value);
2131 _inRelationshipFixup = false;
2134 // Keeping the entity and entry keys in sync.
2135 entry.EntityKey = value;
2137 //Internally, entry.EntityKey asserts that entry._entityKey and entityWithKey.EntityKey are equal.
2138 Debug.Assert(value == entry.EntityKey, "The new key was not set onto the entry correctly");
2144 /// Finds an ObjectStateEntry for the given entity and changes its state to the new state.
2145 /// The operation does not trigger cascade deletion.
2146 /// The operation may change state of adjacent relationships.
2148 /// <param name="entity">entity which state should be changed</param>
2149 /// <param name="entityState">new state of the entity</param>
2150 /// <returns>entry associated with entity</returns>
2151 public ObjectStateEntry ChangeObjectState(object entity, EntityState entityState)
2153 EntityUtil.CheckArgumentNull(entity, "entity");
2154 EntityUtil.CheckValidStateForChangeEntityState(entityState);
2156 EntityEntry entry = null;
2158 this.TransactionManager.BeginLocalPublicAPI();
2161 EntityKey key = entity as EntityKey;
2162 entry = (key != null) ?
2163 this.FindEntityEntry(key) :
2164 this.FindEntityEntry(entity);
2168 if (entityState == EntityState.Detached)
2170 return null; // No-op
2172 throw EntityUtil.NoEntryExistsForObject(entity);
2175 entry.ChangeObjectState(entityState);
2179 this.TransactionManager.EndLocalPublicAPI();
2186 /// Changes state of a relationship between two entities.
2189 /// Both entities must be already tracked by the ObjectContext.
2191 /// <param name="sourceEntity">The instance of the source entity or the EntityKey of the source entity</param>
2192 /// <param name="targetEntity">The instance of the target entity or the EntityKey of the target entity</param>
2193 /// <param name="navigationProperty">The name of the navigation property on the source entity</param>
2194 /// <param name="relationshipState">The requested state of the relationship</param>
2195 /// <returns>The ObjectStateEntry for changed relationship</returns>
2196 public ObjectStateEntry ChangeRelationshipState(
2197 object sourceEntity,
2198 object targetEntity,
2199 string navigationProperty,
2200 EntityState relationshipState)
2202 EntityEntry sourceEntry;
2203 EntityEntry targetEntry;
2205 this.VerifyParametersForChangeRelationshipState(sourceEntity, targetEntity, out sourceEntry, out targetEntry);
2206 EntityUtil.CheckStringArgument(navigationProperty, "navigationProperty");
2208 RelatedEnd relatedEnd = (RelatedEnd)sourceEntry.WrappedEntity.RelationshipManager.GetRelatedEnd(navigationProperty);
2210 return this.ChangeRelationshipState(sourceEntry, targetEntry, relatedEnd, relationshipState);
2214 /// Changes state of a relationship between two entities.
2217 /// Both entities must be already tracked by the ObjectContext.
2219 /// <param name="sourceEntity">The instance of the source entity or the EntityKey of the source entity</param>
2220 /// <param name="targetEntity">The instance of the target entity or the EntityKey of the target entity</param>
2221 /// <param name="navigationPropertySelector">A LINQ expression specifying the navigation property</param>
2222 /// <param name="relationshipState">The requested state of the relationship</param>
2223 /// <returns>The ObjectStateEntry for changed relationship</returns>
2224 public ObjectStateEntry ChangeRelationshipState<TEntity>(
2225 TEntity sourceEntity,
2226 object targetEntity,
2227 Expression<Func<TEntity, object>> navigationPropertySelector,
2228 EntityState relationshipState) where TEntity : class
2230 EntityEntry sourceEntry;
2231 EntityEntry targetEntry;
2233 this.VerifyParametersForChangeRelationshipState(sourceEntity, targetEntity, out sourceEntry, out targetEntry);
2235 // We used to throw an ArgumentException if the expression contained a Convert. Now we remove the convert,
2236 // but if we still need to throw, then we should still throw an ArgumentException to avoid a breaking change.
2237 // Therefore, we keep track of whether or not we removed the convert.
2238 bool removedConvert;
2239 string navigationProperty = ObjectContext.ParsePropertySelectorExpression<TEntity>(navigationPropertySelector, out removedConvert);
2240 RelatedEnd relatedEnd = (RelatedEnd)sourceEntry.WrappedEntity.RelationshipManager.GetRelatedEnd(navigationProperty, throwArgumentException: removedConvert);
2242 return this.ChangeRelationshipState(sourceEntry, targetEntry, relatedEnd, relationshipState);
2246 /// Changes state of a relationship between two entities.
2249 /// Both entities must be already tracked by the ObjectContext.
2251 /// <param name="sourceEntity">The instance of the source entity or the EntityKey of the source entity</param>
2252 /// <param name="targetEntity">The instance of the target entity or the EntityKey of the target entity</param>
2253 /// <param name="relationshipName">The name of relationship</param>
2254 /// <param name="targetRoleName">The target role name of the relationship</param>
2255 /// <param name="relationshipState">The requested state of the relationship</param>
2256 /// <returns>The ObjectStateEntry for changed relationship</returns>
2257 public ObjectStateEntry ChangeRelationshipState(
2258 object sourceEntity,
2259 object targetEntity,
2260 string relationshipName,
2261 string targetRoleName,
2262 EntityState relationshipState)
2264 EntityEntry sourceEntry;
2265 EntityEntry targetEntry;
2267 this.VerifyParametersForChangeRelationshipState(sourceEntity, targetEntity, out sourceEntry, out targetEntry);
2269 RelatedEnd relatedEnd = sourceEntry.WrappedEntity.RelationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName);
2271 return this.ChangeRelationshipState(sourceEntry, targetEntry, relatedEnd, relationshipState);
2274 private ObjectStateEntry ChangeRelationshipState(
2275 EntityEntry sourceEntry,
2276 EntityEntry targetEntry,
2277 RelatedEnd relatedEnd,
2278 EntityState relationshipState)
2280 VerifyInitialStateForChangeRelationshipState(sourceEntry, targetEntry, relatedEnd, relationshipState);
2282 RelationshipWrapper relationshipWrapper = new RelationshipWrapper((AssociationSet)relatedEnd.RelationshipSet,
2283 new KeyValuePair<string, EntityKey>(relatedEnd.SourceRoleName, sourceEntry.EntityKey),
2284 new KeyValuePair<string, EntityKey>(relatedEnd.TargetRoleName, targetEntry.EntityKey));
2286 RelationshipEntry relationshipEntry = this.FindRelationship(relationshipWrapper);
2288 if (relationshipEntry == null && relationshipState == EntityState.Detached)
2294 this.TransactionManager.BeginLocalPublicAPI();
2297 if (relationshipEntry != null)
2299 relationshipEntry.ChangeRelationshipState(targetEntry, relatedEnd, relationshipState);
2303 relationshipEntry = this.CreateRelationship(targetEntry, relatedEnd, relationshipWrapper, relationshipState);
2308 this.TransactionManager.EndLocalPublicAPI();
2311 Debug.Assert(relationshipState != EntityState.Detached || relationshipEntry.State == EntityState.Detached, "state should be detached");
2312 return relationshipState == EntityState.Detached ? null : relationshipEntry;
2315 private void VerifyParametersForChangeRelationshipState(object sourceEntity, object targetEntity, out EntityEntry sourceEntry, out EntityEntry targetEntry)
2317 EntityUtil.CheckArgumentNull(sourceEntity, "sourceEntity");
2318 EntityUtil.CheckArgumentNull(targetEntity, "targetEntity");
2320 sourceEntry = this.GetEntityEntryByObjectOrEntityKey(sourceEntity);
2321 targetEntry = this.GetEntityEntryByObjectOrEntityKey(targetEntity);
2324 private void VerifyInitialStateForChangeRelationshipState(EntityEntry sourceEntry, EntityEntry targetEntry, RelatedEnd relatedEnd, EntityState relationshipState)
2326 relatedEnd.VerifyType(targetEntry.WrappedEntity);
2328 if (relatedEnd.IsForeignKey)
2330 throw new NotSupportedException(System.Data.Entity.Strings.ObjectStateManager_ChangeRelationshipStateNotSupportedForForeignKeyAssociations);
2333 EntityUtil.CheckValidStateForChangeRelationshipState(relationshipState, "relationshipState");
2335 if ((sourceEntry.State == EntityState.Deleted || targetEntry.State == EntityState.Deleted) &&
2336 (relationshipState != EntityState.Deleted && relationshipState != EntityState.Detached))
2338 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotChangeRelationshipStateEntityDeleted);
2341 if ((sourceEntry.State == EntityState.Added || targetEntry.State == EntityState.Added) &&
2342 (relationshipState != EntityState.Added && relationshipState != EntityState.Detached))
2344 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotChangeRelationshipStateEntityAdded);
2348 private RelationshipEntry CreateRelationship(EntityEntry targetEntry, RelatedEnd relatedEnd, RelationshipWrapper relationshipWrapper, EntityState requestedState)
2350 Debug.Assert(requestedState != EntityState.Modified, "relationship cannot be in Modified state");
2352 RelationshipEntry relationshipEntry = null;
2354 switch (requestedState)
2356 case EntityState.Added:
2357 relatedEnd.Add(targetEntry.WrappedEntity,
2358 applyConstraints: true,
2359 addRelationshipAsUnchanged: false,
2360 relationshipAlreadyExists: false,
2361 allowModifyingOtherEndOfRelationship: false,
2362 forceForeignKeyChanges: true);
2363 relationshipEntry = this.FindRelationship(relationshipWrapper);
2364 Debug.Assert(relationshipEntry != null, "null relationshipEntry");
2366 case EntityState.Unchanged:
2367 relatedEnd.Add(targetEntry.WrappedEntity,
2368 applyConstraints: true,
2369 addRelationshipAsUnchanged: false,
2370 relationshipAlreadyExists: false,
2371 allowModifyingOtherEndOfRelationship: false,
2372 forceForeignKeyChanges: true);
2373 relationshipEntry = this.FindRelationship(relationshipWrapper);
2374 relationshipEntry.AcceptChanges();
2376 case EntityState.Deleted:
2377 relationshipEntry = this.AddNewRelation(relationshipWrapper, EntityState.Deleted);
2379 case EntityState.Detached:
2383 Debug.Assert(false, "Invalid requested state");
2387 return relationshipEntry;
2391 private EntityEntry GetEntityEntryByObjectOrEntityKey(object o)
2393 EntityKey key = o as EntityKey;
2394 EntityEntry entry = (key != null) ?
2395 this.FindEntityEntry(key) :
2396 this.FindEntityEntry(o);
2400 throw EntityUtil.NoEntryExistsForObject(o);
2403 if (entry.IsKeyEntry)
2405 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotChangeRelationshipStateKeyEntry);
2413 /// Retrieve the corresponding IEntityStateEntry for the given EntityKey.
2415 /// <exception cref="ArgumentNullException">if key is null</exception>
2416 /// <exception cref="ArgumentException">if key is not found</exception>
2417 IEntityStateEntry IEntityStateManager.GetEntityStateEntry(EntityKey key)
2419 return (IEntityStateEntry)GetEntityEntry(key);
2423 /// Retrieve the corresponding ObjectStateEntry for the given EntityKey.
2425 /// <exception cref="ArgumentNullException">if key is null</exception>
2426 /// <exception cref="ArgumentException">if key is not found</exception>
2427 public ObjectStateEntry GetObjectStateEntry(EntityKey key)
2429 ObjectStateEntry entry;
2430 if (!TryGetObjectStateEntry(key, out entry))
2432 throw EntityUtil.NoEntryExistForEntityKey();
2437 internal EntityEntry GetEntityEntry(EntityKey key)
2440 if (!TryGetEntityEntry(key, out entry))
2442 throw EntityUtil.NoEntryExistForEntityKey();
2449 /// Given an entity, of type object, return the corresponding ObjectStateEntry.
2451 /// <param name="entity"></param>
2452 /// <returns>The corresponding ObjectStateEntry for this object.</returns>
2453 public ObjectStateEntry GetObjectStateEntry(object entity)
2455 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
2456 ObjectStateEntry entry;
2457 if (!TryGetObjectStateEntry(entity, out entry))
2459 throw EntityUtil.NoEntryExistsForObject(entity);
2464 internal EntityEntry GetEntityEntry(object entity)
2466 Debug.Assert(entity != null, "entity is null");
2467 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
2469 EntityEntry entry = FindEntityEntry(entity);
2472 throw EntityUtil.NoEntryExistsForObject(entity);
2478 /// Retrieve the corresponding ObjectStateEntry for the given object.
2480 /// <param name="entity"></param>
2481 /// <param name="entry"></param>
2482 /// <returns>true if the corresponding ObjectStateEntry was found</returns>
2483 public bool TryGetObjectStateEntry(object entity, out ObjectStateEntry entry)
2485 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
2487 EntityUtil.CheckArgumentNull(entity, "entity");
2489 EntityKey entityKey = entity as EntityKey;
2490 if (entityKey != null)
2492 return TryGetObjectStateEntry(entityKey, out entry);
2496 entry = FindEntityEntry(entity);
2499 return entry != null;
2503 /// Retrieve the corresponding IEntityStateEntry for the given EntityKey.
2505 /// <returns>true if the corresponding IEntityStateEntry was found</returns>
2506 /// <exception cref="ArgumentNullException">if key is null</exception>
2507 bool IEntityStateManager.TryGetEntityStateEntry(EntityKey key, out IEntityStateEntry entry)
2509 // Because the passed in IEntityStateEntry reference isn't necessarily an
2510 // ObjectStateEntry, we have to declare our own local copy, use it for the outparam of
2511 // TryGetObjectStateEntry, and then set it onto our outparam if we successfully find
2512 // something (at that point we know we can cast to IEntityStateEntry), but we just can't
2513 // cast in the other direction.
2514 ObjectStateEntry objectStateEntry;
2515 bool result = TryGetObjectStateEntry(key, out objectStateEntry);
2516 entry = (IEntityStateEntry)objectStateEntry;
2521 /// Given a key that represents an entity on the dependent side of a FK, this method attempts to return the key of the
2522 /// entity on the principal side of the FK. If the two entities both exist in the context, then the primary key of
2523 /// the principal entity is found and returned. If the principal entity does not exist in the context, then a key
2524 /// for it is built up from the foreign key values contained in the dependent entity.
2526 /// <param name="dependentKey">The key of the dependent entity</param>
2527 /// <param name="principalRole">The role indicating the FK to navigate</param>
2528 /// <param name="principalKey">Set to the principal key or null on return</param>
2529 /// <returns>True if the principal key was found or built; false if it could not be found or built</returns>
2530 bool IEntityStateManager.TryGetReferenceKey(EntityKey dependentKey, AssociationEndMember principalRole, out EntityKey principalKey)
2532 EntityEntry dependentEntry;
2533 if (!TryGetEntityEntry(dependentKey, out dependentEntry))
2535 principalKey = null;
2538 return dependentEntry.TryGetReferenceKey(principalRole, out principalKey);
2542 /// Retrieve the corresponding ObjectStateEntry for the given EntityKey.
2544 /// <returns>true if the corresponding ObjectStateEntry was found</returns>
2545 /// <exception cref="ArgumentNullException">if key is null</exception>
2546 public bool TryGetObjectStateEntry(EntityKey key, out ObjectStateEntry entry)
2549 EntityEntry entityEntry;
2550 result = this.TryGetEntityEntry(key, out entityEntry);
2551 entry = entityEntry;
2555 internal bool TryGetEntityEntry(EntityKey key, out EntityEntry entry)
2557 entry = null; // must set before checking for null key
2558 EntityUtil.CheckArgumentNull(key, "key");
2560 if (key.IsTemporary)
2561 { // only temporary keys exist in the added state
2562 result = ((null != _addedEntityStore) && _addedEntityStore.TryGetValue(key, out entry));
2565 { // temporary keys do not exist in the unchanged, modified, deleted states.
2566 result = (((null != _unchangedEntityStore) && _unchangedEntityStore.TryGetValue(key, out entry)) ||
2567 ((null != _modifiedEntityStore) && _modifiedEntityStore.TryGetValue(key, out entry)) ||
2568 ((null != _deletedEntityStore) && _deletedEntityStore.TryGetValue(key, out entry)));
2570 Debug.Assert(result == (null != entry), "result and entry mismatch");
2574 internal EntityEntry FindEntityEntry(EntityKey key)
2576 EntityEntry entry = null;
2577 if (null != (object)key)
2579 TryGetEntityEntry(key, out entry);
2585 /// Retrieve the corresponding EntityEntry for the given entity.
2586 /// Returns null if key is unavailable or passed entity is null.
2588 internal EntityEntry FindEntityEntry(object entity)
2590 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
2591 Debug.Assert(!(entity is EntityKey), "Object is a EntityKey instead of raw entity.");
2592 EntityEntry entry = null;
2593 IEntityWithKey entityWithKey = entity as IEntityWithKey;
2595 if (entityWithKey != null)
2597 EntityKey entityEntityKey = entityWithKey.EntityKey;
2598 if (null != (object)entityEntityKey)
2600 TryGetEntityEntry(entityEntityKey, out entry);
2605 TryGetEntryFromKeylessStore(entity, out entry);
2608 // If entity is detached, then entry.Entity won't have the same object reference.
2609 // This can happen if the same entity is loaded with, then without, tracking
2610 // SQL BU Defect Tracking 520058
2611 if (entry != null && !object.ReferenceEquals(entity, entry.Entity))
2620 /// Gets a RelationshipManager for the given entity. For entities that implement IEntityWithRelationships,
2621 /// the RelationshipManager is obtained through that interface. For other types of entity, the RelationshipManager
2622 /// that is being tracked internally is returned. This means that a RelationshipManager for an entity that
2623 /// does not implement IEntityWithRelationships can only be obtained if the entity is being tracked by the
2624 /// ObjectStateManager.
2625 /// Note that all code generated entities that inherit from EntityObject automatically implement IEntityWithRelationships.
2627 /// <param name="entity">The entity for which to return a RelationshipManager</param>
2628 /// <returns>The RelationshipManager</returns>
2629 /// <exception cref="InvalidOperationException">The entity does not implement IEntityWithRelationships and is not tracked by this ObjectStateManager</exception>
2630 public RelationshipManager GetRelationshipManager(object entity)
2632 RelationshipManager rm;
2633 if (!TryGetRelationshipManager(entity, out rm))
2635 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotGetRelationshipManagerForDetachedPocoEntity);
2641 /// Gets a RelationshipManager for the given entity. For entities that implement IEntityWithRelationships,
2642 /// the RelationshipManager is obtained through that interface. For other types of entity, the RelationshipManager
2643 /// that is being tracked internally is returned. This means that a RelationshipManager for an entity that
2644 /// does not implement IEntityWithRelationships can only be obtained if the entity is being tracked by the
2645 /// ObjectStateManager.
2646 /// Note that all code generated entities that inherit from EntityObject automatically implement IEntityWithRelationships.
2648 /// <param name="entity">The entity for which to return a RelationshipManager</param>
2649 /// <param name="relationshipManager">The RelationshipManager, or null if none was found</param>
2650 /// <returns>True if a RelationshipManager was found; false if The entity does not implement IEntityWithRelationships and is not tracked by this ObjectStateManager</returns>
2651 public bool TryGetRelationshipManager(object entity, out RelationshipManager relationshipManager)
2653 EntityUtil.CheckArgumentNull(entity, "entity");
2654 IEntityWithRelationships withRelationships = entity as IEntityWithRelationships;
2655 if (withRelationships != null)
2657 relationshipManager = withRelationships.RelationshipManager;
2658 if (relationshipManager == null)
2660 throw EntityUtil.UnexpectedNullRelationshipManager();
2662 if (relationshipManager.WrappedOwner.Entity != entity)
2664 throw EntityUtil.InvalidRelationshipManagerOwner();
2669 IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(entity, this);
2670 if (wrappedEntity.Context == null)
2672 relationshipManager = null;
2675 relationshipManager = wrappedEntity.RelationshipManager;
2680 internal void ChangeState(RelationshipEntry entry, EntityState oldState, EntityState newState)
2682 if (newState == EntityState.Detached)
2684 // If we're transitioning to detached, completely remove all traces of the entry.
2685 DeleteRelationshipFromLookup((RelationshipEntry)entry);
2687 // delay removal until RelationshipEnds is done
2688 RemoveObjectStateEntryFromDictionary(entry, oldState);
2694 RemoveObjectStateEntryFromDictionary(entry, oldState);
2696 // If we're transitioning to something other than detached, add the
2697 // entry to the appropriate dictionary.
2698 AddRelationshipEntryToDictionary(entry, newState);
2701 // do not fire event for relationship
2704 internal void ChangeState(EntityEntry entry, EntityState oldState, EntityState newState)
2706 bool fireEvent = !entry.IsKeyEntry;
2707 if (newState == EntityState.Detached)
2709 // If we're transitioning to detached, completely remove all traces of the entry.
2711 // SQLBU 508278: Object State Manager should not allow "dangling" relationships to stay in the state manager.
2712 // Remove potential dangling relationships
2713 Debug.Assert((object)entry.EntityKey != null, "attached entry must have a key");
2714 foreach (RelationshipEntry relationshipEntry in CopyOfRelationshipsByKey(entry.EntityKey))
2716 ChangeState(relationshipEntry, relationshipEntry.State, EntityState.Detached);
2719 // delay removal until RelationshipEnds is done
2720 RemoveObjectStateEntryFromDictionary(entry, oldState);
2722 IEntityWrapper wrappedEntity = entry.WrappedEntity; // we have to cache the entity before detaching it totally so we can fire event
2724 // Prevent firing two events for removal from the context during rollback.
2725 if (fireEvent && wrappedEntity.Entity != null && !TransactionManager.IsAttachTracking)
2727 // first notify the view
2728 OnEntityDeleted(CollectionChangeAction.Remove, wrappedEntity.Entity);
2729 OnObjectStateManagerChanged(CollectionChangeAction.Remove, wrappedEntity.Entity);
2734 RemoveObjectStateEntryFromDictionary(entry, oldState);
2736 // If we're transitioning to something other than detached, add the
2737 // entry to the appropriate dictionary.
2738 AddEntityEntryToDictionary(entry, newState);
2741 if (newState == EntityState.Deleted)
2743 entry.RemoveFromForeignKeyIndex();
2744 ForgetEntryWithConceptualNull(entry, resetAllKeys: true);
2747 // fire collectionChanged event only when an entity is being deleted (this includes deleting an added entity which becomes detached)
2748 OnEntityDeleted(CollectionChangeAction.Remove, entry.Entity);
2749 OnObjectStateManagerChanged(CollectionChangeAction.Remove, entry.Entity);
2754 private void AddRelationshipEntryToDictionary(RelationshipEntry entry, EntityState state)
2756 Debug.Assert(entry.IsRelationship, "expecting IsRelationship");
2757 Debug.Assert(null != entry.RelationshipWrapper, "null RelationshipWrapper");
2759 Dictionary<RelationshipWrapper, RelationshipEntry> dictionaryToAdd = null;
2762 case EntityState.Unchanged:
2763 if (null == _unchangedRelationshipStore)
2765 _unchangedRelationshipStore = new Dictionary<RelationshipWrapper, RelationshipEntry>();
2767 dictionaryToAdd = _unchangedRelationshipStore;
2769 case EntityState.Added:
2770 if (null == _addedRelationshipStore)
2772 _addedRelationshipStore = new Dictionary<RelationshipWrapper, RelationshipEntry>();
2774 dictionaryToAdd = _addedRelationshipStore;
2776 case EntityState.Deleted:
2777 if (null == _deletedRelationshipStore)
2779 _deletedRelationshipStore = new Dictionary<RelationshipWrapper, RelationshipEntry>();
2781 dictionaryToAdd = _deletedRelationshipStore;
2784 Debug.Assert(false, "Invalid state.");
2787 Debug.Assert(dictionaryToAdd != null, "Couldn't find the correct relationship dictionary based on entity state.");
2788 dictionaryToAdd.Add(entry.RelationshipWrapper, entry);
2791 private void AddEntityEntryToDictionary(EntityEntry entry, EntityState state)
2793 Debug.Assert(null != (object)entry.EntityKey, "missing EntityKey");
2795 if (entry.RequiresAnyChangeTracking)
2797 _detectChangesNeeded = true;
2800 Dictionary<EntityKey, EntityEntry> dictionaryToAdd = null;
2803 case EntityState.Unchanged:
2804 if (null == _unchangedEntityStore)
2806 _unchangedEntityStore = new Dictionary<EntityKey, EntityEntry>();
2808 dictionaryToAdd = _unchangedEntityStore;
2809 Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Unchanged state");
2811 case EntityState.Added:
2812 if (null == _addedEntityStore)
2814 _addedEntityStore = new Dictionary<EntityKey, EntityEntry>();
2816 dictionaryToAdd = _addedEntityStore;
2817 Debug.Assert(entry.EntityKey.IsTemporary, "adding non-temporary entity key into Added state");
2819 case EntityState.Deleted:
2820 if (null == _deletedEntityStore)
2822 _deletedEntityStore = new Dictionary<EntityKey, EntityEntry>();
2824 dictionaryToAdd = _deletedEntityStore;
2825 Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Deleted state");
2827 case EntityState.Modified:
2828 if (null == _modifiedEntityStore)
2830 _modifiedEntityStore = new Dictionary<EntityKey, EntityEntry>();
2832 dictionaryToAdd = _modifiedEntityStore;
2833 Debug.Assert(!entry.EntityKey.IsTemporary, "adding temporary entity key into Modified state");
2836 Debug.Assert(false, "Invalid state.");
2841 Debug.Assert(dictionaryToAdd != null, "Couldn't find the correct entity dictionary based on entity state.");
2842 dictionaryToAdd.Add(entry.EntityKey, entry);
2843 AddEntryToKeylessStore(entry);
2847 private void AddEntryToKeylessStore(EntityEntry entry)
2849 // Add an entry that doesn't implement IEntityWithKey to the keyless lookup.
2850 // It is used to lookup ObjectStateEntries when all we have is an entity reference.
2851 if (null != entry.Entity && !(entry.Entity is IEntityWithKey))
2853 if (null == _keylessEntityStore)
2855 _keylessEntityStore = new Dictionary<object, EntityEntry>(new ObjectReferenceEqualityComparer());
2857 if (!_keylessEntityStore.ContainsKey(entry.Entity))
2859 _keylessEntityStore.Add(entry.Entity, entry);
2865 /// Removes the given cache entry from the appropriate dictionary, based on
2866 /// the given state and whether or not the entry represents a relationship.
2868 private void RemoveObjectStateEntryFromDictionary(RelationshipEntry entry, EntityState state)
2870 // Determine the appropriate dictionary from which to remove the entry.
2871 Dictionary<RelationshipWrapper, RelationshipEntry> dictionaryContainingEntry = null;
2874 case EntityState.Unchanged:
2875 dictionaryContainingEntry = _unchangedRelationshipStore;
2877 case EntityState.Added:
2878 dictionaryContainingEntry = _addedRelationshipStore;
2880 case EntityState.Deleted:
2881 dictionaryContainingEntry = _deletedRelationshipStore;
2884 Debug.Assert(false, "Invalid state.");
2887 Debug.Assert(dictionaryContainingEntry != null, "Couldn't find the correct relationship dictionary based on entity state.");
2889 bool result = dictionaryContainingEntry.Remove(entry.RelationshipWrapper);
2890 Debug.Assert(result, "The correct relationship dictionary based on entity state doesn't contain the entry.");
2892 if (0 == dictionaryContainingEntry.Count)
2893 { // reduce unused dictionary capacity
2896 case EntityState.Unchanged:
2897 _unchangedRelationshipStore = null;
2899 case EntityState.Added:
2900 _addedRelationshipStore = null;
2902 case EntityState.Deleted:
2903 _deletedRelationshipStore = null;
2910 /// Removes the given cache entry from the appropriate dictionary, based on
2911 /// the given state and whether or not the entry represents a relationship.
2913 private void RemoveObjectStateEntryFromDictionary(EntityEntry entry, EntityState state)
2915 Dictionary<EntityKey, EntityEntry> dictionaryContainingEntry = null;
2918 case EntityState.Unchanged:
2919 dictionaryContainingEntry = _unchangedEntityStore;
2921 case EntityState.Added:
2922 dictionaryContainingEntry = _addedEntityStore;
2924 case EntityState.Deleted:
2925 dictionaryContainingEntry = _deletedEntityStore;
2927 case EntityState.Modified:
2928 dictionaryContainingEntry = _modifiedEntityStore;
2931 Debug.Assert(false, "Invalid state.");
2934 Debug.Assert(dictionaryContainingEntry != null, "Couldn't find the correct entity dictionary based on entity state.");
2936 bool result = dictionaryContainingEntry.Remove(entry.EntityKey);
2937 Debug.Assert(result, "The correct entity dictionary based on entity state doesn't contain the entry.");
2938 RemoveEntryFromKeylessStore(entry.WrappedEntity);
2940 if (0 == dictionaryContainingEntry.Count)
2941 { // reduce unused dictionary capacity
2944 case EntityState.Unchanged:
2945 _unchangedEntityStore = null;
2947 case EntityState.Added:
2948 _addedEntityStore = null;
2950 case EntityState.Deleted:
2951 _deletedEntityStore = null;
2953 case EntityState.Modified:
2954 _modifiedEntityStore = null;
2960 internal void RemoveEntryFromKeylessStore(IEntityWrapper wrappedEntity)
2962 // Remove and entry from the store containing entities not implementing IEntityWithKey
2963 if (null != wrappedEntity && null != wrappedEntity.Entity && !(wrappedEntity.Entity is IEntityWithKey))
2965 _keylessEntityStore.Remove(wrappedEntity.Entity);
2970 /// If a corresponding StateManagerTypeMetadata exists, it is returned.
2971 /// Otherwise, a StateManagerTypeMetadata is created and cached.
2973 internal StateManagerTypeMetadata GetOrAddStateManagerTypeMetadata(Type entityType, EntitySet entitySet)
2975 Debug.Assert(entityType != null, "entityType cannot be null.");
2976 Debug.Assert(entitySet != null, "must have entitySet to correctly qualify Type");
2978 StateManagerTypeMetadata typeMetadata;
2979 if (!_metadataMapping.TryGetValue(new EntitySetQualifiedType(entityType, entitySet), out typeMetadata))
2981 // GetMap doesn't have a mechanism to qualify identity with EntityContainerName
2982 // This is unimportant until each EntityContainer can have its own ObjectTypeMapping.
2983 typeMetadata = AddStateManagerTypeMetadata(entitySet, (ObjectTypeMapping)
2984 MetadataWorkspace.GetMap(entityType.FullName, DataSpace.OSpace, DataSpace.OCSpace));
2986 return typeMetadata;
2990 /// If a corresponding StateManagerTypeMetadata exists, it is returned.
2991 /// Otherwise, a StateManagerTypeMetadata is created and cached.
2993 internal StateManagerTypeMetadata GetOrAddStateManagerTypeMetadata(EdmType edmType)
2995 Debug.Assert(edmType != null, "edmType cannot be null.");
2996 Debug.Assert(Helper.IsEntityType(edmType) ||
2997 Helper.IsComplexType(edmType),
2998 "only expecting ComplexType or EntityType");
3000 StateManagerTypeMetadata typeMetadata;
3001 if (!_metadataStore.TryGetValue(edmType, out typeMetadata))
3003 typeMetadata = AddStateManagerTypeMetadata(edmType, (ObjectTypeMapping)
3004 MetadataWorkspace.GetMap(edmType, DataSpace.OCSpace));
3006 return typeMetadata;
3010 /// Creates an instance of StateManagerTypeMetadata from the given EdmType and ObjectMapping,
3011 /// and stores it in the metadata cache. The new instance is returned.
3013 private StateManagerTypeMetadata AddStateManagerTypeMetadata(EntitySet entitySet, ObjectTypeMapping mapping)
3015 Debug.Assert(null != entitySet, "null entitySet");
3016 Debug.Assert(null != mapping, "null mapping");
3018 EdmType edmType = mapping.EdmType;
3019 Debug.Assert(Helper.IsEntityType(edmType) ||
3020 Helper.IsComplexType(edmType),
3021 "not Entity or complex type");
3023 StateManagerTypeMetadata typeMetadata;
3024 if (!_metadataStore.TryGetValue(edmType, out typeMetadata))
3026 typeMetadata = new StateManagerTypeMetadata(edmType, mapping);
3027 _metadataStore.Add(edmType, typeMetadata);
3031 EntitySetQualifiedType entitySetQualifiedType = new EntitySetQualifiedType(mapping.ClrType.ClrType, entitySet);
3032 if (!_metadataMapping.ContainsKey(entitySetQualifiedType))
3034 _metadataMapping.Add(entitySetQualifiedType, typeMetadata);
3038 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Mapping_CannotMapCLRTypeMultipleTimes(typeMetadata.CdmMetadata.EdmType.FullName));
3040 return typeMetadata;
3043 private StateManagerTypeMetadata AddStateManagerTypeMetadata(EdmType edmType, ObjectTypeMapping mapping)
3045 Debug.Assert(null != edmType, "null EdmType");
3046 Debug.Assert(Helper.IsEntityType(edmType) ||
3047 Helper.IsComplexType(edmType),
3048 "not Entity or complex type");
3050 StateManagerTypeMetadata typeMetadata = new StateManagerTypeMetadata(edmType, mapping);
3051 _metadataStore.Add(edmType, typeMetadata);
3052 return typeMetadata;
3056 /// Mark the ObjectStateManager as disposed
3058 internal void Dispose()
3063 internal bool IsDisposed
3065 get { return _isDisposed; }
3069 /// For every tracked entity which doesn't implement IEntityWithChangeTracker detect changes in the entity's property values
3070 /// and marks appropriate ObjectStateEntry as Modified.
3071 /// For every tracked entity which doesn't implement IEntityWithRelationships detect changes in its relationships.
3073 /// The method is used internally by ObjectContext.SaveChanges() but can be also used if user wants to detect changes
3074 /// and have ObjectStateEntries in appropriate state before the SaveChanges() method is called.
3076 internal void DetectChanges()
3078 var entries = this.GetEntityEntriesForDetectChanges();
3079 if (entries == null)
3084 if (this.TransactionManager.BeginDetectChanges())
3088 // Populate Transact-ionManager.DeletedRelationshipsByGraph and TransactionManager.AddedRelationshipsByGraph
3089 this.DetectChangesInNavigationProperties(entries);
3091 // Populate TransactionManager.ChangedForeignKeys
3092 this.DetectChangesInScalarAndComplexProperties(entries);
3094 // Populate TransactionManager.DeletedRelationshipsByForeignKey and TransactionManager.AddedRelationshipsByForeignKey
3095 this.DetectChangesInForeignKeys(entries);
3097 // Detect conflicts between changes to FK and navigation properties
3098 this.DetectConflicts(entries);
3100 // Update graph and FKs
3101 this.TransactionManager.BeginAlignChanges();
3102 this.AlignChangesInRelationships(entries);
3106 this.TransactionManager.EndAlignChanges();
3107 this.TransactionManager.EndDetectChanges();
3112 private void DetectConflicts(IList<EntityEntry> entries)
3114 TransactionManager tm = this.TransactionManager;
3115 foreach (EntityEntry entry in entries)
3117 //NOTE: DetectChangesInNavigationProperties will have created two navigation changes
3118 // even if the user has only made a single change in there graph, this means we
3119 // only need to check for conflicts on the local end of the relationship.
3121 //Find all relationships being added for this entity
3122 Dictionary<RelatedEnd, HashSet<IEntityWrapper>> addedRelationshipsByGraph;
3123 tm.AddedRelationshipsByGraph.TryGetValue(entry.WrappedEntity, out addedRelationshipsByGraph);
3124 Dictionary<RelatedEnd, HashSet<EntityKey>> addedRelationshipsByForeignKey;
3125 tm.AddedRelationshipsByForeignKey.TryGetValue(entry.WrappedEntity, out addedRelationshipsByForeignKey);
3127 //Ensure new graph relationships do not involve a Deleted Entity
3128 if (addedRelationshipsByGraph != null && addedRelationshipsByGraph.Count > 0)
3130 if (entry.State == EntityState.Deleted)
3132 throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
3136 //Check for conflicting FK changes and changes to PKs
3137 if (addedRelationshipsByForeignKey != null)
3139 foreach (var pair in addedRelationshipsByForeignKey)
3141 //Ensure persisted dependents of identifying FK relationships are not being re-parented
3142 if (entry.State == EntityState.Unchanged || entry.State == EntityState.Modified)
3144 if (pair.Key.IsDependentEndOfReferentialConstraint(true) && pair.Value.Count > 0)
3146 throw EntityUtil.CannotChangeReferentialConstraintProperty();
3150 //Make sure each EntityReference only has one FK change
3151 //(It's possible to have more than one in an identifying 1:1/0..1 relationship
3152 // when two dependent FKs are set to match one principal)
3153 EntityReference reference = pair.Key as EntityReference;
3154 if (reference != null)
3156 if (pair.Value.Count > 1)
3158 throw new InvalidOperationException(
3159 System.Data.Entity.Strings.ObjectStateManager_ConflictingChangesOfRelationshipDetected(
3160 pair.Key.RelationshipNavigation.To,
3161 pair.Key.RelationshipNavigation.RelationshipName));
3167 //Check for conflicting reference changes and changes that will change a PK
3168 if (addedRelationshipsByGraph != null)
3170 // Retrieve key values from related entities
3171 Dictionary<string, KeyValuePair<object, IntBox>> properties = new Dictionary<string, KeyValuePair<object, IntBox>>();
3173 foreach (var pair in addedRelationshipsByGraph)
3175 //Ensure persisted dependents of identifying FK relationships are not being re-parented
3176 if (pair.Key.IsForeignKey && (entry.State == EntityState.Unchanged || entry.State == EntityState.Modified))
3178 //Any reference change is invalid because it is not possible to have a persisted
3179 //principal that matches the dependents key without the reference already being set
3180 if (pair.Key.IsDependentEndOfReferentialConstraint(true) && pair.Value.Count > 0)
3182 throw EntityUtil.CannotChangeReferentialConstraintProperty();
3186 //Check that each EntityReference only has one reference change
3187 //AND that the change agrees with the FK change if present
3188 EntityReference reference = pair.Key as EntityReference;
3189 if (reference != null)
3191 if (pair.Value.Count > 1)
3193 throw new InvalidOperationException(
3194 System.Data.Entity.Strings.ObjectStateManager_ConflictingChangesOfRelationshipDetected(
3195 pair.Key.RelationshipNavigation.To,
3196 pair.Key.RelationshipNavigation.RelationshipName));
3198 else if (pair.Value.Count == 1)
3200 //We know there is a max of one FK change as we checked this already
3201 IEntityWrapper addedEntity = pair.Value.First();
3203 //See if there is also a new FK for this RelatedEnd
3204 HashSet<EntityKey> newFks = null;
3205 if (addedRelationshipsByForeignKey != null)
3207 addedRelationshipsByForeignKey.TryGetValue(pair.Key, out newFks);
3211 // Try the principal key dictionary to see if there is a conflict on the principal side
3212 Dictionary<RelatedEnd, HashSet<EntityKey>> addedRelationshipsByPrincipalKey;
3213 if (tm.AddedRelationshipsByPrincipalKey.TryGetValue(entry.WrappedEntity, out addedRelationshipsByPrincipalKey))
3215 addedRelationshipsByPrincipalKey.TryGetValue(pair.Key, out newFks);
3219 if (newFks != null && newFks.Count > 0)
3221 //Make sure the FK change is consistent with the Reference change
3222 //The following call sometimes creates permanent key of Added entity
3223 EntityKey addedKey = GetPermanentKey(entry.WrappedEntity, reference, addedEntity);
3225 if (addedKey != newFks.First())
3227 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_ConflictingChangesOfRelationshipDetected(
3228 reference.RelationshipNavigation.To,
3229 reference.RelationshipNavigation.RelationshipName));
3234 //If there is no added FK relationship but there is a deleted one then it means
3235 //the FK has been nulled and this will always conflict with an added reference
3236 Dictionary<RelatedEnd, HashSet<EntityKey>> deletedRelationshipsByForeignKey;
3237 if (tm.DeletedRelationshipsByForeignKey.TryGetValue(entry.WrappedEntity, out deletedRelationshipsByForeignKey))
3239 HashSet<EntityKey> removedKeys;
3240 if (deletedRelationshipsByForeignKey.TryGetValue(pair.Key, out removedKeys))
3242 if (removedKeys.Count > 0)
3244 throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_ConflictingChangesOfRelationshipDetected(
3245 reference.RelationshipNavigation.To,
3246 reference.RelationshipNavigation.RelationshipName));
3252 // For each change to the graph, validate that the entity will not have conflicting
3253 // RI constrained property values
3254 // The related entity is detached or added, these are valid cases
3255 // so do not consider their changes in conflict
3256 EntityEntry relatedEntry = FindEntityEntry(addedEntity.Entity);
3257 if (relatedEntry != null &&
3258 (relatedEntry.State == EntityState.Unchanged ||
3259 relatedEntry.State == EntityState.Modified))
3261 Dictionary<string, KeyValuePair<object, IntBox>> retrievedProperties = new Dictionary<string, KeyValuePair<object, IntBox>>();
3262 relatedEntry.GetOtherKeyProperties(retrievedProperties);
3263 // Merge retrievedProperties into the main list of properties
3264 foreach (ReferentialConstraint constraint in ((AssociationType)reference.RelationMetadata).ReferentialConstraints)
3266 if (constraint.ToRole == reference.FromEndProperty)
3268 for (int i = 0; i < constraint.FromProperties.Count; ++i)
3270 EntityEntry.AddOrIncreaseCounter(
3272 constraint.ToProperties[i].Name,
3273 retrievedProperties[constraint.FromProperties[i].Name].Key);
3287 internal EntityKey GetPermanentKey(IEntityWrapper entityFrom, RelatedEnd relatedEndFrom, IEntityWrapper entityTo)
3289 EntityKey entityKey = null;
3290 if (entityTo.ObjectStateEntry != null)
3292 entityKey = entityTo.ObjectStateEntry.EntityKey;
3294 if (entityKey == null || entityKey.IsTemporary)
3296 entityKey = this.CreateEntityKey(this.GetEntitySetOfOtherEnd(entityFrom, relatedEndFrom), entityTo.Entity);
3302 private EntitySet GetEntitySetOfOtherEnd(IEntityWrapper entity, RelatedEnd relatedEnd)
3304 AssociationSet associationSet = (AssociationSet)relatedEnd.RelationshipSet;
3306 EntitySet entitySet = associationSet.AssociationSetEnds[0].EntitySet;
3307 if (entitySet.Name != entity.EntityKey.EntitySetName) //
3313 return associationSet.AssociationSetEnds[1].EntitySet;
3317 private void DetectChangesInForeignKeys(IList<EntityEntry> entries)
3319 foreach (var entry in entries)
3321 if (entry.State == EntityState.Added || entry.State == EntityState.Modified)
3323 entry.DetectChangesInForeignKeys();
3328 private void AlignChangesInRelationships(IList<EntityEntry> entries)
3330 PerformDelete(entries);
3331 PerformAdd(entries);
3334 private void PerformAdd(IList<EntityEntry> entries)
3336 TransactionManager tm = this.TransactionManager;
3338 foreach (EntityEntry entry in entries)
3340 if (entry.State != EntityState.Detached &&
3341 !entry.IsKeyEntry) // Still need to check this here because entries may have been demoted
3343 foreach (RelatedEnd relatedEnd in entry.WrappedEntity.RelationshipManager.Relationships)
3345 // find EntityKey of objects added to relatedEnd by changes of FKs
3347 HashSet<EntityKey> entityKeysOfAddedObjects = null;
3349 Dictionary<RelatedEnd, HashSet<EntityKey>> addedRelationshipsByForeignKey;
3350 if (relatedEnd is EntityReference &&
3351 tm.AddedRelationshipsByForeignKey.TryGetValue(entry.WrappedEntity, out addedRelationshipsByForeignKey))
3353 addedRelationshipsByForeignKey.TryGetValue(relatedEnd, out entityKeysOfAddedObjects);
3357 // find IEntityWrappers of objects added to relatedEnd by changes to navigation property
3359 Dictionary<RelatedEnd, HashSet<IEntityWrapper>> addedRelationshipsByGraph;
3360 HashSet<IEntityWrapper> entitiesToAdd = null;
3361 if (tm.AddedRelationshipsByGraph.TryGetValue(entry.WrappedEntity, out addedRelationshipsByGraph))
3363 addedRelationshipsByGraph.TryGetValue(relatedEnd, out entitiesToAdd);
3367 // merge the 2 sets into one (destroys entitiesToAdd)
3369 // Perform Add of FK or FK + Reference changes
3370 if (entityKeysOfAddedObjects != null)
3372 EntityEntry relatedEntry;
3374 foreach (EntityKey entityKeyOfAddedObjects in entityKeysOfAddedObjects)
3376 // we are interested only in tracked non-Added entities
3377 if (this.TryGetEntityEntry(entityKeyOfAddedObjects, out relatedEntry) &&
3378 relatedEntry.WrappedEntity.Entity != null)
3380 entitiesToAdd = entitiesToAdd != null ? entitiesToAdd : new HashSet<IEntityWrapper>();
3381 // if the change comes only from the FK and the FK is to a deleted entity
3382 // then we do not do fixup to align to that entity so do not add those
3383 // implementation note: we do not need to check for contains because if it's there we don't need to add it
3384 if (relatedEntry.State != EntityState.Deleted)
3386 // Remove it from the list of entities to add by reference because it will be added now
3387 entitiesToAdd.Remove(relatedEntry.WrappedEntity);
3389 PerformAdd(entry.WrappedEntity, relatedEnd, relatedEntry.WrappedEntity, true);
3394 // Need to update the CFK and dangling FK references even if there is no related entity
3395 EntityReference reference = relatedEnd as EntityReference;
3396 Debug.Assert(reference != null);
3397 entry.FixupEntityReferenceByForeignKey(reference);
3403 // Perform Add for Reference changes
3404 if (entitiesToAdd != null)
3406 foreach (IEntityWrapper entityToAdd in entitiesToAdd)
3408 PerformAdd(entry.WrappedEntity, relatedEnd, entityToAdd, false);
3416 private void PerformAdd(IEntityWrapper wrappedOwner, RelatedEnd relatedEnd, IEntityWrapper entityToAdd, bool isForeignKeyChange)
3418 Debug.Assert(wrappedOwner == relatedEnd.WrappedOwner, "entry.WrappedEntity is not the same as relatedEnd.WrappedOwner?");
3420 relatedEnd.ValidateStateForAdd(relatedEnd.WrappedOwner);
3421 relatedEnd.ValidateStateForAdd(entityToAdd);
3423 // We need to determine if adding entityToAdd is going to cause reparenting
3424 // if relatedEnd is a principal then
3425 // Get the target relatedEnd on entityToAdd to check if we are in this situation
3426 // if relatedEnd is a dependent then
3428 if (relatedEnd.IsPrincipalEndOfReferentialConstraint())
3430 EntityReference targetReference = relatedEnd.GetOtherEndOfRelationship(entityToAdd) as EntityReference;
3431 if (targetReference != null && IsReparentingReference(entityToAdd, targetReference))
3433 TransactionManager.EntityBeingReparented = targetReference.GetDependentEndOfReferentialConstraint(targetReference.ReferenceValue.Entity);
3436 else if (relatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
3438 EntityReference reference = relatedEnd as EntityReference;
3439 if (reference != null && IsReparentingReference(wrappedOwner, reference))
3441 TransactionManager.EntityBeingReparented = reference.GetDependentEndOfReferentialConstraint(reference.ReferenceValue.Entity);
3446 relatedEnd.Add(entityToAdd,
3447 applyConstraints: false,
3448 addRelationshipAsUnchanged: false,
3449 relationshipAlreadyExists: false,
3450 allowModifyingOtherEndOfRelationship: true,
3451 forceForeignKeyChanges: !isForeignKeyChange);
3455 TransactionManager.EntityBeingReparented = null;
3459 private void PerformDelete(IList<EntityEntry> entries)
3461 TransactionManager tm = this.TransactionManager;
3463 foreach (EntityEntry entry in entries)
3465 if (entry.State != EntityState.Detached &&
3466 entry.State != EntityState.Deleted &&
3467 !entry.IsKeyEntry) // Still need to check this here because entries may have been demoted
3469 foreach (RelatedEnd relatedEnd in entry.WrappedEntity.RelationshipManager.Relationships)
3471 // find EntityKey of objects deleted from relatedEnd by changes of FKs
3473 HashSet<EntityKey> entityKeysOfDeletedObjects = null;
3475 Dictionary<RelatedEnd, HashSet<EntityKey>> deletedRelationshipsByForeignKey;
3476 if (relatedEnd is EntityReference &&
3477 tm.DeletedRelationshipsByForeignKey.TryGetValue(entry.WrappedEntity, out deletedRelationshipsByForeignKey))
3479 deletedRelationshipsByForeignKey.TryGetValue(relatedEnd as EntityReference, out entityKeysOfDeletedObjects);
3482 // find IEntityWrappers of objects deleted from relatedEnd by changes to navigation property
3484 Dictionary<RelatedEnd, HashSet<IEntityWrapper>> deletedRelationshipsByGraph;
3485 HashSet<IEntityWrapper> entitiesToDelete = null;
3486 if (tm.DeletedRelationshipsByGraph.TryGetValue(entry.WrappedEntity, out deletedRelationshipsByGraph))
3488 deletedRelationshipsByGraph.TryGetValue(relatedEnd, out entitiesToDelete);
3491 // Perform the deletes:
3492 // 1. FK only OR combined FK/Ref changes (same change to both FK and reference)
3493 if (entityKeysOfDeletedObjects != null)
3495 foreach (EntityKey key in entityKeysOfDeletedObjects)
3497 EntityEntry relatedEntry;
3498 IEntityWrapper relatedEntity = null;
3499 EntityReference reference = relatedEnd as EntityReference;
3500 if (this.TryGetEntityEntry(key, out relatedEntry) &&
3501 relatedEntry.WrappedEntity.Entity != null)
3503 relatedEntity = relatedEntry.WrappedEntity;
3507 // The relatedEntity may be added, and we only have a permanent key
3508 // so look at the permanent key of the reference to decide
3509 if (reference != null &&
3510 reference.ReferenceValue != NullEntityWrapper.NullWrapper &&
3511 reference.ReferenceValue.EntityKey.IsTemporary &&
3512 this.TryGetEntityEntry(reference.ReferenceValue.EntityKey, out relatedEntry) &&
3513 relatedEntry.WrappedEntity.Entity != null)
3515 EntityKey permanentRelatedKey = new EntityKey((EntitySet)relatedEntry.EntitySet, (IExtendedDataRecord)relatedEntry.CurrentValues);
3516 if (key == permanentRelatedKey)
3518 relatedEntity = relatedEntry.WrappedEntity;
3523 if (relatedEntity != null)
3525 entitiesToDelete = entitiesToDelete != null ? entitiesToDelete : new HashSet<IEntityWrapper>();
3526 // if the reference also changed, we will remove that now
3527 // if only the FK changed, it will not be in the list entitiesToDelete and
3528 // so we should preserve the FK value
3529 // if the reference is being set to null, (was a delete, but not an add)
3530 // then we need to preserve the FK values regardless
3531 bool preserveForeignKey = ShouldPreserveForeignKeyForDependent(entry.WrappedEntity, relatedEnd, relatedEntity, entitiesToDelete);
3532 // No need to also do a graph remove of the same value
3533 entitiesToDelete.Remove(relatedEntity);
3534 if (reference != null && IsReparentingReference(entry.WrappedEntity, reference))
3536 TransactionManager.EntityBeingReparented = reference.GetDependentEndOfReferentialConstraint(reference.ReferenceValue.Entity);
3540 relatedEnd.Remove(relatedEntity, preserveForeignKey);
3544 TransactionManager.EntityBeingReparented = null;
3546 // stop trying to remove something, if the owner was detached or deleted because of RIC/cascade delete
3547 if (entry.State == EntityState.Detached || entry.State == EntityState.Deleted || entry.IsKeyEntry)
3552 if (reference != null &&
3553 reference.IsForeignKey &&
3554 reference.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
3556 // Ensure that the cached FK value on the reference is in sync because it is possible that we
3557 // didn't take any actions above that would cause this to be set.
3558 reference.SetCachedForeignKey(ForeignKeyFactory.CreateKeyFromForeignKeyValues(entry, reference), entry);
3563 // 2. Changes to the reference only
3564 if (entitiesToDelete != null)
3566 foreach (IEntityWrapper entityToDelete in entitiesToDelete)
3568 bool preserveForeignKey = ShouldPreserveForeignKeyForPrincipal(entry.WrappedEntity, relatedEnd, entityToDelete, entitiesToDelete);
3569 EntityReference reference = relatedEnd as EntityReference;
3570 if (reference != null && IsReparentingReference(entry.WrappedEntity, reference))
3572 TransactionManager.EntityBeingReparented = reference.GetDependentEndOfReferentialConstraint(reference.ReferenceValue.Entity);
3576 relatedEnd.Remove(entityToDelete, preserveForeignKey);
3580 TransactionManager.EntityBeingReparented = null;
3583 // stop trying to remove something, if the owner was detached or deleted because of RIC/cascade delete
3584 if (entry.State == EntityState.Detached || entry.State == EntityState.Deleted || entry.IsKeyEntry)
3591 // skip the remaining relatedEnds if the owner was detached or deleted because of RIC/cascade delete
3592 if (entry.State == EntityState.Detached || entry.State == EntityState.Deleted || entry.IsKeyEntry)
3601 private bool ShouldPreserveForeignKeyForPrincipal(IEntityWrapper entity, RelatedEnd relatedEnd, IEntityWrapper relatedEntity,
3602 HashSet<IEntityWrapper> entitiesToDelete)
3604 bool preserveForeignKey = false;
3605 if (relatedEnd.IsForeignKey)
3607 RelatedEnd otherEnd = relatedEnd.GetOtherEndOfRelationship(relatedEntity);
3608 if (otherEnd.IsDependentEndOfReferentialConstraint(false))
3610 // Check the changes being applied to the dependent end
3611 HashSet<EntityKey> entityKeysOfDeletedObjects = null;
3612 Dictionary<RelatedEnd, HashSet<EntityKey>> deletedRelationshipsByForeignKey;
3613 Dictionary<RelatedEnd, HashSet<IEntityWrapper>> deletedRelationshipsByGraph;
3614 // There must be a foreign key and graph change on the dependent side to know if we need to preserve the FK
3615 if (TransactionManager.DeletedRelationshipsByForeignKey.TryGetValue(relatedEntity, out deletedRelationshipsByForeignKey) &&
3616 deletedRelationshipsByForeignKey.TryGetValue(otherEnd, out entityKeysOfDeletedObjects) &&
3617 entityKeysOfDeletedObjects.Count > 0 &&
3618 TransactionManager.DeletedRelationshipsByGraph.TryGetValue(relatedEntity, out deletedRelationshipsByGraph) &&
3619 deletedRelationshipsByGraph.TryGetValue(otherEnd, out entitiesToDelete))
3621 preserveForeignKey = ShouldPreserveForeignKeyForDependent(relatedEntity, otherEnd, entity, entitiesToDelete);
3625 return preserveForeignKey;
3628 private bool ShouldPreserveForeignKeyForDependent(IEntityWrapper entity, RelatedEnd relatedEnd, IEntityWrapper relatedEntity,
3629 HashSet<IEntityWrapper> entitiesToDelete)
3631 bool hasReferenceRemove = entitiesToDelete.Contains(relatedEntity);
3632 return (!hasReferenceRemove ||
3633 hasReferenceRemove && !HasAddedReference(entity, relatedEnd as EntityReference));
3636 private bool HasAddedReference(IEntityWrapper wrappedOwner, EntityReference reference)
3638 Dictionary<RelatedEnd, HashSet<IEntityWrapper>> addedRelationshipsByGraph;
3639 HashSet<IEntityWrapper> entitiesToAdd = null;
3640 if (reference != null &&
3641 TransactionManager.AddedRelationshipsByGraph.TryGetValue(wrappedOwner, out addedRelationshipsByGraph) &&
3642 addedRelationshipsByGraph.TryGetValue(reference, out entitiesToAdd) &&
3643 entitiesToAdd.Count > 0)
3650 private bool IsReparentingReference(IEntityWrapper wrappedEntity, EntityReference reference)
3652 TransactionManager tm = this.TransactionManager;
3653 if (reference.IsPrincipalEndOfReferentialConstraint())
3655 // need to find the dependent and make sure that it is being reparented
3656 wrappedEntity = reference.ReferenceValue;
3657 reference = wrappedEntity.Entity == null ?
3659 reference.GetOtherEndOfRelationship(wrappedEntity) as EntityReference;
3662 if (wrappedEntity.Entity != null && reference != null)
3664 HashSet<EntityKey> entityKeysOfAddedObjects = null;
3665 Dictionary<RelatedEnd, HashSet<EntityKey>> addedRelationshipsByForeignKey;
3666 if (tm.AddedRelationshipsByForeignKey.TryGetValue(wrappedEntity, out addedRelationshipsByForeignKey) &&
3667 addedRelationshipsByForeignKey.TryGetValue(reference, out entityKeysOfAddedObjects) &&
3668 entityKeysOfAddedObjects.Count > 0)
3673 Dictionary<RelatedEnd, HashSet<IEntityWrapper>> addedRelationshipsByGraph;
3674 HashSet<IEntityWrapper> entitiesToAdd = null;
3675 if (tm.AddedRelationshipsByGraph.TryGetValue(wrappedEntity, out addedRelationshipsByGraph) &&
3676 addedRelationshipsByGraph.TryGetValue(reference, out entitiesToAdd) &&
3677 entitiesToAdd.Count > 0)
3685 private void DetectChangesInNavigationProperties(IList<EntityEntry> entries)
3687 // Detect changes in navigation properties
3688 // (populates this.TransactionManager.DeletedRelationships and this.TransactionManager.AddedRelationships)
3689 foreach (var entry in entries)
3691 Debug.Assert(!entry.IsKeyEntry, "List should be filtered before it gets to this method.");
3692 if (entry.WrappedEntity.RequiresRelationshipChangeTracking)
3694 entry.DetectChangesInRelationshipsOfSingleEntity();
3699 private void DetectChangesInScalarAndComplexProperties(IList<EntityEntry> entries)
3701 foreach (var entry in entries)
3703 Debug.Assert(!entry.IsKeyEntry, "List should be filtered before it gets to this method.");
3705 if (entry.State != EntityState.Added)
3707 if (entry.RequiresScalarChangeTracking || entry.RequiresComplexChangeTracking)
3709 entry.DetectChangesInProperties(!entry.RequiresScalarChangeTracking);
3715 internal EntityKey CreateEntityKey(EntitySet entitySet, object entity)
3717 Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
3718 Debug.Assert(entitySet != null, "null entitySet");
3719 Debug.Assert(entity != null, "null entity");
3721 // Creates an EntityKey based on the values in the entity and the given EntitySet
3722 ReadOnlyMetadataCollection<EdmMember> keyMembers = entitySet.ElementType.KeyMembers;
3723 StateManagerTypeMetadata typeMetadata = this.GetOrAddStateManagerTypeMetadata(EntityUtil.GetEntityIdentityType(entity.GetType()), entitySet);
3724 object[] keyValues = new object[keyMembers.Count];
3726 for (int i = 0; i < keyMembers.Count; ++i)
3728 string keyName = keyMembers[i].Name;
3729 int ordinal = typeMetadata.GetOrdinalforCLayerMemberName(keyName);
3732 throw EntityUtil.EntityTypeDoesNotMatchEntitySet(entity.GetType().FullName, entitySet.Name, "entity");
3735 keyValues[i] = typeMetadata.Member(ordinal).GetValue(entity);
3736 if (keyValues[i] == null)
3738 throw EntityUtil.NullKeyValue(keyName, entitySet.ElementType.Name);
3742 if (keyValues.Length == 1)
3744 return new EntityKey(entitySet, keyValues[0]);
3748 return new EntityKey(entitySet, keyValues);
3753 /// Flag that is set when we are processing an FK setter for a full proxy.
3754 /// This is used to determine whether or not we will attempt to call out into FK
3755 /// setters and null references during fixup.
3756 /// The value of this property is either null if the code is not executing an
3757 /// FK setter, or points to the entity on which the FK setter has been called.
3759 internal object EntityInvokingFKSetter
3766 internal sealed class ObjectReferenceEqualityComparer : IEqualityComparer<object>
3768 bool IEqualityComparer<object>.Equals(object x, object y)
3770 return (Object.ReferenceEquals(x, y));
3773 int IEqualityComparer<object>.GetHashCode(object obj)
3775 return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);