public virtual IEnumerable <InternalEntityEntry> GetDependentsUsingRelationshipSnapshot(InternalEntityEntry principalEntry) { HashSet <InternalEntityEntry> dependents; return(_map.TryGetValue(_principalKeyValueFactory.CreateFromRelationshipSnapshot(principalEntry), out dependents) ? dependents : Enumerable.Empty <InternalEntityEntry>()); }
private void AddToCollection(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value, bool fromQuery) { if (navigation != null) { _changeDetector.Suspend(); try { if (entry.AddToCollection(navigation, value, fromQuery)) { entry.AddToCollectionSnapshot(navigation, value.Entity); } } finally { _changeDetector.Resume(); } } }
private void RemoveFromCollection(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value) { _changeDetector.Suspend(); try { if (entry.RemoveFromCollection(navigation, value)) { entry.RemoveFromCollectionSnapshot(navigation, value.Entity); } } finally { _changeDetector.Resume(); } }
private void ConditionallyNullForeignKeyProperties( InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey) { var principalProperties = foreignKey.PrincipalKey.Properties; var dependentProperties = foreignKey.Properties; var hasOnlyKeyProperties = true; var currentPrincipal = dependentEntry.StateManager.FindPrincipal(dependentEntry, foreignKey); if (currentPrincipal != null && currentPrincipal != principalEntry) { return; } 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 void SetNavigation(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value, bool fromQuery) { if (navigation != null) { _changeDetector.Suspend(); var entity = value?.Entity; try { entry.SetProperty(navigation, entity, fromQuery); } finally { _changeDetector.Resume(); } entry.SetRelationshipSnapshotValue(navigation, entity); } }
private void InitialFixup( InternalEntityEntry entry, bool fromQuery) { var entityType = entry.EntityType; var stateManager = entry.StateManager; foreach (var foreignKey in entityType.GetForeignKeys()) { var principalEntry = stateManager.FindPrincipal(entry, foreignKey); if (principalEntry != null) { var navigation = foreignKey.DependentToPrincipal; var existingPrincipal = navigation == null ? null : entry[navigation]; if (existingPrincipal == null || existingPrincipal == principalEntry.Entity) { // Set navigation to principal based on FK properties SetNavigation(entry, navigation, principalEntry, fromQuery); // Add this entity to principal's collection, or set inverse for 1:1 ToDependentFixup(entry, principalEntry, foreignKey, fromQuery); } } } foreach (var foreignKey in entityType.GetReferencingForeignKeys()) { if (foreignKey.DeclaringEntityType.FindPrimaryKey() != null) { var dependents = stateManager.GetDependents(entry, foreignKey); if (foreignKey.IsUnique) { var dependentEntry = (InternalEntityEntry)dependents.FirstOrDefault(); if (dependentEntry != null) { if ((!foreignKey.IsOwnership || (dependentEntry.EntityState != EntityState.Deleted && dependentEntry.EntityState != EntityState.Detached)) && (foreignKey.PrincipalToDependent == null || entry[foreignKey.PrincipalToDependent] == null || entry[foreignKey.PrincipalToDependent] == dependentEntry.Entity)) { // Set navigations to and from principal entity that is indicated by FK SetNavigation(entry, foreignKey.PrincipalToDependent, dependentEntry, fromQuery); SetNavigation(dependentEntry, foreignKey.DependentToPrincipal, entry, fromQuery); } } } else { foreach (InternalEntityEntry dependentEntry in dependents) { if ((!foreignKey.IsOwnership || (dependentEntry.EntityState != EntityState.Deleted && dependentEntry.EntityState != EntityState.Detached)) && (!fromQuery || foreignKey.DependentToPrincipal == null || dependentEntry.GetCurrentValue(foreignKey.DependentToPrincipal) == null)) { // Add to collection on principal indicated by FK and set inverse navigation AddToCollection(entry, foreignKey.PrincipalToDependent, dependentEntry, fromQuery); SetNavigation(dependentEntry, foreignKey.DependentToPrincipal, entry, fromQuery); } } } } } // 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) { var setModified = entry.EntityState != EntityState.Unchanged; foreach (var foreignKey in entityType.GetReferencingForeignKeys()) { if (foreignKey.DeclaringEntityType.FindPrimaryKey() != null) { var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { var navigationValue = entry[principalToDependent]; if (navigationValue != null) { if (principalToDependent.IsCollection) { var dependents = ((IEnumerable)navigationValue).Cast <object>().ToList(); foreach (var dependentEntity in dependents) { var dependentEntry = stateManager.TryGetEntry(dependentEntity, foreignKey.DeclaringEntityType); 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, fromQuery); } } } else { var targetEntityType = principalToDependent.TargetEntityType; var dependentEntry = stateManager.TryGetEntry(navigationValue, targetEntityType); 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, fromQuery); } } } } } } foreach (var foreignKey in entityType.GetForeignKeys()) { var dependentToPrincipal = foreignKey.DependentToPrincipal; if (dependentToPrincipal != null) { var navigationValue = entry[dependentToPrincipal]; if (navigationValue != null) { var targetEntityType = dependentToPrincipal.TargetEntityType; var principalEntry = stateManager.TryGetEntry(navigationValue, targetEntityType); 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, setModified, fromQuery); } } } } // 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.GetRecordedReferrers(entry.Entity, clear: true)) { DelayedFixup(danglerEntry.Item2, danglerEntry.Item1, entry, fromQuery); } } }
/// <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 = (InternalEntityEntry)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 StateChanging(InternalEntityEntry entry, EntityState newState) { }
/// <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, IEnumerable <IKey> containingPrincipalKeys, IEnumerable <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 = (InternalEntityEntry)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 (InternalEntityEntry 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 (InternalEntityEntry 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; } }
/// <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 NavigationCollectionChanged( InternalEntityEntry entry, INavigation navigation, IEnumerable <object> added, IEnumerable <object> removed) { if (_inFixup) { return; } var foreignKey = navigation.ForeignKey; var stateManager = entry.StateManager; var inverse = navigation.Inverse; var targetEntityType = navigation.TargetEntityType; foreach (var oldValue in removed) { var oldTargetEntry = stateManager.TryGetEntry(oldValue, targetEntityType); if (oldTargetEntry != null && oldTargetEntry.EntityState != EntityState.Detached) { try { _inFixup = true; // Null FKs and navigations of dependents that have been removed, unless they // have already been changed. ConditionallyNullForeignKeyProperties(oldTargetEntry, entry, foreignKey); if (inverse != null && ReferenceEquals(oldTargetEntry[inverse], entry.Entity) && (!foreignKey.IsOwnership || (oldTargetEntry.EntityState != EntityState.Deleted && oldTargetEntry.EntityState != EntityState.Detached))) { SetNavigation(oldTargetEntry, inverse, null, fromQuery: false); } entry.RemoveFromCollectionSnapshot(navigation, oldValue); } finally { _inFixup = false; } } } foreach (var newValue in added) { var newTargetEntry = stateManager.GetOrCreateEntry(newValue, targetEntityType); if (newTargetEntry.EntityState != EntityState.Detached) { try { _inFixup = true; // For a dependent added to the collection, remove it from the collection of // the principal entity that it was previously part of var oldPrincipalEntry = stateManager.FindPrincipalUsingRelationshipSnapshot(newTargetEntry, foreignKey); if (oldPrincipalEntry != null && oldPrincipalEntry != entry) { RemoveFromCollection(oldPrincipalEntry, navigation, newTargetEntry); } // Set the FK properties on added dependents to match this principal SetForeignKeyProperties(newTargetEntry, entry, foreignKey, setModified: true, fromQuery: false); // Set the inverse navigation to point to this principal SetNavigation(newTargetEntry, inverse, entry, fromQuery: false); } finally { _inFixup = false; } } else { stateManager.RecordReferencedUntrackedEntity(newValue, navigation, entry); _attacher.AttachGraph( newTargetEntry, EntityState.Added, EntityState.Modified, forceStateWhenUnknownKey: false); } entry.AddToCollectionSnapshot(navigation, newValue); } }