Skip to content

Commit

Permalink
Sort inserts to preserve tracking order on more cases.
Browse files Browse the repository at this point in the history
Always sort deletes before updates and updates before inserts for the same table.

Fixes #14371
Fixes #15180
Fixes #25228
  • Loading branch information
AndriySvyryd committed Sep 4, 2021
1 parent d82352e commit 1b924c7
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 122 deletions.
125 changes: 27 additions & 98 deletions src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ protected virtual IEnumerable<IReadOnlyModificationCommand> CreateModificationCo
foreach (var mapping in mappings)
{
var table = mapping.Table;
var tableKey = (table.Name, table.Schema);

IModificationCommand command;
var isMainEntry = true;
Expand All @@ -189,6 +188,7 @@ protected virtual IEnumerable<IReadOnlyModificationCommand> CreateModificationCo
sharedTablesCommandsMap = new Dictionary<(string, string?), SharedTableEntryMap<IModificationCommand>>();
}

var tableKey = (table.Name, table.Schema);
if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap))
{
sharedCommandsMap = new SharedTableEntryMap<IModificationCommand>(table, updateAdapter);
Expand Down Expand Up @@ -285,7 +285,7 @@ protected virtual IReadOnlyList<List<IReadOnlyModificationCommand>> TopologicalS
var predecessorsMap = CreateKeyValuePredecessorMap(modificationCommandGraph);
AddForeignKeyEdges(modificationCommandGraph, predecessorsMap);

AddUniqueValueEdges(modificationCommandGraph);
AddSameTableEdges(modificationCommandGraph);

return modificationCommandGraph.BatchingTopologicalSort(FormatCycle);
}
Expand Down Expand Up @@ -641,125 +641,54 @@ private static void AddMatchingPredecessorEdge<T>(
}
}

private void AddUniqueValueEdges(Multigraph<IReadOnlyModificationCommand, IAnnotatable> commandGraph)
private static void AddSameTableEdges(Multigraph<IReadOnlyModificationCommand, IAnnotatable> modificationCommandGraph)
{
Dictionary<IIndex, Dictionary<object[], IReadOnlyModificationCommand>>? indexPredecessorsMap = null;
var keyPredecessorsMap = new Dictionary<(IKey, IKeyValueIndex), List<IReadOnlyModificationCommand>>();
foreach (var command in commandGraph.Vertices)
var deletedDictionary = new Dictionary<(string, string?), List<IReadOnlyModificationCommand>>();
var modifiedDictionary = new Dictionary<(string, string?), List<IReadOnlyModificationCommand>>();

foreach (var command in modificationCommandGraph.Vertices)
{
if (command.EntityState != EntityState.Modified
&& command.EntityState != EntityState.Deleted)
if (command.EntityState == EntityState.Deleted)
{
continue;
deletedDictionary.GetOrAddNew((command.TableName, command.Schema)).Add(command);
}
}

for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
foreach (var command in modificationCommandGraph.Vertices)
{
if (command.EntityState == EntityState.Modified)
{
var entry = command.Entries[entryIndex];
foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any()))
var key = (command.TableName, command.Schema);
if (deletedDictionary.TryGetValue(key, out var deletedList))
{
if (entry.EntityState == EntityState.Modified
&& !index.Properties.Any(p => entry.IsModified(p)))
foreach (var deleted in deletedList)
{
continue;
}

var valueFactory = index.GetNullableValueFactory<object[]>();
if (valueFactory.TryCreateFromOriginalValues(entry, out var indexValue))
{
indexPredecessorsMap ??= new Dictionary<IIndex, Dictionary<object[], IReadOnlyModificationCommand>>();
if (!indexPredecessorsMap.TryGetValue(index, out var predecessorCommands))
{
predecessorCommands = new Dictionary<object[], IReadOnlyModificationCommand>(valueFactory.EqualityComparer);
indexPredecessorsMap.Add(index, predecessorCommands);
}

if (!predecessorCommands.ContainsKey(indexValue))
{
predecessorCommands.Add(indexValue, command);
}
modificationCommandGraph.AddEdge(deleted, command, command.Entries[0].EntityType);
}
}

if (command.EntityState != EntityState.Deleted)
{
continue;
}

foreach (var key in entry.EntityType.GetKeys().Where(k => k.GetMappedConstraints().Any()))
{
var principalKeyValue = Dependencies.KeyValueIndexFactorySource
.GetKeyValueIndexFactory(key)
.CreatePrincipalKeyValue(entry, null);

if (principalKeyValue != null)
{
if (!keyPredecessorsMap.TryGetValue((key, principalKeyValue), out var predecessorCommands))
{
predecessorCommands = new List<IReadOnlyModificationCommand>();
keyPredecessorsMap.Add((key, principalKeyValue), predecessorCommands);
}

predecessorCommands.Add(command);
}
}
modifiedDictionary.GetOrAddNew(key).Add(command);
}
}

