private static void ConditionallyNullForeignKeyProperties( InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey) { var principalProperties = foreignKey.PrincipalKey.Properties; var dependentProperties = foreignKey.Properties; if (principalEntry != null && principalEntry.EntityState != EntityState.Detached) { for (var i = 0; i < foreignKey.Properties.Count; i++) { if (!StructuralComparisons.StructuralEqualityComparer.Equals( principalEntry[principalProperties[i]], dependentEntry[dependentProperties[i]])) { return; } } } for (var i = 0; i < foreignKey.Properties.Count; i++) { dependentEntry[dependentProperties[i]] = null; dependentEntry.StateManager.UpdateDependentMap(dependentEntry, foreignKey); dependentEntry.SetRelationshipSnapshotValue(dependentProperties[i], null); } }
private static void SetForeignKeyProperties( InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, bool setModified) { var principalProperties = foreignKey.PrincipalKey.Properties; var dependentProperties = foreignKey.Properties; for (var i = 0; i < foreignKey.Properties.Count; i++) { var principalValue = principalEntry[principalProperties[i]]; var dependentProperty = dependentProperties[i]; if (!StructuralComparisons.StructuralEqualityComparer.Equals( dependentEntry[dependentProperty], principalValue) || (dependentEntry.IsConceptualNull(dependentProperty) && principalValue != null)) { dependentEntry.SetProperty(dependentProperty, principalValue, setModified); dependentEntry.StateManager.UpdateDependentMap(dependentEntry, foreignKey); dependentEntry.SetRelationshipSnapshotValue(dependentProperty, principalValue); } } }
private void Unfixup(INavigation navigation, InternalEntityEntry oldPrincipalEntry, InternalEntityEntry dependentEntry) { var dependentEntity = dependentEntry.Entity; if (navigation.IsDependentToPrincipal()) { SetNavigation(navigation.GetSetter(), dependentEntity, null); dependentEntry.SetRelationshipSnapshotValue(navigation, null); } else { if (navigation.IsCollection()) { var collectionAccessor = navigation.GetCollectionAccessor(); if (collectionAccessor.Contains(oldPrincipalEntry.Entity, dependentEntity)) { collectionAccessor.Remove(oldPrincipalEntry.Entity, dependentEntity); oldPrincipalEntry.RemoveFromCollectionSnapshot(navigation, dependentEntity); } } else { SetNavigation(navigation.GetSetter(), oldPrincipalEntry.Entity, null); oldPrincipalEntry.SetRelationshipSnapshotValue(navigation, null); } } }
private static void SetForeignKeyProperties( InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, bool setModified, bool fromQuery) { var principalProperties = foreignKey.PrincipalKey.Properties; var dependentProperties = foreignKey.Properties; for (var i = 0; i < foreignKey.Properties.Count; i++) { var principalProperty = principalProperties[i]; var dependentProperty = dependentProperties[i]; var principalValue = principalEntry[principalProperty]; var dependentValue = dependentEntry[dependentProperty]; if (!PrincipalValueEqualsDependentValue(principalProperty, dependentValue, principalValue) || (dependentEntry.IsConceptualNull(dependentProperty) && principalValue != null)) { if (principalEntry.HasTemporaryValue(principalProperty)) { dependentEntry.SetTemporaryValue(dependentProperty, principalValue, setModified); } else { dependentEntry.SetProperty(dependentProperty, principalValue, fromQuery, setModified); } dependentEntry.StateManager.UpdateDependentMap(dependentEntry, foreignKey); dependentEntry.SetRelationshipSnapshotValue(dependentProperty, principalValue); } } }
private static void SetNullForeignKey(InternalEntityEntry dependentEntry, IReadOnlyList <IProperty> dependentProperties) { foreach (var dependentProperty in dependentProperties) { dependentEntry[dependentProperty] = null; dependentEntry.SetRelationshipSnapshotValue(dependentProperty, null); } }
private void ConditionallyNullForeignKeyProperties( InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey) { var principalProperties = foreignKey.PrincipalKey.Properties; var dependentProperties = foreignKey.Properties; var hasOnlyKeyProperties = true; if (principalEntry != null && principalEntry.EntityState != EntityState.Detached) { for (var i = 0; i < foreignKey.Properties.Count; i++) { if (!PrincipalValueEqualsDependentValue( principalProperties[i], dependentEntry[dependentProperties[i]], principalEntry[principalProperties[i]])) { return; } if (!dependentProperties[i].IsKey()) { hasOnlyKeyProperties = false; } } } for (var i = 0; i < foreignKey.Properties.Count; i++) { if (!dependentProperties[i].IsKey()) { dependentEntry[dependentProperties[i]] = null; dependentEntry.StateManager.UpdateDependentMap(dependentEntry, foreignKey); dependentEntry.SetRelationshipSnapshotValue(dependentProperties[i], null); } } if (foreignKey.IsRequired && hasOnlyKeyProperties && dependentEntry.EntityState != EntityState.Detached) { switch (dependentEntry.EntityState) { case EntityState.Added: dependentEntry.SetEntityState(EntityState.Detached); DeleteFixup(dependentEntry); break; case EntityState.Unchanged: case EntityState.Modified: dependentEntry.SetEntityState(EntityState.Deleted); DeleteFixup(dependentEntry); break; } } }
private static void SetForeignKeyValue(IForeignKey foreignKey, InternalEntityEntry dependentEntry, IReadOnlyList <object> principalValues) { for (var i = 0; i < foreignKey.Properties.Count; i++) { var principalValue = principalValues[i]; if ((foreignKey.Properties[i].GetGenerationProperty() == null) || !foreignKey.PrincipalKey.Properties[i].ClrType.IsDefaultValue(principalValue)) { var dependentProperty = foreignKey.Properties[i]; dependentEntry[dependentProperty] = principalValue; dependentEntry.SetRelationshipSnapshotValue(dependentProperty, principalValue); } } }
private void StealReference(IForeignKey foreignKey, InternalEntityEntry dependentEntry) { var navigation = foreignKey.DependentToPrincipal; if (navigation != null) { SetNavigation(navigation.GetSetter(), dependentEntry.Entity, null); dependentEntry.SetRelationshipSnapshotValue(navigation, null); } foreach (var property in foreignKey.Properties.Where(p => p.IsNullable).ToList()) { dependentEntry[property] = null; } }
private void SetNavigation(InternalEntityEntry entry, INavigation navigation, object value) { if (navigation != null) { _changeDetector.Suspend(); try { entry[navigation] = value; } finally { _changeDetector.Resume(); } entry.SetRelationshipSnapshotValue(navigation, value); } }
private static void SetForeignKeyProperties( InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, bool setModified) { var principalProperties = foreignKey.PrincipalKey.Properties; var dependentProperties = foreignKey.Properties; for (var i = 0; i < foreignKey.Properties.Count; i++) { var principalValue = principalEntry[principalProperties[i]]; dependentEntry.SetProperty(dependentProperties[i], principalValue, setModified); dependentEntry.StateManager.UpdateDependentMap(dependentEntry, foreignKey); dependentEntry.SetRelationshipSnapshotValue(dependentProperties[i], principalValue); } }
private void SetNavigation(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value) { if (navigation != null) { _changeDetector.Suspend(); var entity = value?.Entity; try { entry[navigation] = entity; } finally { _changeDetector.Resume(); } entry.SetRelationshipSnapshotValue(navigation, entity); } }
private void DoFixup(IEnumerable <INavigation> navigations, InternalEntityEntry principalEntry, InternalEntityEntry[] dependentEntries) { foreach (var navigation in navigations) { if (navigation.IsDependentToPrincipal()) { var setter = navigation.GetSetter(); foreach (var dependent in dependentEntries) { SetNavigation(setter, dependent.Entity, principalEntry.Entity); dependent.SetRelationshipSnapshotValue(navigation, principalEntry.Entity); } } else { if (navigation.IsCollection()) { var collectionAccessor = navigation.GetCollectionAccessor(); foreach (var dependent in dependentEntries) { var dependentEntity = dependent.Entity; if (!collectionAccessor.Contains(principalEntry.Entity, dependentEntity)) { collectionAccessor.Add(principalEntry.Entity, dependentEntity); principalEntry.AddToCollectionSnapshot(navigation, dependentEntity); } } } else { // TODO: Decide how to handle case where multiple values match non-collection nav prop // Issue #739 var value = dependentEntries.Single().Entity; SetNavigation(navigation.GetSetter(), principalEntry.Entity, value); principalEntry.SetRelationshipSnapshotValue(navigation, value); } } } }
private static void DetectKeyChange(InternalEntityEntry entry, IProperty property) { var keys = property.FindContainingKeys().ToList(); var foreignKeys = property.FindContainingForeignKeys().ToList(); if ((keys.Count > 0) || (foreignKeys.Count > 0)) { var snapshotValue = entry.GetRelationshipSnapshotValue(property); var currentValue = entry[property]; // Note that mutation of a byte[] key is not supported or detected, but two different instances // of byte[] with the same content must be detected as equal. if (!StructuralComparisons.StructuralEqualityComparer.Equals(currentValue, snapshotValue)) { var stateManager = entry.StateManager; if (foreignKeys.Count > 0) { stateManager.Notify.ForeignKeyPropertyChanged(entry, property, snapshotValue, currentValue); foreach (var foreignKey in foreignKeys) { stateManager.UpdateDependentMap(entry, foreignKey); } } if (keys.Count > 0) { foreach (var key in keys) { stateManager.UpdateIdentityMap(entry, key); } stateManager.Notify.PrincipalKeyPropertyChanged(entry, property, snapshotValue, currentValue); } entry.SetRelationshipSnapshotValue(property, currentValue); } } }
/// <summary> /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, object oldValue, object newValue) { if (_inFixup) { return; } var foreignKey = navigation.ForeignKey; var stateManager = entry.StateManager; var inverse = navigation.FindInverse(); var oldTargetEntry = oldValue == null ? null : stateManager.TryGetEntry(oldValue); if (oldTargetEntry?.EntityState == EntityState.Detached) { oldTargetEntry = null; } var newTargetEntry = newValue == null ? null : stateManager.TryGetEntry(newValue); if (newTargetEntry?.EntityState == EntityState.Detached) { newTargetEntry = null; } try { _inFixup = true; if (navigation.IsDependentToPrincipal()) { if (newValue != null) { if (newTargetEntry != null) { if (foreignKey.IsUnique) { // Navigation points to principal. Find the dependent that previously pointed to that principal and // null out its FKs and navigation property. A.k.a. reference stealing. // However, if the FK has already been changed or the reference is already set to point // to something else, then don't change it. var victimDependentEntry = stateManager.GetDependents(newTargetEntry, foreignKey).FirstOrDefault(); if (victimDependentEntry != null && victimDependentEntry != entry) { ConditionallyNullForeignKeyProperties(victimDependentEntry, newTargetEntry, foreignKey); if (ReferenceEquals(victimDependentEntry[navigation], newTargetEntry.Entity)) { SetNavigation(victimDependentEntry, navigation, null); } } } // Set the FK properties to reflect the change to the navigation. SetForeignKeyProperties(entry, newTargetEntry, foreignKey, setModified: true); } } else { // Null the FK properties to reflect that the navigation has been nulled out. ConditionallyNullForeignKeyProperties(entry, oldTargetEntry, foreignKey); } if (inverse != null) { var collectionAccessor = inverse.IsCollection() ? inverse.GetCollectionAccessor() : null; // Set the inverse reference or add the entity to the inverse collection if (newTargetEntry != null) { SetReferenceOrAddToCollection(newTargetEntry, inverse, collectionAccessor, entry.Entity); } // Remove the entity from the old collection, or null the old inverse unless it was already // changed to point to something else if (oldTargetEntry != null) { if (collectionAccessor != null) { RemoveFromCollection(oldTargetEntry, inverse, collectionAccessor, entry.Entity); } else if (ReferenceEquals(oldTargetEntry[inverse], entry.Entity)) { SetNavigation(oldTargetEntry, inverse, null); } } } } else { Debug.Assert(foreignKey.IsUnique); if (newTargetEntry != null) { // Navigation points to dependent and is 1:1. Find the principal that previously pointed to that // dependent and null out its navigation property. A.k.a. reference stealing. // However, if the reference is already set to point to something else, then don't change it. var victimPrincipalEntry = stateManager.GetPrincipal(newTargetEntry, foreignKey); if (victimPrincipalEntry != null && victimPrincipalEntry != entry && ReferenceEquals(victimPrincipalEntry[navigation], newTargetEntry.Entity)) { SetNavigation(victimPrincipalEntry, navigation, null); } SetForeignKeyProperties(newTargetEntry, entry, foreignKey, setModified: true); SetNavigation(newTargetEntry, inverse, entry.Entity); } if (oldTargetEntry != null) { // Null the FK properties on the old dependent, unless they have already been changed ConditionallyNullForeignKeyProperties(oldTargetEntry, entry, foreignKey); // Clear the inverse reference, unless it has already been changed if (inverse != null && ReferenceEquals(oldTargetEntry[inverse], entry.Entity)) { SetNavigation(oldTargetEntry, inverse, null); } } } } finally { _inFixup = false; } if (newValue != null && newTargetEntry == null) { stateManager.RecordReferencedUntrackedEntity(newValue, navigation, entry); entry.SetRelationshipSnapshotValue(navigation, newValue); _attacher.AttachGraph(stateManager.GetOrCreateEntry(newValue), EntityState.Added); } }
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, object oldValue, object newValue) { if (_inFixup) { return; } var foreignKey = navigation.ForeignKey; var stateManager = entry.StateManager; var inverse = navigation.Inverse; var targetEntityType = navigation.TargetEntityType; var oldTargetEntry = oldValue == null ? null : stateManager.TryGetEntry(oldValue, targetEntityType); if (oldTargetEntry?.EntityState == EntityState.Detached) { oldTargetEntry = null; } var newTargetEntry = newValue == null ? null : stateManager.TryGetEntry(newValue, targetEntityType); if (newTargetEntry?.EntityState == EntityState.Detached) { newTargetEntry = null; } try { _inFixup = true; if (navigation.IsOnDependent) { if (newValue != null) { if (newTargetEntry != null) { if (foreignKey.IsUnique) { // Navigation points to principal. Find the dependent that previously pointed to that principal and // null out its FKs and navigation property. A.k.a. reference stealing. // However, if the FK has already been changed or the reference is already set to point // to something else, then don't change it. var victimDependentEntry = stateManager.GetDependents(newTargetEntry, foreignKey).FirstOrDefault(); if (victimDependentEntry != null && victimDependentEntry != entry) { ConditionallyNullForeignKeyProperties(victimDependentEntry, newTargetEntry, foreignKey); if (ReferenceEquals(victimDependentEntry[navigation], newTargetEntry.Entity) && victimDependentEntry.StateManager .TryGetEntry(victimDependentEntry.Entity, navigation.DeclaringEntityType) != null) { SetNavigation(victimDependentEntry, navigation, null, fromQuery: false); } } } // Set the FK properties to reflect the change to the navigation. SetForeignKeyProperties(entry, newTargetEntry, foreignKey, setModified: true, fromQuery: false); } } else { // Null the FK properties to reflect that the navigation has been nulled out. ConditionallyNullForeignKeyProperties(entry, oldTargetEntry, foreignKey); entry.SetRelationshipSnapshotValue(navigation, null); } if (inverse != null) { // Set the inverse reference or add the entity to the inverse collection if (newTargetEntry != null) { SetReferenceOrAddToCollection(newTargetEntry, inverse, entry, fromQuery: false); } // Remove the entity from the old collection, or null the old inverse unless it was already // changed to point to something else if (oldTargetEntry != null && oldTargetEntry.EntityState != EntityState.Deleted) { ResetReferenceOrRemoveCollection(oldTargetEntry, inverse, entry, fromQuery: false); } } } else { Check.DebugAssert(foreignKey.IsUnique, $"foreignKey {foreignKey} is not unique"); if (oldTargetEntry != null) { // Null the FK properties on the old dependent, unless they have already been changed ConditionallyNullForeignKeyProperties(oldTargetEntry, entry, foreignKey); // Clear the inverse reference, unless it has already been changed if (inverse != null && ReferenceEquals(oldTargetEntry[inverse], entry.Entity) && (!oldTargetEntry.EntityType.HasDefiningNavigation() || entry.EntityType.GetNavigations().All( n => n == navigation || !ReferenceEquals(oldTargetEntry.Entity, entry[n])))) { SetNavigation(oldTargetEntry, inverse, null, fromQuery: false); } } if (newTargetEntry != null) { // Navigation points to dependent and is 1:1. Find the principal that previously pointed to that // dependent and null out its navigation property. A.k.a. reference stealing. // However, if the reference is already set to point to something else, then don't change it. var victimPrincipalEntry = stateManager.FindPrincipal(newTargetEntry, foreignKey); if (victimPrincipalEntry != null && victimPrincipalEntry != entry && ReferenceEquals(victimPrincipalEntry[navigation], newTargetEntry.Entity)) { SetNavigation(victimPrincipalEntry, navigation, null, fromQuery: false); } SetForeignKeyProperties(newTargetEntry, entry, foreignKey, setModified: true, fromQuery: false); SetNavigation(newTargetEntry, inverse, entry, fromQuery: false); } } if (newValue == null) { entry.SetIsLoaded(navigation, loaded: false); } } finally { _inFixup = false; } if (newValue != null && newTargetEntry == null) { stateManager.RecordReferencedUntrackedEntity(newValue, navigation, entry); entry.SetRelationshipSnapshotValue(navigation, newValue); newTargetEntry = stateManager.GetOrCreateEntry(newValue, targetEntityType); _attacher.AttachGraph( newTargetEntry, EntityState.Added, EntityState.Modified, forceStateWhenUnknownKey: false); } }
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> public virtual void KeyPropertyChanged( InternalEntityEntry entry, IProperty property, IReadOnlyList <IKey> containingPrincipalKeys, IReadOnlyList <IForeignKey> containingForeignKeys, object oldValue, object newValue) { if (entry.EntityState == EntityState.Detached) { return; } try { _inFixup = true; var stateManager = entry.StateManager; foreach (var foreignKey in containingForeignKeys) { var newPrincipalEntry = stateManager.FindPrincipal(entry, foreignKey) ?? stateManager.FindPrincipalUsingPreStoreGeneratedValues(entry, foreignKey); var oldPrincipalEntry = stateManager.FindPrincipalUsingRelationshipSnapshot(entry, foreignKey); var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { if (oldPrincipalEntry != null && oldPrincipalEntry.EntityState != EntityState.Deleted) { // Remove this entity from the principal collection that it was previously part of, // or null the navigation for a 1:1 unless that reference was already changed. ResetReferenceOrRemoveCollection(oldPrincipalEntry, principalToDependent, entry, fromQuery: false); } if (newPrincipalEntry != null && !entry.IsConceptualNull(property)) { // Add this entity to the collection of the new principal, or set the navigation for a 1:1 SetReferenceOrAddToCollection(newPrincipalEntry, principalToDependent, entry, fromQuery: false); } } var dependentToPrincipal = foreignKey.DependentToPrincipal; if (dependentToPrincipal != null) { if (newPrincipalEntry != null) { if (foreignKey.IsUnique) { // Dependent has been changed to point to a new principal. // Find the dependent that previously pointed to the new principal and null out its FKs // and navigation property. A.k.a. reference stealing. // However, if the FK has already been changed or the reference is already set to point // to something else, then don't change it. var targetDependentEntry = stateManager.GetDependentsUsingRelationshipSnapshot(newPrincipalEntry, foreignKey).FirstOrDefault(); if (targetDependentEntry != null && targetDependentEntry != entry) { ConditionallyNullForeignKeyProperties(targetDependentEntry, newPrincipalEntry, foreignKey); if (ReferenceEquals(targetDependentEntry[dependentToPrincipal], newPrincipalEntry.Entity) && targetDependentEntry.StateManager.TryGetEntry( targetDependentEntry.Entity, foreignKey.DeclaringEntityType) != null) { SetNavigation(targetDependentEntry, dependentToPrincipal, null, fromQuery: false); } } } if (!entry.IsConceptualNull(property)) { SetNavigation(entry, dependentToPrincipal, newPrincipalEntry, fromQuery: false); } } else if (oldPrincipalEntry != null) { if (ReferenceEquals(entry[dependentToPrincipal], oldPrincipalEntry.Entity) && entry.StateManager.TryGetEntry(entry.Entity, foreignKey.DeclaringEntityType) != null) { SetNavigation(entry, dependentToPrincipal, null, fromQuery: false); } } else { if (entry[dependentToPrincipal] == null && entry.StateManager.TryGetEntry(entry.Entity, foreignKey.DeclaringEntityType) != null) { // FK has changed but navigation is still null entry.SetIsLoaded(dependentToPrincipal, false); } } } stateManager.UpdateDependentMap(entry, foreignKey); } foreach (var key in containingPrincipalKeys) { stateManager.UpdateIdentityMap(entry, key); // Propagate principal key values into FKs foreach (var foreignKey in key.GetReferencingForeignKeys()) { foreach (var dependentEntry in stateManager .GetDependentsUsingRelationshipSnapshot(entry, foreignKey).ToList()) { SetForeignKeyProperties(dependentEntry, entry, foreignKey, setModified: true, fromQuery: false); } if (foreignKey.IsOwnership) { continue; } // Fix up dependents that have been added by propagating through different foreign key foreach (var dependentEntry in stateManager.GetDependents(entry, foreignKey).ToList()) { var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { if (!entry.IsConceptualNull(property)) { // Add this entity to the collection of the new principal, or set the navigation for a 1:1 SetReferenceOrAddToCollection(entry, principalToDependent, dependentEntry, fromQuery: false); } } var dependentToPrincipal = foreignKey.DependentToPrincipal; if (dependentToPrincipal != null) { if (!entry.IsConceptualNull(property)) { SetNavigation(dependentEntry, dependentToPrincipal, entry, fromQuery: false); } } } } } entry.SetRelationshipSnapshotValue(property, newValue); } finally { _inFixup = false; } }
private void InitialFixup(InternalEntityEntry entry, bool fromQuery) { var entityType = entry.EntityType; var stateManager = entry.StateManager; foreach (var foreignKey in entityType.GetForeignKeys()) { var principalEntry = stateManager.GetPrincipal(entry, foreignKey); if (principalEntry != null) { // Set navigation to principal based on FK properties SetNavigation(entry, foreignKey.DependentToPrincipal, principalEntry.Entity); // Add this entity to principal's collection, or set inverse for 1:1 var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { SetReferenceOrAddToCollection( principalEntry, principalToDependent, principalToDependent.IsCollection() ? principalToDependent.GetCollectionAccessor() : null, entry.Entity); } } } foreach (var foreignKey in entityType.GetReferencingForeignKeys()) { var dependents = stateManager.GetDependents(entry, foreignKey).ToList(); if (dependents.Any()) { var dependentToPrincipal = foreignKey.DependentToPrincipal; var principalToDependent = foreignKey.PrincipalToDependent; if (foreignKey.IsUnique) { var dependentEntry = dependents.First(); // Set navigations to and from principal entity that is indicated by FK SetNavigation(entry, principalToDependent, dependentEntry.Entity); SetNavigation(dependentEntry, dependentToPrincipal, entry.Entity); } else { var collectionAccessor = principalToDependent?.GetCollectionAccessor(); foreach (var dependentEntry in dependents) { var dependentEntity = dependentEntry.Entity; // Add to collection on principal indicated by FK and set inverse navigation AddToCollection(entry, principalToDependent, collectionAccessor, dependentEntity); SetNavigation(dependentEntry, dependentToPrincipal, entry.Entity); } } } } // If the new state is from a query then we are going to assume that the FK value is the source of // truth and not attempt to ascertain relationships from navigation properties if (!fromQuery) { foreach (var foreignKey in entityType.GetReferencingForeignKeys()) { var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { var navigationValue = entry[principalToDependent]; if (navigationValue != null) { var setModified = entry.EntityState != EntityState.Unchanged; if (principalToDependent.IsCollection()) { var dependents = ((IEnumerable)navigationValue).Cast <object>(); foreach (var dependentEntity in dependents) { var dependentEntry = stateManager.TryGetEntry(dependentEntity); if (dependentEntry == null || dependentEntry.EntityState == EntityState.Detached) { // If dependents in collection are not yet tracked, then save them away so that // when we start tracking them we can come back and fixup this principal to them stateManager.RecordReferencedUntrackedEntity(dependentEntity, principalToDependent, entry); } else { FixupToDependent(entry, dependentEntry, foreignKey, setModified); } } } else { var dependentEntry = stateManager.TryGetEntry(navigationValue); if (dependentEntry == null || dependentEntry.EntityState == EntityState.Detached) { // If dependent is not yet tracked, then save it away so that // when we start tracking it we can come back and fixup this principal to it stateManager.RecordReferencedUntrackedEntity(navigationValue, principalToDependent, entry); } else { FixupToDependent(entry, dependentEntry, foreignKey, setModified); } } } } } foreach (var foreignKey in entityType.GetForeignKeys()) { var dependentToPrincipal = foreignKey.DependentToPrincipal; if (dependentToPrincipal != null) { var navigationValue = entry[dependentToPrincipal]; if (navigationValue != null) { var principalEntry = stateManager.TryGetEntry(navigationValue); if (principalEntry == null || principalEntry.EntityState == EntityState.Detached) { // If principal is not yet tracked, then save it away so that // when we start tracking it we can come back and fixup this dependent to it stateManager.RecordReferencedUntrackedEntity(navigationValue, dependentToPrincipal, entry); } else { FixupToPrincipal(entry, principalEntry, foreignKey, entry.EntityState != EntityState.Unchanged); } } } } // If the entity was previously referenced while it was still untracked, go back and do the fixup // that we would have done then now that the entity is tracked. foreach (var danglerEntry in stateManager.GetRecordedReferers(entry.Entity)) { DelayedFixup(danglerEntry.Item2, danglerEntry.Item1, entry); } // Esnure current value is snapshotted for all keys and FKs foreach (var property in entityType.GetProperties().Where(p => p.GetRelationshipIndex() >= 0)) { entry.SetRelationshipSnapshotValue(property, entry[property]); } } }
/// <summary> /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public virtual void KeyPropertyChanged( InternalEntityEntry entry, IProperty property, IReadOnlyList <IKey> containingPrincipalKeys, IReadOnlyList <IForeignKey> containingForeignKeys, object oldValue, object newValue) { if (entry.EntityState == EntityState.Detached) { return; } try { _inFixup = true; var stateManager = entry.StateManager; foreach (var foreignKey in containingForeignKeys) { var newPrincipalEntry = stateManager.GetPrincipal(entry, foreignKey); var oldPrincipalEntry = stateManager.GetPrincipalUsingRelationshipSnapshot(entry, foreignKey); var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { var collectionAccessor = principalToDependent.IsCollection() ? principalToDependent.GetCollectionAccessor() : null; if (oldPrincipalEntry != null) { // Remove this entity from the principal collection that it was previously part of, // or null the navigation for a 1:1 unless that reference was already changed. if (collectionAccessor != null) { RemoveFromCollection(oldPrincipalEntry, principalToDependent, collectionAccessor, entry.Entity); } else if (ReferenceEquals(oldPrincipalEntry[principalToDependent], entry.Entity)) { SetNavigation(oldPrincipalEntry, principalToDependent, null); } } if (newPrincipalEntry != null) { // Add this entity to the collection of the new principal, or set the navigation for a 1:1 SetReferenceOrAddToCollection(newPrincipalEntry, principalToDependent, collectionAccessor, entry.Entity); } } var dependentToPrincipal = foreignKey.DependentToPrincipal; if (dependentToPrincipal != null) { if (newPrincipalEntry != null) { if (foreignKey.IsUnique) { // Dependent has been changed to point to a new principal. // Find the dependent that previously pointed to the new principal and null out its FKs // and navigation property. A.k.a. reference stealing. // However, if the FK has already been changed or the reference is already set to point // to something else, then don't change it. var targetDependentEntry = stateManager.GetDependentsUsingRelationshipSnapshot(newPrincipalEntry, foreignKey).FirstOrDefault(); if (targetDependentEntry != null && targetDependentEntry != entry) { ConditionallyNullForeignKeyProperties(targetDependentEntry, newPrincipalEntry, foreignKey); if (ReferenceEquals(targetDependentEntry[dependentToPrincipal], newPrincipalEntry.Entity)) { SetNavigation(targetDependentEntry, dependentToPrincipal, null); } } } SetNavigation(entry, dependentToPrincipal, newPrincipalEntry.Entity); } else if (oldPrincipalEntry != null && ReferenceEquals(entry[dependentToPrincipal], oldPrincipalEntry.Entity)) { SetNavigation(entry, dependentToPrincipal, null); } } stateManager.UpdateDependentMap(entry, foreignKey); } foreach (var key in containingPrincipalKeys) { stateManager.UpdateIdentityMap(entry, key); // Propagate principal key values into FKs foreach (var foreignKey in key.GetReferencingForeignKeys()) { foreach (var dependentEntry in stateManager.GetDependentsUsingRelationshipSnapshot(entry, foreignKey).ToList()) { SetForeignKeyProperties(dependentEntry, entry, foreignKey, setModified: true); } } } entry.SetRelationshipSnapshotValue(property, newValue); } finally { _inFixup = false; } }
private void DetectNavigationChange(InternalEntityEntry entry, INavigation navigation) { var snapshotValue = entry.GetRelationshipSnapshotValue(navigation); var currentValue = entry[navigation]; var stateManager = entry.StateManager; var added = new HashSet <object>(ReferenceEqualityComparer.Instance); if (navigation.IsCollection()) { var snapshotCollection = (IEnumerable)snapshotValue; var currentCollection = (IEnumerable)currentValue; var removed = new HashSet <object>(ReferenceEqualityComparer.Instance); if (snapshotCollection != null) { foreach (var entity in snapshotCollection) { removed.Add(entity); } } if (currentCollection != null) { foreach (var entity in currentCollection) { if (!removed.Remove(entity)) { added.Add(entity); } } } if (added.Any() || removed.Any()) { stateManager.Notify.NavigationCollectionChanged(entry, navigation, added, removed); foreach (var addedEntity in added) { entry.AddToCollectionSnapshot(navigation, addedEntity); } foreach (var removedEntity in removed) { entry.RemoveFromCollectionSnapshot(navigation, removedEntity); } } } else if (!ReferenceEquals(currentValue, snapshotValue)) { stateManager.Notify.NavigationReferenceChanged(entry, navigation, snapshotValue, currentValue); if (currentValue != null) { added.Add(currentValue); } entry.SetRelationshipSnapshotValue(navigation, currentValue); } foreach (var addedEntity in added) { var addedEntry = stateManager.GetOrCreateEntry(addedEntity); if (addedEntry.EntityState == EntityState.Detached) { _attacher.AttachGraph(addedEntry, EntityState.Added); } } }