From 1e977aeca516d72ebb94e2968051ff9d0c8490c6 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 21 Apr 2020 12:50:06 +0200 Subject: [PATCH] Expose async transaction commit/rollback APIs (#20695) Closes #20694 --- .../Internal/CosmosTransactionManager.cs | 18 ++++++ .../Storage/Internal/InMemoryTransaction.cs | 10 ++-- .../Internal/InMemoryTransactionManager.cs | 24 ++++++++ .../Internal/MigrationCommandExecutor.cs | 7 ++- .../Storage/RelationalConnection.cs | 32 +++++++++- .../Update/Internal/BatchExecutor.cs | 5 +- src/EFCore/Infrastructure/DatabaseFacade.cs | 16 +++++ .../Storage/ExecutionStrategyExtensions.cs | 2 +- src/EFCore/Storage/IDbContextTransaction.cs | 10 ++-- .../Storage/IDbContextTransactionManager.cs | 18 ++++++ .../InMemoryTransactionManagerTest.cs | 24 ++++---- .../TransactionTestBase.cs | 4 +- .../RelationalConnectionTest.cs | 58 ++++++++++++++++++- .../RelationalDatabaseFacadeExtensionsTest.cs | 6 ++ .../RelationalEventIdTest.cs | 2 + .../TestRelationalTransaction.cs | 26 +++++++++ .../Query/BadDataSqliteTest.cs | 12 ++-- test/EFCore.Tests/DatabaseFacadeTest.cs | 42 +++++++++++++- .../TestInMemoryTransactionManager.cs | 6 ++ 19 files changed, 284 insertions(+), 38 deletions(-) diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosTransactionManager.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosTransactionManager.cs index 20c74304184..cf10358d87f 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosTransactionManager.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosTransactionManager.cs @@ -43,6 +43,15 @@ public virtual Task BeginTransactionAsync( /// public virtual void CommitTransaction() => throw new NotSupportedException(); + /// + /// 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. + /// + public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default) + => throw new NotSupportedException(); + /// /// 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 @@ -51,6 +60,15 @@ public virtual Task BeginTransactionAsync( /// public virtual void RollbackTransaction() => throw new NotSupportedException(); + /// + /// 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. + /// + public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + => throw new NotSupportedException(); + /// /// 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 diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryTransaction.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryTransaction.cs index 99f366b9da4..f9b7530e966 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryTransaction.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryTransaction.cs @@ -40,9 +40,8 @@ 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. /// - public virtual void Rollback() - { - } + public virtual Task CommitAsync(CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -50,8 +49,9 @@ public virtual void Rollback() /// 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. /// - public virtual Task CommitAsync(CancellationToken cancellationToken = default) - => Task.CompletedTask; + public virtual void Rollback() + { + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs index d865d3487cf..77f393e5c86 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs @@ -82,6 +82,18 @@ public virtual Task BeginTransactionAsync( /// public virtual void CommitTransaction() => _logger.TransactionIgnoredWarning(); + /// + /// 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. + /// + public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default) + { + _logger.TransactionIgnoredWarning(); + return Task.CompletedTask; + } + /// /// 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 @@ -90,6 +102,18 @@ public virtual Task BeginTransactionAsync( /// public virtual void RollbackTransaction() => _logger.TransactionIgnoredWarning(); + /// + /// 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. + /// + public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + { + _logger.TransactionIgnoredWarning(); + return Task.CompletedTask; + } + /// /// 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 diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs b/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs index 32dc3b2fe6a..2b8552b77f5 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs @@ -118,7 +118,7 @@ public virtual async Task ExecuteNonQueryAsync( if (transaction != null && command.TransactionSuppressed) { - transaction.Commit(); + await transaction.CommitAsync(cancellationToken); await transaction.DisposeAsync(); transaction = null; } @@ -126,7 +126,10 @@ public virtual async Task ExecuteNonQueryAsync( await command.ExecuteNonQueryAsync(connection, cancellationToken: cancellationToken); } - transaction?.Commit(); + if (transaction != null) + { + await transaction.CommitAsync(cancellationToken); + } } finally { diff --git a/src/EFCore.Relational/Storage/RelationalConnection.cs b/src/EFCore.Relational/Storage/RelationalConnection.cs index 69de2582923..2a28fcac086 100644 --- a/src/EFCore.Relational/Storage/RelationalConnection.cs +++ b/src/EFCore.Relational/Storage/RelationalConnection.cs @@ -410,7 +410,7 @@ public virtual IDbContextTransaction UseTransaction(DbTransaction transaction) /// Specifies an existing to be used for database operations. /// /// The transaction to be used. - /// A to observe while waiting for the task to complete. + /// A to observe while waiting for the task to complete. /// An instance of that wraps the provided transaction. public virtual async Task UseTransactionAsync( DbTransaction transaction, @@ -466,6 +466,21 @@ public virtual void CommitTransaction() CurrentTransaction.Commit(); } + /// + /// Commits all changes made to the database in the current transaction. + /// + /// A to observe while waiting for the task to complete. + /// A Task representing the asynchronous operation. + public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default) + { + if (CurrentTransaction == null) + { + throw new InvalidOperationException(RelationalStrings.NoActiveTransaction); + } + + return CurrentTransaction.CommitAsync(cancellationToken); + } + /// /// Discards all changes made to the database in the current transaction. /// @@ -479,6 +494,21 @@ public virtual void RollbackTransaction() CurrentTransaction.Rollback(); } + /// + /// Discards all changes made to the database in the current transaction. + /// + /// A to observe while waiting for the task to complete. + /// A Task representing the asynchronous operation. + public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + { + if (CurrentTransaction == null) + { + throw new InvalidOperationException(RelationalStrings.NoActiveTransaction); + } + + return CurrentTransaction.RollbackAsync(cancellationToken); + } + /// /// Opens the connection to the database. /// diff --git a/src/EFCore.Relational/Update/Internal/BatchExecutor.cs b/src/EFCore.Relational/Update/Internal/BatchExecutor.cs index 3821d8bc2ff..1f0b4809152 100644 --- a/src/EFCore.Relational/Update/Internal/BatchExecutor.cs +++ b/src/EFCore.Relational/Update/Internal/BatchExecutor.cs @@ -129,7 +129,10 @@ public virtual async Task ExecuteAsync( rowsAffected += batch.ModificationCommands.Count; } - startedTransaction?.Commit(); + if (startedTransaction != null) + { + await startedTransaction.CommitAsync(cancellationToken); + } } finally { diff --git a/src/EFCore/Infrastructure/DatabaseFacade.cs b/src/EFCore/Infrastructure/DatabaseFacade.cs index 896aea7c5f7..05743f8cb75 100644 --- a/src/EFCore/Infrastructure/DatabaseFacade.cs +++ b/src/EFCore/Infrastructure/DatabaseFacade.cs @@ -160,12 +160,28 @@ public virtual Task BeginTransactionAsync(CancellationTok public virtual void CommitTransaction() => Dependencies.TransactionManager.CommitTransaction(); + /// + /// Applies the outstanding operations in the current transaction to the database. + /// + /// A to observe while waiting for the task to complete. + /// A Task representing the asynchronous operation. + public virtual Task CommitTransactionAsync(CancellationToken cancellationToken = default) + => Dependencies.TransactionManager.CommitTransactionAsync(cancellationToken); + /// /// Discards the outstanding operations in the current transaction. /// public virtual void RollbackTransaction() => Dependencies.TransactionManager.RollbackTransaction(); + /// + /// Applies the outstanding operations in the current transaction to the database. + /// + /// A to observe while waiting for the task to complete. + /// A Task representing the asynchronous operation. + public virtual Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + => Dependencies.TransactionManager.RollbackTransactionAsync(cancellationToken); + /// /// Creates an instance of the configured . /// diff --git a/src/EFCore/Storage/ExecutionStrategyExtensions.cs b/src/EFCore/Storage/ExecutionStrategyExtensions.cs index 6acf6127731..54f57eaa003 100644 --- a/src/EFCore/Storage/ExecutionStrategyExtensions.cs +++ b/src/EFCore/Storage/ExecutionStrategyExtensions.cs @@ -734,7 +734,7 @@ public static Task ExecuteInTransactionAsync( s.CommitFailed = false; s.Result = await s.Operation(s.State, ct); s.CommitFailed = true; - transaction.Commit(); + await transaction.CommitAsync(cancellationToken); } return s.Result; diff --git a/src/EFCore/Storage/IDbContextTransaction.cs b/src/EFCore/Storage/IDbContextTransaction.cs index 549c30bb471..9a03a219753 100644 --- a/src/EFCore/Storage/IDbContextTransaction.cs +++ b/src/EFCore/Storage/IDbContextTransaction.cs @@ -29,11 +29,6 @@ public interface IDbContextTransaction : IDisposable, IAsyncDisposable /// void Commit(); - /// - /// Discards all changes made to the database in the current transaction. - /// - void Rollback(); - /// /// Commits all changes made to the database in the current transaction asynchronously. /// @@ -41,6 +36,11 @@ public interface IDbContextTransaction : IDisposable, IAsyncDisposable /// A representing the asynchronous operation. Task CommitAsync(CancellationToken cancellationToken = default); + /// + /// Discards all changes made to the database in the current transaction. + /// + void Rollback(); + /// /// Discards all changes made to the database in the current transaction asynchronously. /// diff --git a/src/EFCore/Storage/IDbContextTransactionManager.cs b/src/EFCore/Storage/IDbContextTransactionManager.cs index 51759c2a2eb..ffd36a8bd50 100644 --- a/src/EFCore/Storage/IDbContextTransactionManager.cs +++ b/src/EFCore/Storage/IDbContextTransactionManager.cs @@ -45,11 +45,29 @@ public interface IDbContextTransactionManager : IResettableService /// void CommitTransaction(); + /// + /// Commits all changes made to the database in the current transaction. + /// + /// A to observe while waiting for the task to complete. + /// + /// A task that represents the asynchronous operation. + /// + Task CommitTransactionAsync(CancellationToken cancellationToken = default); + /// /// Discards all changes made to the database in the current transaction. /// void RollbackTransaction(); + /// + /// Discards all changes made to the database in the current transaction. + /// + /// A to observe while waiting for the task to complete. + /// + /// A task that represents the asynchronous operation. + /// + Task RollbackTransactionAsync(CancellationToken cancellationToken = default); + /// /// Gets the current transaction. /// diff --git a/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs b/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs index 4891df9f008..23e8a134e6f 100644 --- a/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs @@ -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) { diff --git a/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs b/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs index 6a512fcd87a..f0ec2cdf575 100644 --- a/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs @@ -857,7 +857,7 @@ public virtual async Task RelationalTransaction_can_be_rolled_back(bool autoTran { context.Entry(context.Set().OrderBy(c => c.Id).First()).State = EntityState.Deleted; await context.SaveChangesAsync(); - transaction.Rollback(); + await transaction.RollbackAsync(); AssertStoreInitialState(); } @@ -877,7 +877,7 @@ public virtual async Task RelationalTransaction_can_be_rolled_back_from_context( { context.Entry(context.Set().OrderBy(c => c.Id).First()).State = EntityState.Deleted; await context.SaveChangesAsync(); - context.Database.RollbackTransaction(); + await context.Database.RollbackTransactionAsync(); AssertStoreInitialState(); } diff --git a/test/EFCore.Relational.Tests/RelationalConnectionTest.cs b/test/EFCore.Relational.Tests/RelationalConnectionTest.cs index dcec8e0b6e0..a363cd4bbb2 100644 --- a/test/EFCore.Relational.Tests/RelationalConnectionTest.cs +++ b/test/EFCore.Relational.Tests/RelationalConnectionTest.cs @@ -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"))); @@ -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"))); @@ -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() { diff --git a/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs b/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs index 8c6d05b7863..a09d6168661 100644 --- a/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs @@ -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; } diff --git a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs index 848892763f7..82b1b853b6b 100644 --- a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs @@ -157,6 +157,7 @@ public Task BeginTransactionAsync(CancellationToken cance public bool Close() => throw new NotImplementedException(); public void CommitTransaction() => throw new NotImplementedException(); + public Task CommitTransactionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); public void Dispose() => throw new NotImplementedException(); public bool Open(bool errorsExpected = false) => throw new NotImplementedException(); @@ -167,6 +168,7 @@ public Task OpenAsync(CancellationToken cancellationToken, bool errorsExpe public Task ResetStateAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); public void RollbackTransaction() => throw new NotImplementedException(); + public Task RollbackTransactionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); public IDbContextTransaction UseTransaction(DbTransaction transaction) => throw new NotImplementedException(); public Task UseTransactionAsync( diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalTransaction.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalTransaction.cs index 908d06cd7c2..e7d25f4dfc0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalTransaction.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalTransaction.cs @@ -3,6 +3,8 @@ using System; using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Storage; @@ -56,5 +58,29 @@ public override void Commit() base.Commit(); } + + public override async Task CommitAsync(CancellationToken cancellationToken = default) + { + if (_testConnection.CommitFailures.Count > 0) + { + var fail = _testConnection.CommitFailures.Dequeue(); + if (fail.HasValue) + { + if (fail.Value) + { + await this.GetDbTransaction().RollbackAsync(cancellationToken); + } + else + { + await this.GetDbTransaction().CommitAsync(cancellationToken); + } + + await _testConnection.DbConnection.CloseAsync(); + throw SqlExceptionFactory.CreateSqlException(_testConnection.ErrorNumber, _testConnection.ConnectionId); + } + } + + await base.CommitAsync(cancellationToken); + } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs index 4891d8b4702..f839b8a69d4 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs @@ -345,13 +345,13 @@ public void ResetState() public Task BeginTransactionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public void CommitTransaction() - { - } + public void CommitTransaction() { } + public Task CommitTransactionAsync(CancellationToken cancellationToken = default) + => Task.CompletedTask; - public void RollbackTransaction() - { - } + public void RollbackTransaction() { } + public Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + => Task.CompletedTask; public IDbContextTransaction CurrentTransaction => throw new NotImplementedException(); public SemaphoreSlim Semaphore { get; } diff --git a/test/EFCore.Tests/DatabaseFacadeTest.cs b/test/EFCore.Tests/DatabaseFacadeTest.cs index 89d23c1cfe3..a5434c26cc7 100644 --- a/test/EFCore.Tests/DatabaseFacadeTest.cs +++ b/test/EFCore.Tests/DatabaseFacadeTest.cs @@ -159,7 +159,21 @@ public Task BeginTransactionAsync(CancellationToken cance => Task.FromResult(_transaction); public void CommitTransaction() => CommitCalls++; + + public Task CommitTransactionAsync(CancellationToken cancellationToken = default) + { + CommitCalls++; + return Task.CompletedTask; + } + public void RollbackTransaction() => RollbackCalls++; + + public Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + { + RollbackCalls++; + return Task.CompletedTask; + } + public IDbContextTransaction CurrentTransaction => _transaction; public Transaction EnlistedTransaction { get; } public void EnlistTransaction(Transaction transaction) => throw new NotImplementedException(); @@ -174,8 +188,8 @@ private class FakeDbContextTransaction : IDbContextTransaction public ValueTask DisposeAsync() => throw new NotImplementedException(); public Guid TransactionId { get; } public void Commit() => throw new NotImplementedException(); - public void Rollback() => throw new NotImplementedException(); public Task CommitAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public void Rollback() => throw new NotImplementedException(); public Task RollbackAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); } @@ -192,6 +206,19 @@ public void Can_commit_transaction() Assert.Equal(1, manager.CommitCalls); } + [ConditionalFact] + public async Task Can_commit_transaction_async() + { + var manager = new FakeDbContextTransactionManager(new FakeDbContextTransaction()); + + var context = InMemoryTestHelpers.Instance.CreateContext( + new ServiceCollection().AddSingleton(manager)); + + await context.Database.CommitTransactionAsync(); + + Assert.Equal(1, manager.CommitCalls); + } + [ConditionalFact] public void Can_roll_back_transaction() { @@ -205,6 +232,19 @@ public void Can_roll_back_transaction() Assert.Equal(1, manager.RollbackCalls); } + [ConditionalFact] + public async Task Can_roll_back_transaction_async() + { + var manager = new FakeDbContextTransactionManager(new FakeDbContextTransaction()); + + var context = InMemoryTestHelpers.Instance.CreateContext( + new ServiceCollection().AddSingleton(manager)); + + await context.Database.RollbackTransactionAsync(); + + Assert.Equal(1, manager.RollbackCalls); + } + [ConditionalFact] public void Can_get_current_transaction() { diff --git a/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs b/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs index 848cd3455c8..94e98de7f0f 100644 --- a/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs +++ b/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs @@ -33,8 +33,14 @@ public override Task BeginTransactionAsync(CancellationTo public override void CommitTransaction() => CurrentTransaction.Commit(); + public override Task CommitTransactionAsync(CancellationToken cancellationToken = default) + => CurrentTransaction.CommitAsync(cancellationToken); + public override void RollbackTransaction() => CurrentTransaction.Rollback(); + public override Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + => CurrentTransaction.RollbackAsync(cancellationToken); + public override void EnlistTransaction(Transaction transaction) => _enlistedTransaction = transaction; private class TestInMemoryTransaction : IDbContextTransaction