Skip to content

Commit 8236cb9

Browse files
authored
Removed unnecessary batching boundaries from topological sort (#27713)
Closes #20664
1 parent 0b3e4fb commit 8236cb9

File tree

20 files changed

+535
-425
lines changed

20 files changed

+535
-425
lines changed

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

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class CommandBatchPreparer : ICommandBatchPreparer
1616
{
1717
private readonly int _minBatchSize;
1818
private readonly bool _sensitiveLoggingEnabled;
19-
private readonly Multigraph<IReadOnlyModificationCommand, IAnnotatable> _modificationCommandGraph = new();
19+
private readonly Multigraph<IReadOnlyModificationCommand, IAnnotatable> _modificationCommandGraph;
2020

2121
/// <summary>
2222
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -29,6 +29,8 @@ public CommandBatchPreparer(CommandBatchPreparerDependencies dependencies)
2929
_minBatchSize =
3030
dependencies.Options.Extensions.OfType<RelationalOptionsExtension>().FirstOrDefault()?.MinBatchSize
3131
?? 1;
32+
33+
_modificationCommandGraph = new(dependencies.ModificationCommandComparer);
3234
Dependencies = dependencies;
3335

3436
if (dependencies.LoggingOptions.IsSensitiveDataLoggingEnabled)
@@ -57,16 +59,14 @@ public CommandBatchPreparer(CommandBatchPreparerDependencies dependencies)
5759
{
5860
var parameterNameGenerator = Dependencies.ParameterNameGeneratorFactory.Create();
5961
var commands = CreateModificationCommands(entries, updateAdapter, parameterNameGenerator.GenerateNext);
60-
var sortedCommandSets = TopologicalSort(commands);
62+
var commandSets = TopologicalSort(commands);
6163

62-
for (var commandSetIndex = 0; commandSetIndex < sortedCommandSets.Count; commandSetIndex++)
64+
for (var commandSetIndex = 0; commandSetIndex < commandSets.Count; commandSetIndex++)
6365
{
64-
var independentCommandSet = sortedCommandSets[commandSetIndex];
65-
66-
independentCommandSet.Sort(Dependencies.ModificationCommandComparer);
66+
var commandSet = commandSets[commandSetIndex];
6767

6868
var batch = Dependencies.ModificationCommandBatchFactory.Create();
69-
foreach (var modificationCommand in independentCommandSet)
69+
foreach (var modificationCommand in commandSet)
7070
{
7171
(modificationCommand as ModificationCommand)?.AssertColumnsNotInitialized();
7272
if (modificationCommand.EntityState == EntityState.Modified
@@ -108,7 +108,7 @@ public CommandBatchPreparer(CommandBatchPreparerDependencies dependencies)
108108
}
109109
}
110110

111-
var hasMoreCommandSets = commandSetIndex < sortedCommandSets.Count - 1;
111+
var hasMoreCommandSets = commandSetIndex < commandSets.Count - 1;
112112

113113
if (batch.ModificationCommands.Count == 1
114114
|| batch.ModificationCommands.Count >= _minBatchSize)
@@ -295,10 +295,10 @@ private string FormatCycle(
295295
var builder = new StringBuilder();
296296
for (var i = 0; i < data.Count; i++)
297297
{
298-
var (command1, command2, annotatables) = data[i];
298+
var (command1, command2, edges) = data[i];
299299
Format(command1, builder);
300300

301-
switch (annotatables.First())
301+
switch (edges.First())
302302
{
303303
case IForeignKeyConstraint foreignKey:
304304
Format(foreignKey, command1, command2, builder);
@@ -536,10 +536,40 @@ private void AddForeignKeyEdges(
536536

537537
var dependentKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory()
538538
.CreateDependentValueIndex(command);
539-
if (dependentKeyValue != null)
539+
540+
if (dependentKeyValue is null || !predecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands))
540541
{
541-
AddMatchingPredecessorEdge(
542-
predecessorsMap, dependentKeyValue, commandGraph, command, foreignKey);
542+
continue;
543+
}
544+
545+
foreach (var predecessor in predecessorCommands)
546+
{
547+
if (predecessor != command)
548+
{
549+
// If we're adding/inserting a dependent where the principal key is being database-generated, then
550+
// the dependency edge represents a batching boundary: fetch the principal database-generated
551+
// property from the database in separate batch, in order to populate the dependent foreign key
552+
// property in the next.
553+
var requiresBatchingBoundary = false;
554+
555+
for (var i = 0; i < foreignKey.PrincipalColumns.Count; i++)
556+
{
557+
for (var j = 0; j < predecessor.Entries.Count; j++)
558+
{
559+
var entry = predecessor.Entries[j];
560+
561+
if (foreignKey.PrincipalColumns[i].FindColumnMapping(entry.EntityType) is IColumnMapping columnMapping
562+
&& entry.IsStoreGenerated(columnMapping.Property))
563+
{
564+
requiresBatchingBoundary = true;
565+
goto AfterLoop;
566+
}
567+
}
568+
}
569+
AfterLoop:
570+
571+
commandGraph.AddEdge(predecessor, command, foreignKey, requiresBatchingBoundary);
572+
}
543573
}
544574
}
545575
}
@@ -623,7 +653,7 @@ private static void AddMatchingPredecessorEdge<T>(
623653
T keyValue,
624654
Multigraph<IReadOnlyModificationCommand, IAnnotatable> commandGraph,
625655
IReadOnlyModificationCommand command,
626-
IAnnotatable edge)
656+
IAnnotatable edgeAnnotatable)
627657
where T : notnull
628658
{
629659
if (predecessorsMap.TryGetValue(keyValue, out var predecessorCommands))
@@ -632,7 +662,7 @@ private static void AddMatchingPredecessorEdge<T>(
632662
{
633663
if (predecessor != command)
634664
{
635-
commandGraph.AddEdge(predecessor, command, edge);
665+
commandGraph.AddEdge(predecessor, command, edgeAnnotatable);
636666
}
637667
}
638668
}

src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public class SqlServerModificationCommandBatch : AffectedCountModificationComman
1818
private const int DefaultNetworkPacketSizeBytes = 4096;
1919
private const int MaxScriptLength = 65536 * DefaultNetworkPacketSizeBytes / 2;
2020
private const int MaxParameterCount = 2100;
21-
private readonly int _maxBatchSize;
2221
private readonly List<IReadOnlyModificationCommand> _pendingBulkInsertCommands = new();
2322

2423
/// <summary>
@@ -31,7 +30,7 @@ public SqlServerModificationCommandBatch(
3130
ModificationCommandBatchFactoryDependencies dependencies,
3231
int maxBatchSize)
3332
: base(dependencies)
34-
=> _maxBatchSize = maxBatchSize;
33+
=> MaxBatchSize = maxBatchSize;
3534

3635
/// <summary>
3736
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -48,8 +47,7 @@ public SqlServerModificationCommandBatch(
4847
/// <remarks>
4948
/// For SQL Server, this is 42 by default, and cannot exceed 1000.
5049
/// </remarks>
51-
protected override int MaxBatchSize
52-
=> _maxBatchSize;
50+
protected override int MaxBatchSize { get; }
5351

5452
/// <summary>
5553
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -165,8 +163,8 @@ protected override void AddCommand(IReadOnlyModificationCommand modificationComm
165163
private static bool CanBeInsertedInSameStatement(
166164
IReadOnlyModificationCommand firstCommand,
167165
IReadOnlyModificationCommand secondCommand)
168-
=> string.Equals(firstCommand.TableName, secondCommand.TableName, StringComparison.Ordinal)
169-
&& string.Equals(firstCommand.Schema, secondCommand.Schema, StringComparison.Ordinal)
166+
=> firstCommand.TableName == secondCommand.TableName
167+
&& firstCommand.Schema == secondCommand.Schema
170168
&& firstCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName).SequenceEqual(
171169
secondCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName))
172170
&& firstCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName).SequenceEqual(

src/EFCore/Metadata/IReadOnlyEntityType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ IEnumerable<IReadOnlyEntityType> GetDerivedTypesInclusive()
125125
=> new[] { this }.Concat(GetDerivedTypes());
126126

127127
/// <summary>
128-
/// Gets all types in the model that directly derive from a given entity type.
128+
/// Gets all types in the model that directly derive from a given entity type, in a deterministic top-to-bottom ordering.
129129
/// </summary>
130130
/// <returns>The derived types.</returns>
131131
IEnumerable<IReadOnlyEntityType> GetDirectlyDerivedTypes();

src/EFCore/Metadata/Internal/EntityType.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,9 +427,8 @@ private void UpdateBaseTypeConfigurationSource(ConfigurationSource configuration
427427
/// any release. You should only use it directly in your code with extreme caution and knowing that
428428
/// doing so can result in application failures when updating to a new Entity Framework Core release.
429429
/// </summary>
430-
// Note this is ISet because there is no suitable readonly interface in the profiles we are using
431430
[DebuggerStepThrough]
432-
public virtual ISet<EntityType> GetDirectlyDerivedTypes()
431+
public virtual IReadOnlySet<EntityType> GetDirectlyDerivedTypes()
433432
=> _directlyDerivedTypes;
434433

435434
/// <summary>

src/EFCore/Metadata/Internal/ModelExtensions.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections;
5+
46
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
57

68
/// <summary>
@@ -36,18 +38,25 @@ public static IEnumerable<IEntityType> GetRootEntityTypes(this IModel model)
3638
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3739
/// </summary>
3840
public static IEnumerable<IEntityType> GetEntityTypesInHierarchicalOrder(this IModel model)
39-
=> Sort(model.GetEntityTypes());
40-
41-
private static IEnumerable<IEntityType> Sort(IEnumerable<IEntityType> entityTypes)
4241
{
43-
var entityTypeGraph = new Multigraph<IEntityType, int>();
44-
entityTypeGraph.AddVertices(entityTypes);
45-
foreach (var entityType in entityTypes.Where(et => et.BaseType != null))
42+
var entityTypes = new Queue<IEntityType>();
43+
44+
foreach (var root in model.GetRootEntityTypes())
4645
{
47-
entityTypeGraph.AddEdge(entityType.BaseType!, entityType, 0);
46+
entityTypes.Enqueue(root);
4847
}
4948

50-
return entityTypeGraph.BatchingTopologicalSort().SelectMany(b => b.OrderBy(et => et.Name));
49+
while (entityTypes.Count > 0)
50+
{
51+
var current = entityTypes.Dequeue();
52+
53+
yield return current;
54+
55+
foreach (var descendant in current.GetDirectlyDerivedTypes())
56+
{
57+
entityTypes.Enqueue(descendant);
58+
}
59+
}
5160
}
5261

5362
/// <summary>

0 commit comments

Comments
 (0)