Skip to content

Commit 0a06b01

Browse files
authored
Refactor CommandBatchPreparer (#38133)
1 parent fe8e85f commit 0a06b01

1 file changed

Lines changed: 134 additions & 222 deletions

File tree

src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs

Lines changed: 134 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -650,256 +650,168 @@ private void AddForeignKeyEdges()
650650
{
651651
if (command.EntityState is EntityState.Modified or EntityState.Added)
652652
{
653-
if (command.Table != null)
654-
{
655-
foreach (var foreignKey in command.Table.ReferencingForeignKeyConstraints)
656-
{
657-
if (!IsModified(foreignKey.PrincipalUniqueConstraint.Columns, command))
658-
{
659-
continue;
660-
}
661-
662-
var principalKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
663-
.CreatePrincipalEquatableKeyValue(command);
664-
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
665-
666-
if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands))
667-
{
668-
predecessorCommands = [];
669-
predecessorsMap.Add(principalKeyValue, predecessorCommands);
670-
}
671-
672-
predecessorCommands.Add(command);
673-
}
674-
}
675-
676-
for (var i = 0; i < command.Entries.Count; i++)
677-
{
678-
var entry = command.Entries[i];
679-
foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys())
680-
{
681-
if (!CanCreateDependency(foreignKey, command, principal: true)
682-
|| !IsModified(foreignKey.PrincipalKey.Properties, entry)
683-
|| (command.Table != null
684-
&& !IsStoreGenerated(entry, foreignKey.PrincipalKey)
685-
&& foreignKey.GetMappedConstraints().Any()))
686-
{
687-
continue;
688-
}
689-
690-
var principalKeyValue = foreignKey.GetDependentKeyValueFactory()
691-
.CreatePrincipalEquatableKey(entry);
692-
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
693-
694-
if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands))
695-
{
696-
predecessorCommands = [];
697-
predecessorsMap.Add(principalKeyValue, predecessorCommands);
698-
}
699-
700-
predecessorCommands.Add(command);
701-
}
702-
}
653+
AddForeignKeyPredecessors(command, predecessorsMap, principal: true);
703654
}
704655

705656
if (command.EntityState is EntityState.Modified or EntityState.Deleted)
706657
{
707-
if (command.Table != null)
708-
{
709-
foreach (var foreignKey in command.Table!.ForeignKeyConstraints)
710-
{
711-
if (!IsModified(foreignKey.Columns, command))
712-
{
713-
continue;
714-
}
715-
716-
var dependentKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
717-
.CreateDependentEquatableKeyValue(command, fromOriginalValues: true);
718-
if (dependentKeyValue != null)
719-
{
720-
if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
721-
{
722-
predecessorCommands = [];
723-
originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
724-
}
725-
726-
predecessorCommands.Add(command);
727-
}
728-
}
658+
AddForeignKeyPredecessors(command, originalPredecessorsMap, principal: false);
659+
}
660+
}
729661

