diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
index 6b2e45614fc..15515fd683e 100644
--- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
+++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
@@ -650,256 +650,168 @@ private void AddForeignKeyEdges()
{
if (command.EntityState is EntityState.Modified or EntityState.Added)
{
- if (command.Table != null)
- {
- foreach (var foreignKey in command.Table.ReferencingForeignKeyConstraints)
- {
- if (!IsModified(foreignKey.PrincipalUniqueConstraint.Columns, command))
- {
- continue;
- }
-
- var principalKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
- .CreatePrincipalEquatableKeyValue(command);
- Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
-
- if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands))
- {
- predecessorCommands = [];
- predecessorsMap.Add(principalKeyValue, predecessorCommands);
- }
-
- predecessorCommands.Add(command);
- }
- }
-
- for (var i = 0; i < command.Entries.Count; i++)
- {
- var entry = command.Entries[i];
- foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys())
- {
- if (!CanCreateDependency(foreignKey, command, principal: true)
- || !IsModified(foreignKey.PrincipalKey.Properties, entry)
- || (command.Table != null
- && !IsStoreGenerated(entry, foreignKey.PrincipalKey)
- && foreignKey.GetMappedConstraints().Any()))
- {
- continue;
- }
-
- var principalKeyValue = foreignKey.GetDependentKeyValueFactory()
- .CreatePrincipalEquatableKey(entry);
- Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
-
- if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands))
- {
- predecessorCommands = [];
- predecessorsMap.Add(principalKeyValue, predecessorCommands);
- }
-
- predecessorCommands.Add(command);
- }
- }
+ AddForeignKeyPredecessors(command, predecessorsMap, principal: true);
}
if (command.EntityState is EntityState.Modified or EntityState.Deleted)
{
- if (command.Table != null)
- {
- foreach (var foreignKey in command.Table!.ForeignKeyConstraints)
- {
- if (!IsModified(foreignKey.Columns, command))
- {
- continue;
- }
-
- var dependentKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
- .CreateDependentEquatableKeyValue(command, fromOriginalValues: true);
- if (dependentKeyValue != null)
- {
- if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
- {
- predecessorCommands = [];
- originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
- }
-
- predecessorCommands.Add(command);
- }
- }
+ AddForeignKeyPredecessors(command, originalPredecessorsMap, principal: false);
+ }
+ }
- // Also handle FKs with no mapped constraints (e.g. TPC with abstract principal mapped to no table)
- foreach (var entry in command.Entries)
- {
- foreach (var foreignKey in entry.EntityType.GetForeignKeys())
- {
- if (!CanCreateDependency(foreignKey, command, principal: false)
- || !IsModified(foreignKey.Properties, entry)
- || foreignKey.GetMappedConstraints().Any())
- {
- continue;
- }
+ foreach (var command in _modificationCommandGraph.Vertices)
+ {
+ if (command.EntityState is EntityState.Modified or EntityState.Added)
+ {
+ AddForeignKeyEdges(command, predecessorsMap, principal: false);
+ }
- var dependentKeyValue = foreignKey.GetDependentKeyValueFactory()
- ?.CreateDependentEquatableKey(entry, fromOriginalValues: true);
+ if (command.EntityState is EntityState.Modified or EntityState.Deleted)
+ {
+ AddForeignKeyEdges(command, originalPredecessorsMap, principal: true);
+ }
+ }
+ }
- if (dependentKeyValue != null)
- {
- if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
- {
- predecessorCommands = [];
- originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
- }
+ ///
+ /// 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.
+ ///
+ private static void AddForeignKeyPredecessors(
+ IReadOnlyModificationCommand command,
+ Dictionary