Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 41 additions & 34 deletions src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,8 @@ private void AddForeignKeyEdges()
if (!CanCreateDependency(foreignKey, command, principal: true)
|| !IsModified(foreignKey.PrincipalKey.Properties, entry)
|| (command.Table != null
&& !IsStoreGenerated(entry, foreignKey.PrincipalKey)))
&& !IsStoreGenerated(entry, foreignKey.PrincipalKey)
&& foreignKey.GetMappedConstraints().Any()))
Comment thread
andrewraper-Sage marked this conversation as resolved.
{
continue;
}
Expand Down Expand Up @@ -726,31 +727,30 @@ private void AddForeignKeyEdges()
}
}
}
else

foreach (var entry in command.Entries)
{
foreach (var entry in command.Entries)
foreach (var foreignKey in entry.EntityType.GetForeignKeys())
{
foreach (var foreignKey in entry.EntityType.GetForeignKeys())
if (!CanCreateDependency(foreignKey, command, principal: false)
Comment thread
andrewraper-Sage marked this conversation as resolved.
Outdated
|| !IsModified(foreignKey.Properties, entry)
|| foreignKey.GetMappedConstraints().Any(c => c.Table == command.Table))
Comment thread
andrewraper-Sage marked this conversation as resolved.
Outdated
{
if (!CanCreateDependency(foreignKey, command, principal: false)
|| !IsModified(foreignKey.Properties, entry))
{
continue;
}
continue;
}

var dependentKeyValue = foreignKey.GetDependentKeyValueFactory()
?.CreateDependentEquatableKey(entry, fromOriginalValues: true);
var dependentKeyValue = foreignKey.GetDependentKeyValueFactory()
?.CreateDependentEquatableKey(entry, fromOriginalValues: true);

if (dependentKeyValue != null)
if (dependentKeyValue != null)
{
if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
{
if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
{
predecessorCommands = [];
originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
}

predecessorCommands.Add(command);
predecessorCommands = [];
originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands);
}

predecessorCommands.Add(command);
}
}
}
Expand Down Expand Up @@ -825,25 +825,24 @@ private void AddForeignKeyEdges()
originalPredecessorsMap, principalKeyValue, command, foreignKey);
}
}
else

// ReSharper disable once ForCanBeConvertedToForeach
for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
{
// ReSharper disable once ForCanBeConvertedToForeach
for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
var entry = command.Entries[entryIndex];
foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys())
{
var entry = command.Entries[entryIndex];
foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys())
if (!CanCreateDependency(foreignKey, command, principal: true)
|| foreignKey.GetMappedConstraints().Any(c => c.PrincipalTable == command.Table))
Comment thread
andrewraper-Sage marked this conversation as resolved.
Outdated
{
if (!CanCreateDependency(foreignKey, command, principal: true))
{
continue;
}

var principalKeyValue = foreignKey.GetDependentKeyValueFactory()
.CreatePrincipalEquatableKey(entry, fromOriginalValues: true);
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
AddMatchingPredecessorEdge(
originalPredecessorsMap, principalKeyValue, command, foreignKey);
continue;
}

var principalKeyValue = foreignKey.GetDependentKeyValueFactory()
.CreatePrincipalEquatableKey(entry, fromOriginalValues: true);
Check.DebugAssert(principalKeyValue != null, "null principalKeyValue");
AddMatchingPredecessorEdge(
originalPredecessorsMap, principalKeyValue, command, foreignKey);
}
}
}
Expand Down Expand Up @@ -873,6 +872,14 @@ private static bool CanCreateDependency(IForeignKey foreignKey, IReadOnlyModific
{
if (command.Table != null)
{
// JSON-owned entities are stored inline in their owner's column and never have separate
// modification commands, so they cannot participate in inter-command dependency ordering.
var otherEntityType = principal ? foreignKey.DeclaringEntityType : foreignKey.PrincipalEntityType;
if (otherEntityType.IsMappedToJson())
{
return false;
}

if (foreignKey.IsRowInternal(StoreObjectIdentifier.Table(command.TableName, command.Schema))
|| (foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
&& foreignKey.PrincipalKey.Properties.SequenceEqual(foreignKey.Properties)))
Expand Down
86 changes: 86 additions & 0 deletions test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1328,4 +1328,90 @@ private class AnotherFakeEntity
public int Id { get; set; }
public int? AnotherId { get; set; }
}

[ConditionalFact]
public void BatchCommands_sorts_added_entities_with_TPC_abstract_principal()
{
var configuration = CreateContextServices(CreateTpcFKModel());
var stateManager = configuration.GetRequiredService<IStateManager>();

var principalEntry = stateManager.GetOrCreateEntry(
new ConcretePrincipal { Id = 1 });
principalEntry.SetEntityState(EntityState.Added);

var dependentEntry = stateManager.GetOrCreateEntry(
new TpcDependent { Id = 1, PrincipalId = 1 });
dependentEntry.SetEntityState(EntityState.Added);

var modelData = new UpdateAdapter(stateManager);

var batches = CreateBatches([dependentEntry, principalEntry], modelData);
var batch = Assert.Single(batches);

Assert.Equal(
[principalEntry, dependentEntry],
batch.ModificationCommands.Select(c => c.Entries.Single()));
}

[ConditionalFact]
public void BatchCommands_sorts_deleted_entities_with_TPC_abstract_principal()
{
var configuration = CreateContextServices(CreateTpcFKModel());
var stateManager = configuration.GetRequiredService<IStateManager>();

var principalEntry = stateManager.GetOrCreateEntry(
new ConcretePrincipal { Id = 1 });
principalEntry.SetEntityState(EntityState.Deleted);

var dependentEntry = stateManager.GetOrCreateEntry(
new TpcDependent { Id = 1, PrincipalId = 1 });
dependentEntry.SetEntityState(EntityState.Deleted);

var modelData = new UpdateAdapter(stateManager);

var batches = CreateBatches([principalEntry, dependentEntry], modelData);
var batch = Assert.Single(batches);

Assert.Equal(
[dependentEntry, principalEntry],
batch.ModificationCommands.Select(c => c.Entries.Single()));
}

private static IModel CreateTpcFKModel()
{
var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder();

modelBuilder.Entity<AbstractPrincipal>()
.UseTpcMappingStrategy()
.ToTable((string)null)
.Property(e => e.Id)
.ValueGeneratedNever();

Comment thread
andrewraper-Sage marked this conversation as resolved.
modelBuilder.Entity<ConcretePrincipal>()
.ToTable(nameof(ConcretePrincipal));

Comment thread
andrewraper-Sage marked this conversation as resolved.
modelBuilder.Entity<TpcDependent>(b =>
{
b.HasOne<AbstractPrincipal>()
.WithMany()
.HasForeignKey(c => c.PrincipalId);
});

return modelBuilder.Model.FinalizeModel();
}

private abstract class AbstractPrincipal
{
public int Id { get; set; }
}

private class ConcretePrincipal : AbstractPrincipal
{
}

private class TpcDependent
{
public int Id { get; set; }
public int PrincipalId { get; set; }
}
}
Loading