730-
// Also handle FKs with no mapped constraints (e.g. TPC with abstract principal mapped to no table)
731-
foreach (var entry in command.Entries)
732-
{
733-
foreach (var foreignKey in entry.EntityType.GetForeignKeys())
734-
{
735-
if (!CanCreateDependency(foreignKey, command, principal: false)
736-
|| !IsModified(foreignKey.Properties, entry)
737-
|| foreignKey.GetMappedConstraints().Any())
738-
{
739-
continue;
740-
}
662+
foreach (var command in _modificationCommandGraph.Vertices)
663+
{
664+
if (command.EntityState is EntityState.Modified or EntityState.Added)
665+
{
666+
AddForeignKeyEdges(command, predecessorsMap, principal: false);
667+
}
741668

742-
var dependentKeyValue = foreignKey.GetDependentKeyValueFactory()
743-
?.CreateDependentEquatableKey(entry, fromOriginalValues: true);
669+
if (command.EntityState is EntityState.Modified or EntityState.Deleted)
670+
{
671+
AddForeignKeyEdges(command, originalPredecessorsMap, principal: true);
672+
}
673+
}
674+
}
744675

745-
if (dependentKeyValue != null)
746-
{
747-
if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
748-
{
749-
predecessorCommands = [];
750-
originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
751-
}
676+
/// <summary>
677+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
678+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
679+
/// any release. You should only use it directly in your code with extreme caution and knowing that
680+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
681+
/// </summary>
682+
private static void AddForeignKeyPredecessors(
683+
IReadOnlyModificationCommand command,
684+
Dictionary<object, List<IReadOnlyModificationCommand>> predecessorsMap,
685+
bool principal)
686+
{
687+
// This call doesn't need the IsStoreGenerated check because the constraint-level factory
688+
// (IRowForeignKeyValueFactory) works from column modifications and always produces a usable key value,
689+
// even for store-generated keys. The model-level factory below works from IUpdateEntry, which may hold
690+
// temporary sentinel values for store-generated keys, so it needs to register those separately to ensure
691+
// the dependent side can find the principal via either factory.
692+
foreach (var (_, keyValue) in GetForeignKeyConstraintValues(command, principal, fromOriginalValues: !principal))
693+
{
694+
AddPredecessor(predecessorsMap, keyValue, command);
695+
}
752696

753-
predecessorCommands.Add(command);
754-
}
755-
}
756-
}
757-
}
758-
else
759-
{
760-
foreach (var entry in command.Entries)
761-
{
762-
foreach (var foreignKey in entry.EntityType.GetForeignKeys())
763-
{
764-
if (!CanCreateDependency(foreignKey, command, principal: false)
765-
|| !IsModified(foreignKey.Properties, entry))
766-
{
767-
continue;
768-
}
697+
// Handle the cases where there is no relational model and where there is no mapped foreign key constraint (TPC)
698+
foreach (var (_, keyValue) in GetForeignKeyValues(
699+
command, principal, fromOriginalValues: !principal, checkStoreGenerated: principal))
700+
{
701+
AddPredecessor(predecessorsMap, keyValue, command);
702+
}
703+
}
769704

770-
var dependentKeyValue = foreignKey.GetDependentKeyValueFactory()
771-
?.CreateDependentEquatableKey(entry, fromOriginalValues: true);
705+
/// <summary>
706+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
707+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
708+
/// any release. You should only use it directly in your code with extreme caution and knowing that
709+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
710+
/// </summary>
711+
private void AddForeignKeyEdges(
712+
IReadOnlyModificationCommand command,
713+
Dictionary<object, List<IReadOnlyModificationCommand>> predecessorsMap,
714+
bool principal)
715+
{
716+
foreach (var (constraint, keyValue) in GetForeignKeyConstraintValues(command, principal, fromOriginalValues: principal))
717+
{
718+
AddMatchingPredecessorEdge(predecessorsMap, keyValue, command, constraint, checkStoreGenerated: !principal);
719+
}
772720

773-
if (dependentKeyValue != null)
774-
{
775-
if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
776-
{
777-
predecessorCommands = [];
778-
originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
779-
}
721+
// Handle the cases where there is no relational model and where there is no mapped foreign key constraint (TPC)
722+
foreach (var (foreignKey, keyValue) in GetForeignKeyValues(
723+
command, principal, fromOriginalValues: principal, checkStoreGenerated: false))
724+
{
725+
AddMatchingPredecessorEdge(predecessorsMap, keyValue, command, foreignKey, checkStoreGenerated: !principal);
726+
}
727+
}
780728

781-
predecessorCommands.Add(command);
782-
}
783-
}
784-
}
785-
}
786-
}
729+
private static IEnumerable<(IForeignKeyConstraint Constraint, object KeyValue)> GetForeignKeyConstraintValues(
730+
IReadOnlyModificationCommand command,
731+
bool principal,
732+
bool fromOriginalValues)
733+
{
734+
if (command.Table == null)
735+
{
736+
yield break;
787737
}
788738

789-
foreach (var command in _modificationCommandGraph.Vertices)
739+
var constraints = principal ? command.Table.ReferencingForeignKeyConstraints : command.Table.ForeignKeyConstraints;
740+
foreach (var constraint in constraints)
790741
{
791-
if (command.EntityState is EntityState.Modified or EntityState.Added)
742+
var columns = principal ? constraint.PrincipalUniqueConstraint.Columns : constraint.Columns;
743+
if (!IsModified(columns, command))
792744
{
793-
if (command.Table != null)
794-
{
795-
foreach (var foreignKey in command.Table.ForeignKeyConstraints)
796-
{
797-
if (!IsModified(foreignKey.Columns, command))
798-
{
799-
continue;
800-
}
801-
802-
var dependentKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
803-
.CreateDependentEquatableKeyValue(command);
804-
if (dependentKeyValue is null)
805-
{
806-
continue;
807-
}
808-
809-
AddMatchingPredecessorEdge(
810-
predecessorsMap, dependentKeyValue, command, foreignKey, checkStoreGenerated: true);
811-
}
812-
}
813-
814-
// ReSharper disable once ForCanBeConvertedToForeach
815-
for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
816-
{
817-
var entry = command.Entries[entryIndex];
818-
foreach (var foreignKey in entry.EntityType.GetForeignKeys())
819-
{
820-
if (!CanCreateDependency(foreignKey, command, principal: false)
821-
|| !IsModified(foreignKey.Properties, entry))
822-
{
823-
continue;
824-
}
745+
continue;
746+
}
825747

826-
var dependentKeyValue = foreignKey.GetDependentKeyValueFactory()
827-
?.CreateDependentEquatableKey(entry);
828-
if (dependentKeyValue == null)
829-
{
830-
continue;
831-
}
748+
var foreignKeyValueFactory = ((ForeignKeyConstraint)constraint).GetRowForeignKeyValueFactory();
749+
var keyValue = principal
750+
? foreignKeyValueFactory.CreatePrincipalEquatableKeyValue(command, fromOriginalValues)
751+
: foreignKeyValueFactory.CreateDependentEquatableKeyValue(command, fromOriginalValues);
832752

833-
AddMatchingPredecessorEdge(
834-
predecessorsMap, dependentKeyValue, command, foreignKey, checkStoreGenerated: true);
835-
}
836-
}
753+
if (keyValue is null)
754+
{
755+
Check.DebugAssert(!principal, "null principal keyValue");
756+
continue;
837757
}
838758

839-
if (command.EntityState is EntityState.Modified or EntityState.Deleted)
759+
yield return (constraint, keyValue);
760+
}
761+
}
762+
763+
private static IEnumerable<(IForeignKey ForeignKey, object KeyValue)> GetForeignKeyValues(
764+
IReadOnlyModificationCommand command,
765+
bool principal,
766+
bool fromOriginalValues,
767+
bool checkStoreGenerated)
768+
{
769+
// ReSharper disable once ForCanBeConvertedToForeach
770+
for (var i = 0; i < command.Entries.Count; i++)
771+
{
772+
var entry = command.Entries[i];
773+
var foreignKeys = principal
774+
? entry.EntityType.GetReferencingForeignKeys()
775+
: entry.EntityType.GetForeignKeys();
776+
foreach (var foreignKey in foreignKeys)
840777
{
841-
if (command.Table != null)
778+
var properties = principal ? foreignKey.PrincipalKey.Properties : foreignKey.Properties;
779+
if (!CanCreateDependency(foreignKey, command, principal)
780+
|| !IsModified(properties, entry)
781+
|| (command.Table != null
782+
&& (!checkStoreGenerated || !IsStoreGenerated(entry, foreignKey.PrincipalKey))
783+
&& foreignKey.GetMappedConstraints().Any()))
842784
{
843-
foreach (var foreignKey in command.Table.ReferencingForeignKeyConstraints)
844-
{
845-
if (!IsModified(foreignKey.PrincipalUniqueConstraint.Columns, command))
846-
{
847-
continue;
848-
}
785+
continue;
786+
}
849787

850-
var principalKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
851-
.CreatePrincipalEquatableKeyValue(command, fromOriginalValues: true);
852-
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
853-
AddMatchingPredecessorEdge(
854-
originalPredecessorsMap, principalKeyValue, command, foreignKey);
855-
}
788+
var foreignKeyValueFactory = foreignKey.GetDependentKeyValueFactory();
789+
var keyValue = principal
790+
? foreignKeyValueFactory.CreatePrincipalEquatableKey(entry, fromOriginalValues)
791+
: foreignKeyValueFactory.CreateDependentEquatableKey(entry, fromOriginalValues);
856792

857-
// Also handle FKs with no mapped constraints (e.g. TPC with abstract principal mapped to no table)
858-
// ReSharper disable once ForCanBeConvertedToForeach
859-
for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
860-
{
861-
var entry = command.Entries[entryIndex];
862-
foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys())
863-
{
864-
if (!CanCreateDependency(foreignKey, command, principal: true)
865-
|| foreignKey.GetMappedConstraints().Any())
866-
{
867-
continue;
868-
}
869-
870-
var principalKeyValue = foreignKey.GetDependentKeyValueFactory()
871-
.CreatePrincipalEquatableKey(entry, fromOriginalValues: true);
872-
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
873-
AddMatchingPredecessorEdge(
874-
originalPredecessorsMap, principalKeyValue, command, foreignKey);
875-
}
876-
}
877-
}
878-
else
793+
if (keyValue != null)
879794
{
880-
// ReSharper disable once ForCanBeConvertedToForeach
881-
for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
882-
{
883-
var entry = command.Entries[entryIndex];
884-
foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys())
885-
{
886-
if (!CanCreateDependency(foreignKey, command, principal: true))
887-
{
888-
continue;
889-
}
890-
891-
var principalKeyValue = foreignKey.GetDependentKeyValueFactory()
892-
.CreatePrincipalEquatableKey(entry, fromOriginalValues: true);
893-
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
894-
AddMatchingPredecessorEdge(
895-
originalPredecessorsMap, principalKeyValue, command, foreignKey);
896-
}
897-
}
795+
yield return (foreignKey, keyValue);
898796
}
899797
}
900798
}
901799
}
902800

801+
private static void AddPredecessor(
802+
Dictionary<object, List<IReadOnlyModificationCommand>> predecessorsMap,
803+
object keyValue,
804+
IReadOnlyModificationCommand command)
805+
{
806+
if (!predecessorsMap.TryGetValue(keyValue, out var predecessorCommands))
807+
{
808+
predecessorCommands = [];
809+
predecessorsMap.Add(keyValue, predecessorCommands);
810+
}
811+
812+
predecessorCommands.Add(command);
813+
}
814+
903815
private static bool IsStoreGenerated(IUpdateEntry entry, IKey key)
904816
{
905817
var keyProperties = key.Properties;
@@ -1149,7 +1061,7 @@ private void AddMatchingPredecessorEdge<T>(
11491061
T keyValue,
11501062
IReadOnlyModificationCommand command,
11511063
IForeignKeyConstraint foreignKey,
1152-
bool checkStoreGenerated = false)
1064+
bool checkStoreGenerated)
11531065
where T : notnull
11541066
{
11551067
if (predecessorsMap.TryGetValue(keyValue, out var predecessorCommands))

0 commit comments

Comments
 (0)