Skip to content

Commit

Permalink
Expose async transaction commit/rollback APIs
Browse files Browse the repository at this point in the history
Closes #20694
  • Loading branch information
roji committed Apr 20, 2020
1 parent eb30f2c commit 1b25fb9
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 32 deletions.
10 changes: 5 additions & 5 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ public virtual void Commit()
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void Rollback()
{
}
public virtual Task CommitAsync(CancellationToken cancellationToken = default)
=> Task.CompletedTask;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Task CommitAsync(CancellationToken cancellationToken = default)
=> Task.CompletedTask;
public virtual void Rollback()
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
24 changes: 24 additions & 0 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ public virtual Task<IDbContextTransaction> BeginTransactionAsync(
/// </summary>
public virtual void CommitTransaction() => _logger.TransactionIgnoredWarning();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default)
{
_logger.TransactionIgnoredWarning();
return Task.CompletedTask;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -90,6 +102,18 @@ public virtual Task<IDbContextTransaction> BeginTransactionAsync(
/// </summary>
public virtual void RollbackTransaction() => _logger.TransactionIgnoredWarning();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
{
_logger.TransactionIgnoredWarning();
return Task.CompletedTask;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,18 @@ public virtual async Task ExecuteNonQueryAsync(
if (transaction != null
&& command.TransactionSuppressed)
{
transaction.Commit();
await transaction.CommitAsync(cancellationToken);
await transaction.DisposeAsync();
transaction = null;
}

await command.ExecuteNonQueryAsync(connection, cancellationToken: cancellationToken);
}

transaction?.Commit();
if (transaction != null)
{
await transaction.CommitAsync(cancellationToken);
}
}
finally
{
Expand Down
32 changes: 31 additions & 1 deletion src/EFCore.Relational/Storage/RelationalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ public virtual IDbContextTransaction UseTransaction(DbTransaction transaction)
/// Specifies an existing <see cref="DbTransaction" /> to be used for database operations.
/// </summary>
/// <param name="transaction"> The transaction to be used. </param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <param name="cancellationToken"> A <see cref="CancellationToken" /> to observe while waiting for the task to complete. </param>
/// <returns> An instance of <see cref="IDbTransaction" /> that wraps the provided transaction. </returns>
public virtual async Task<IDbContextTransaction> UseTransactionAsync(
DbTransaction transaction,
Expand Down Expand Up @@ -466,6 +466,21 @@ public virtual void CommitTransaction()
CurrentTransaction.Commit();
}

/// <summary>
/// Commits all changes made to the database in the current transaction.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns> A Task representing the asynchronous operation. </returns>
public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default)
{
if (CurrentTransaction == null)
{
throw new InvalidOperationException(RelationalStrings.NoActiveTransaction);
}

return CurrentTransaction.CommitAsync(cancellationToken);
}

/// <summary>
/// Discards all changes made to the database in the current transaction.
/// </summary>
Expand All @@ -479,6 +494,21 @@ public virtual void RollbackTransaction()
CurrentTransaction.Rollback();
}

/// <summary>
/// Discards all changes made to the database in the current transaction.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns> A Task representing the asynchronous operation. </returns>
public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
{
if (CurrentTransaction == null)
{
throw new InvalidOperationException(RelationalStrings.NoActiveTransaction);
}

return CurrentTransaction.RollbackAsync(cancellationToken);
}

/// <summary>
/// Opens the connection to the database.
/// </summary>
Expand Down
5 changes: 4 additions & 1 deletion src/EFCore.Relational/Update/Internal/BatchExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ public virtual async Task<int> ExecuteAsync(
rowsAffected += batch.ModificationCommands.Count;
}

startedTransaction?.Commit();
if (startedTransaction != null)
{
await startedTransaction.CommitAsync(cancellationToken);
}
}
finally
{
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore/Infrastructure/DatabaseFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,28 @@ public virtual Task<IDbContextTransaction> BeginTransactionAsync(CancellationTok
public virtual void CommitTransaction()
=> Dependencies.TransactionManager.CommitTransaction();

/// <summary>
/// Applies the outstanding operations in the current transaction to the database.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns> A Task representing the asynchronous operation. </returns>
public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default)
=> Dependencies.TransactionManager.CommitTransactionAsync(cancellationToken);

/// <summary>
/// Discards the outstanding operations in the current transaction.
/// </summary>
public virtual void RollbackTransaction()
=> Dependencies.TransactionManager.RollbackTransaction();

/// <summary>
/// Applies the outstanding operations in the current transaction to the database.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns> A Task representing the asynchronous operation. </returns>
public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
=> Dependencies.TransactionManager.RollbackTransactionAsync(cancellationToken);

/// <summary>
/// Creates an instance of the configured <see cref="IExecutionStrategy" />.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Storage/ExecutionStrategyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ public static Task<TResult> ExecuteInTransactionAsync<TState, TResult>(
s.CommitFailed = false;
s.Result = await s.Operation(s.State, ct);
s.CommitFailed = true;
transaction.Commit();
await transaction.CommitAsync(cancellationToken);
}
return s.Result;
Expand Down
10 changes: 5 additions & 5 deletions src/EFCore/Storage/IDbContextTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ public interface IDbContextTransaction : IDisposable, IAsyncDisposable
/// </summary>
void Commit();

