/// <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; } }