Skip to content

Commit

Permalink
Stop wrapping single changes in transactions where possible
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Feb 23, 2022
1 parent 8d4e2da commit 499d454
Show file tree
Hide file tree
Showing 29 changed files with 780 additions and 63 deletions.
12 changes: 12 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,12 @@
<data name="MissingParameterValue" xml:space="preserve">
<value>No value was provided for the required parameter '{parameter}'.</value>
</data>
<data name="ModificationCommandBatchAlreadyComplete" xml:space="preserve">
<value>Cannot add commands to a completed ModificationCommandBatch.</value>
</data>
<data name="ModificationCommandBatchNotComplete" xml:space="preserve">
<value>Cannot execute an ModificationCommandBatch which hasn't been completed.</value>
</data>
<data name="ModificationCommandInvalidEntityState" xml:space="preserve">
<value>Cannot save changes for an entity of type '{entityType}' in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values of the entity.</value>
</data>
Expand Down
57 changes: 54 additions & 3 deletions src/EFCore.Relational/Update/IUpdateSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ void AppendNextSequenceValueOperation(
/// <param name="commandStringBuilder">The builder to which the SQL fragment should be appended.</param>
void AppendBatchHeader(StringBuilder commandStringBuilder);

/// <summary>
/// Prepends a SQL command for turning on autocommit mode in the database, in case it is off.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be prepended.</param>
void PrependEnsureAutocommit(StringBuilder commandStringBuilder);

/// <summary>
/// Appends a SQL command for deleting a row to the commands being built.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be appended.</param>
/// <param name="command">The command that represents the delete operation.</param>
/// <param name="commandPosition">The ordinal of this command in the batch.</param>
/// <param name="requiresTransaction">Returns whether the SQL appended must be executed in a transaction to work correctly.</param>
/// <returns>The <see cref="ResultSetMapping" /> for the command.</returns>
ResultSetMapping AppendDeleteOperation(
StringBuilder commandStringBuilder,
IReadOnlyModificationCommand command,
int commandPosition,
out bool requiresTransaction);

/// <summary>
/// Appends a SQL command for deleting a row to the commands being built.
/// </summary>
Expand All @@ -64,7 +84,22 @@ void AppendNextSequenceValueOperation(
ResultSetMapping AppendDeleteOperation(
StringBuilder commandStringBuilder,
IReadOnlyModificationCommand command,
int commandPosition);
int commandPosition)
=> AppendDeleteOperation(commandStringBuilder, command, commandPosition, out _);

/// <summary>
/// Appends a SQL command for inserting a row to the commands being built.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be appended.</param>
/// <param name="command">The command that represents the delete operation.</param>
/// <param name="commandPosition">The ordinal of this command in the batch.</param>
/// <param name="requiresTransaction">Returns whether the SQL appended must be executed in a transaction to work correctly.</param>
/// <returns>The <see cref="ResultSetMapping" /> for the command.</returns>
ResultSetMapping AppendInsertOperation(
StringBuilder commandStringBuilder,
IReadOnlyModificationCommand command,
int commandPosition,
out bool requiresTransaction);

/// <summary>
/// Appends a SQL command for inserting a row to the commands being built.
Expand All @@ -76,7 +111,22 @@ ResultSetMapping AppendDeleteOperation(
ResultSetMapping AppendInsertOperation(
StringBuilder commandStringBuilder,
IReadOnlyModificationCommand command,
int commandPosition);
int commandPosition)
=> AppendInsertOperation(commandStringBuilder, command, commandPosition, out _);

/// <summary>
/// Appends a SQL command for updating a row to the commands being built.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be appended.</param>
/// <param name="command">The command that represents the delete operation.</param>
/// <param name="commandPosition">The ordinal of this command in the batch.</param>
/// <param name="requiresTransaction">Returns whether the SQL appended must be executed in a transaction to work correctly.</param>
/// <returns>The <see cref="ResultSetMapping" /> for the command.</returns>
ResultSetMapping AppendUpdateOperation(
StringBuilder commandStringBuilder,
IReadOnlyModificationCommand command,
int commandPosition,
out bool requiresTransaction);

/// <summary>
/// Appends a SQL command for updating a row to the commands being built.
Expand All @@ -88,5 +138,6 @@ ResultSetMapping AppendInsertOperation(
ResultSetMapping AppendUpdateOperation(
StringBuilder commandStringBuilder,
IReadOnlyModificationCommand command,
int commandPosition);
int commandPosition)
=> AppendUpdateOperation(commandStringBuilder, command, commandPosition, out _);
}
46 changes: 38 additions & 8 deletions src/EFCore.Relational/Update/Internal/BatchExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ public virtual int Execute(
IEnumerable<ModificationCommandBatch> commandBatches,
IRelationalConnection connection)
{
using var batchEnumerator = commandBatches.GetEnumerator();

if (!batchEnumerator.MoveNext())
{
return 0;
}

var currentBatch = batchEnumerator.Current;
var nextBatch = batchEnumerator.MoveNext() ? batchEnumerator.Current : null;

var rowsAffected = 0;
var transaction = connection.CurrentTransaction;
var beganTransaction = false;
Expand All @@ -62,7 +72,9 @@ public virtual int Execute(
if (transaction == null
&& transactionEnlistManager?.EnlistedTransaction is null
&& transactionEnlistManager?.CurrentAmbientTransaction is null
&& CurrentContext.Context.Database.AutoTransactionsEnabled)
&& CurrentContext.Context.Database.AutoTransactionsEnabled
// Don't start a transaction if we have a single batch which doesn't require a transaction (single command), for perf.
&& (nextBatch is not null || currentBatch.RequiresTransaction))
{
transaction = connection.BeginTransaction();
beganTransaction = true;
Expand All @@ -79,10 +91,13 @@ public virtual int Execute(
}
}

foreach (var batch in commandBatches)
while (currentBatch is not null)
{
batch.Execute(connection);
rowsAffected += batch.ModificationCommands.Count;
currentBatch.Execute(connection);
rowsAffected += currentBatch.ModificationCommands.Count;

currentBatch = nextBatch;
nextBatch = batchEnumerator.MoveNext() ? batchEnumerator.Current : null;
}

if (beganTransaction)
Expand Down Expand Up @@ -147,6 +162,16 @@ public virtual async Task<int> ExecuteAsync(
IRelationalConnection connection,
CancellationToken cancellationToken = default)
{
using var batchEnumerator = commandBatches.GetEnumerator();

if (!batchEnumerator.MoveNext())
{
return 0;
}

var currentBatch = batchEnumerator.Current;
var nextBatch = batchEnumerator.MoveNext() ? batchEnumerator.Current : null;

var rowsAffected = 0;
var transaction = connection.CurrentTransaction;
var beganTransaction = false;
Expand All @@ -157,7 +182,9 @@ public virtual async Task<int> ExecuteAsync(
if (transaction == null
&& transactionEnlistManager?.EnlistedTransaction is null
&& transactionEnlistManager?.CurrentAmbientTransaction is null
&& CurrentContext.Context.Database.AutoTransactionsEnabled)
&& CurrentContext.Context.Database.AutoTransactionsEnabled
// Don't start a transaction if we have a single batch which doesn't require a transaction (single command), for perf.
&& (nextBatch is not null || currentBatch.RequiresTransaction))
{
transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
beganTransaction = true;
Expand All @@ -174,10 +201,13 @@ public virtual async Task<int> ExecuteAsync(
}
}

foreach (var batch in commandBatches)
while (currentBatch is not null)
{
await batch.ExecuteAsync(connection, cancellationToken).ConfigureAwait(false);
rowsAffected += batch.ModificationCommands.Count;
await currentBatch.ExecuteAsync(connection, cancellationToken).ConfigureAwait(false);
rowsAffected += currentBatch.ModificationCommands.Count;

currentBatch = nextBatch;
nextBatch = batchEnumerator.MoveNext() ? batchEnumerator.Current : null;
}

if (beganTransaction)
Expand Down
14 changes: 12 additions & 2 deletions src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public virtual IEnumerable<ModificationCommandBatch> BatchCommands(
batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count);
}

batch.Complete();

yield return batch;
}
else
Expand All @@ -92,7 +94,10 @@ public virtual IEnumerable<ModificationCommandBatch> BatchCommands(

foreach (var command in batch.ModificationCommands)
{
yield return StartNewBatch(parameterNameGenerator, command);
batch = StartNewBatch(parameterNameGenerator, command);
batch.Complete();

yield return batch;
}
}

Expand All @@ -109,6 +114,8 @@ public virtual IEnumerable<ModificationCommandBatch> BatchCommands(
batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count);
}

batch.Complete();

yield return batch;
}
else
Expand All @@ -118,7 +125,10 @@ public virtual IEnumerable<ModificationCommandBatch> BatchCommands(

foreach (var command in batch.ModificationCommands)
{
yield return StartNewBatch(parameterNameGenerator, command);
batch = StartNewBatch(parameterNameGenerator, command);
batch.Complete();

yield return batch;
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore.Relational/Update/ModificationCommandBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ public abstract class ModificationCommandBatch
/// </returns>
public abstract bool AddCommand(IReadOnlyModificationCommand modificationCommand);

/// <summary>
/// Indicates that no more commands will be added to this batch, and prepares it for execution.
/// </summary>
public abstract void Complete();

/// <summary>
/// Indicates whether the batch requires a transaction in order to execute correctly.
/// </summary>
public abstract bool RequiresTransaction { get; }

/// <summary>
/// Sends insert/update/delete commands to the database.
/// </summary>
Expand Down
Loading

0 comments on commit 499d454

Please sign in to comment.