/// <summary>
/// Discards all changes made to the database in the current transaction.
/// </summary>
void Rollback();

/// <summary>
/// Commits all changes made to the database in the current transaction asynchronously.
/// </summary>
/// <param name="cancellationToken"> The cancellation token. </param>
/// <returns> A <see cref="Task" /> representing the asynchronous operation. </returns>
Task CommitAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Discards all changes made to the database in the current transaction.
/// </summary>
void Rollback();

/// <summary>
/// Discards all changes made to the database in the current transaction asynchronously.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/EFCore/Storage/IDbContextTransactionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,29 @@ public interface IDbContextTransactionManager : IResettableService
/// </summary>
void CommitTransaction();

/// <summary>
/// Commits all changes made to the database in the current transaction.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// </returns>
Task CommitTransactionAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Discards all changes made to the database in the current transaction.
/// </summary>
void RollbackTransaction();

/// <summary>
/// Discards all changes made to the database in the current transaction.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// </returns>
Task RollbackTransactionAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Gets the current transaction.
/// </summary>
Expand Down
24 changes: 12 additions & 12 deletions test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,27 @@ public void CurrentTransaction_returns_null()

[ConditionalFact]
public void Throws_on_BeginTransaction()
{
AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).BeginTransaction());
}
=> AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).BeginTransaction());

[ConditionalFact]
public void Throws_on_BeginTransactionAsync()
{
AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).BeginTransactionAsync().GetAwaiter().GetResult());
}
=> AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).BeginTransactionAsync().GetAwaiter().GetResult());

[ConditionalFact]
public void Throws_on_CommitTransaction()
{
AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).CommitTransaction());
}
=> AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).CommitTransaction());

[ConditionalFact]
public void Throws_on_CommitTransactionAsync()
=> AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).CommitTransactionAsync().GetAwaiter().GetResult());

[ConditionalFact]
public void Throws_on_RollbackTransaction()
{
AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).RollbackTransaction());
}
=> AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).RollbackTransaction());

[ConditionalFact]
public void Throws_on_RollbackTransactionAsync()
=> AssertThrows(() => new InMemoryTransactionManager(CreateLogger()).RollbackTransactionAsync().GetAwaiter().GetResult());