if (indexPredecessorsMap != null)
foreach (var command in modificationCommandGraph.Vertices)
{
foreach (var command in commandGraph.Vertices)
if (command.EntityState == EntityState.Added)
{
if (command.EntityState == EntityState.Deleted)
var key = (command.TableName, command.Schema);
if (deletedDictionary.TryGetValue(key, out var deletedList))
{
continue;
}

foreach (var entry in command.Entries)
{
foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any()))
foreach (var deleted in deletedList)
{
if (entry.EntityState == EntityState.Modified
&& !index.Properties.Any(p => entry.IsModified(p)))
{
continue;
}

var valueFactory = index.GetNullableValueFactory<object[]>();
if (valueFactory.TryCreateFromCurrentValues(entry, out var indexValue)
&& indexPredecessorsMap.TryGetValue(index, out var predecessorCommands)
&& predecessorCommands.TryGetValue(indexValue, out var predecessor)
&& predecessor != command)
{
commandGraph.AddEdge(predecessor, command, index);
}
modificationCommandGraph.AddEdge(deleted, command, command.Entries[0].EntityType);
}
}
}
}

if (keyPredecessorsMap != null)
{
foreach (var command in commandGraph.Vertices)
{
if (command.EntityState != EntityState.Added)
{
continue;
}

foreach (var entry in command.Entries)
if (modifiedDictionary.TryGetValue(key, out var modifiedList))
{
foreach (var key in entry.EntityType.GetKeys().Where(k => k.GetMappedConstraints().Any()))
foreach (var modified in modifiedList)
{
var principalKeyValue = Dependencies.KeyValueIndexFactorySource
.GetKeyValueIndexFactory(key)
.CreatePrincipalKeyValue(entry, null);

if (principalKeyValue != null)
{
AddMatchingPredecessorEdge(
keyPredecessorsMap, (key, principalKeyValue), commandGraph, command, key);
}
modificationCommandGraph.AddEdge(modified, command, command.Entries[0].EntityType);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,13 @@ public virtual int Compare(IReadOnlyModificationCommand? x, IReadOnlyModificatio
return 1;
}

result = StringComparer.Ordinal.Compare(x.Schema, y.Schema);
if (result != 0)
{
return result;
}

result = StringComparer.Ordinal.Compare(x.TableName, y.TableName);
if (result != 0)
{
return result;
}

var xState = x.EntityState;
result = (int)xState - (int)y.EntityState;
result = StringComparer.Ordinal.Compare(x.Schema, y.Schema);
if (result != 0)
{
return result;
Expand All @@ -82,18 +75,15 @@ public virtual int Compare(IReadOnlyModificationCommand? x, IReadOnlyModificatio
}
}

if (xState != EntityState.Added)
var xKey = xEntry.EntityType.FindPrimaryKey()!;
for (var i = 0; i < xKey.Properties.Count; i++)
{
var xKey = xEntry.EntityType.FindPrimaryKey()!;
for (var i = 0; i < xKey.Properties.Count; i++)
{
var xKeyProperty = xKey.Properties[i];
var xKeyProperty = xKey.Properties[i];

result = xKeyProperty.GetCurrentValueComparer().Compare(xEntry, yEntry);
if (result != 0)
{
return result;
}
result = xKeyProperty.GetCurrentValueComparer().Compare(xEntry, yEntry);
if (result != 0)
{
return result;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public static DbContextOptionsBuilder UseSqlServer(
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
Check.NotEmpty(connectionString, nameof(connectionString));

var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnectionString(connectionString);
var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithConnection(null).WithConnectionString(connectionString);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);
Expand Down Expand Up @@ -90,7 +91,8 @@ public static DbContextOptionsBuilder UseSqlServer(
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
Check.NotNull(connection, nameof(connection));

var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection);
var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithConnectionString(null).WithConnection(connection);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public static DbContextOptionsBuilder UseSqlite(
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
Check.NotEmpty(connectionString, nameof(connectionString));

var extension = (SqliteOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnectionString(connectionString);
var extension = (SqliteOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithConnection(null).WithConnectionString(connectionString);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);
Expand Down Expand Up @@ -89,7 +90,8 @@ public static DbContextOptionsBuilder UseSqlite(
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
Check.NotNull(connection, nameof(connection));

var extension = (SqliteOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection);
var extension = (SqliteOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithConnectionString(null).WithConnection(connection);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);
Expand Down
Loading

0 comments on commit 1b924c7

Please sign in to comment.