private static void AssertThrows(Action action)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ public virtual async Task RelationalTransaction_can_be_rolled_back(bool autoTran
{
context.Entry(context.Set<TransactionCustomer>().OrderBy(c => c.Id).First()).State = EntityState.Deleted;
await context.SaveChangesAsync();
transaction.Rollback();
await transaction.RollbackAsync();

AssertStoreInitialState();
}
Expand All @@ -877,7 +877,7 @@ public virtual async Task RelationalTransaction_can_be_rolled_back_from_context(
{
context.Entry(context.Set<TransactionCustomer>().OrderBy(c => c.Id).First()).State = EntityState.Deleted;
await context.SaveChangesAsync();
context.Database.RollbackTransaction();
await context.Database.RollbackTransactionAsync();

AssertStoreInitialState();
}
Expand Down
58 changes: 56 additions & 2 deletions test/EFCore.Relational.Tests/RelationalConnectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ public void Can_use_existing_transaction()
}

[ConditionalFact]
public void Commit_calls_commit_on_DbTransaction()
public void Commit_calls_Commit_on_DbTransaction()
{
using var connection = new FakeRelationalConnection(
CreateOptions(new FakeRelationalOptionsExtension().WithConnectionString("Database=FrodoLives")));
Expand All @@ -809,7 +809,34 @@ public void Commit_calls_commit_on_DbTransaction()
}

[ConditionalFact]
public void Rollback_calls_rollback_on_DbTransaction()
public async Task CommitAsync_calls_CommitAsync_on_DbTransaction()
{
using var connection = new FakeRelationalConnection(
CreateOptions(new FakeRelationalOptionsExtension().WithConnectionString("Database=FrodoLives")));
Assert.Equal(0, connection.DbConnections.Count);

Assert.Null(connection.CurrentTransaction);

using (var transaction = await connection.BeginTransactionAsync())
{
Assert.Same(transaction, connection.CurrentTransaction);

Assert.Equal(1, connection.DbConnections.Count);
var dbConnection = connection.DbConnections[0];

Assert.Equal(1, dbConnection.DbTransactions.Count);
var dbTransaction = dbConnection.DbTransactions[0];

await connection.CommitTransactionAsync();

Assert.Equal(1, dbTransaction.CommitCount);
}

Assert.Null(connection.CurrentTransaction);
}

[ConditionalFact]
public void Rollback_calls_Rollback_on_DbTransaction()
{
using var connection = new FakeRelationalConnection(
CreateOptions(new FakeRelationalOptionsExtension().WithConnectionString("Database=FrodoLives")));
Expand All @@ -835,6 +862,33 @@ public void Rollback_calls_rollback_on_DbTransaction()
Assert.Null(connection.CurrentTransaction);
}

[ConditionalFact]
public async Task Rollback_calls_RollbackAsync_on_DbTransaction()
{
using var connection = new FakeRelationalConnection(
CreateOptions(new FakeRelationalOptionsExtension().WithConnectionString("Database=FrodoLives")));
Assert.Equal(0, connection.DbConnections.Count);

Assert.Null(connection.CurrentTransaction);

using (var transaction = await connection.BeginTransactionAsync())
{
Assert.Same(transaction, connection.CurrentTransaction);

Assert.Equal(1, connection.DbConnections.Count);
var dbConnection = connection.DbConnections[0];

Assert.Equal(1, dbConnection.DbTransactions.Count);
var dbTransaction = dbConnection.DbTransactions[0];

await connection.RollbackTransactionAsync();

Assert.Equal(1, dbTransaction.RollbackCount);
}

Assert.Null(connection.CurrentTransaction);
}

[ConditionalFact]
public void Can_create_new_connection_with_CommandTimeout()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ public void RollbackTransaction()
{
}

public Task CommitTransactionAsync(CancellationToken cancellationToken = default)
=> Task.CompletedTask;

public Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
=> Task.CompletedTask;

public IDbContextTransaction CurrentTransaction { get; }

public Transaction EnlistedTransaction { get; }
Expand Down
Loading

0 comments on commit 1b25fb9

Please sign in to comment.