Skip to content

Commit c913c55

Browse files
authored
Transaction async commit & Rollback (#103)
* Transaction async commit & Rollback * Add cancelation token to ExecuteInTransaction * Token to action
1 parent 20a19b3 commit c913c55

File tree

8 files changed

+152
-23
lines changed

8 files changed

+152
-23
lines changed

src/Data/ITransaction.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
24

35
namespace Kros.KORM.Data
46
{
@@ -13,11 +15,21 @@ public interface ITransaction : IDisposable
1315
/// </summary>
1416
void Commit();
1517

18+
/// <summary>
19+
/// Async commits all changes made to the database in the current transaction.
20+
/// </summary>
21+
Task CommitAsync(CancellationToken cancellationToken = default);
22+
1623
/// <summary>
1724
/// Discards all changes made to the database in the current transaction.
1825
/// </summary>
1926
void Rollback();
2027

28+
/// <summary>
29+
/// Async discards all changes made to the database in the current transaction.
30+
/// </summary>
31+
Task RollbackAsync(CancellationToken cancellationToken = default);
32+
2133
/// <summary>
2234
/// The time in seconds to wait for the <see cref="System.Data.Common.DbCommand.CommandTimeout">command</see> in this transaction to execute.
2335
/// If not set, default value (30 s) will be used.

src/Data/TransactionHelper.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Data;
55
using System.Data.Common;
66
using System.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
79

810
namespace Kros.KORM.Data
911
{
@@ -37,19 +39,45 @@ public Transaction(TransactionHelper transactionHelper, ConnectionHelper connect
3739
}
3840

3941
public void Commit()
42+
=> OnCommit(false).GetAwaiter().GetResult();
43+
44+
public Task CommitAsync(CancellationToken cancellationToken = default)
45+
=> OnCommit(true, cancellationToken);
46+
47+
private async Task OnCommit(bool useAsync, CancellationToken cancellationToken = default)
4048
{
4149
_wasCommitOrRollback = true;
4250
if (_transactionHelper.CanCommitTransaction)
4351
{
44-
_transaction.Value.Commit();
52+
if (useAsync)
53+
{
54+
await _transaction.Value.CommitAsync(cancellationToken);
55+
}
56+
else
57+
{
58+
_transaction.Value.Commit();
59+
}
4560
_transactionHelper.EndTransaction(true);
4661
}
4762
}
4863

4964
public void Rollback()
65+
=> OnRollback(false).GetAwaiter().GetResult();
66+
67+
public Task RollbackAsync(CancellationToken cancellationToken = default)
68+
=> OnRollback(true, cancellationToken);
69+
70+
private async Task OnRollback(bool useAsync, CancellationToken cancellationToken = default)
5071
{
5172
_wasCommitOrRollback = true;
52-
_transaction.Value.Rollback();
73+
if (useAsync)
74+
{
75+
_transaction.Value.Rollback();
76+
}
77+
else
78+
{
79+
await _transaction.Value.RollbackAsync(cancellationToken);
80+
}
5381
_transactionHelper.EndTransaction(false);
5482
}
5583

@@ -105,6 +133,18 @@ public void Rollback()
105133
_wasCommitOrRollback = true;
106134
}
107135

136+
public Task CommitAsync(CancellationToken cancellationToken = default)
137+
{
138+
Commit();
139+
return Task.CompletedTask;
140+
}
141+
142+
public Task RollbackAsync(CancellationToken cancellationToken = default)
143+
{
144+
Rollback();
145+
return Task.CompletedTask;
146+
}
147+
108148
public int CommandTimeout
109149
{
110150
get => _timeout;

src/Kros.KORM.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netcoreapp3.1;net46</TargetFrameworks>
5-
<Version>5.1.1</Version>
4+
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
5+
<Version>6.0.0</Version>
66
<Authors>KROS a. s.</Authors>
77
<Company>KROS a. s.</Company>
88
<Description>KORM is fast, easy to use, micro ORM tool (Kros Object Relation Mapper).</Description>

src/Query/DbSet.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -355,17 +355,17 @@ private async Task CommitChangesCoreAsync(
355355
bool ignoreValueGenerators,
356356
CancellationToken cancellationToken = default)
357357
{
358-
await _provider.ExecuteInTransactionAsync(async () =>
358+
await _provider.ExecuteInTransactionAsync(async (token) =>
359359
{
360-
await CommitChangesAddedItemsAsync(_addedItems, useAsync, ignoreValueGenerators, cancellationToken);
361-
await CommitChangesEditedItemsAsync(_editedItems, useAsync, ignoreValueGenerators, cancellationToken);
362-
await CommitChangesUpsertedItemsAsync(_upsertedItems, useAsync, cancellationToken);
363-
await CommitChangesDeletedItemsAsync(_deletedItems, useAsync, cancellationToken);
364-
await CommitChangesDeletedItemsByIdAsync(_deletedItemsIds, useAsync, cancellationToken);
365-
await CommitChangesDeletedByConditionsAsync(_deleteExpressions, useAsync, cancellationToken);
360+
await CommitChangesAddedItemsAsync(_addedItems, useAsync, ignoreValueGenerators, token);
361+
await CommitChangesEditedItemsAsync(_editedItems, useAsync, ignoreValueGenerators, token);
362+
await CommitChangesUpsertedItemsAsync(_upsertedItems, useAsync, token);
363+
await CommitChangesDeletedItemsAsync(_deletedItems, useAsync, token);
364+
await CommitChangesDeletedItemsByIdAsync(_deletedItemsIds, useAsync, token);
365+
await CommitChangesDeletedByConditionsAsync(_deleteExpressions, useAsync, token);
366366

367367
Clear();
368-
});
368+
}, cancellationToken);
369369
}
370370

371371
/// <summary>

src/Query/Providers/IQueryProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ public interface IQueryProvider : System.Linq.IQueryProvider, IDisposable
6868
/// Asynchronously executes action in transaction.
6969
/// </summary>
7070
/// <param name="action">Action which will be executed.</param>
71+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
7172
/// <returns>
7273
/// A task that represents the asynchronous operation.
7374
/// </returns>
74-
Task ExecuteInTransactionAsync(Func<Task> action);
75+
Task ExecuteInTransactionAsync(Func<CancellationToken, Task> action, CancellationToken cancellationToken = default);
7576

7677
/// <summary>
7778
/// Executes the command.

src/Query/Providers/QueryProvider.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,19 +278,19 @@ public async Task<object> ExecuteScalarCommandAsync(DbCommand command, Cancellat
278278
}
279279

280280
/// <inheritdoc/>
281-
public async Task ExecuteInTransactionAsync(Func<Task> action)
281+
public async Task ExecuteInTransactionAsync(Func<CancellationToken, Task> action, CancellationToken cancellationToken = default)
282282
{
283283
using (OpenConnection())
284284
using (var transaction = _transactionHelper.Value.BeginTransaction())
285285
{
286286
try
287287
{
288-
await action();
289-
transaction.Commit();
288+
await action(cancellationToken);
289+
await transaction.CommitAsync(cancellationToken);
290290
}
291291
catch
292292
{
293-
transaction.Rollback();
293+
await transaction.RollbackAsync(cancellationToken);
294294
throw;
295295
}
296296
}

tests/Kros.KORM.UnitTests/Integration/TransactionTests.cs

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Collections.Generic;
88
using System.Data;
9+
using System.Threading.Tasks;
910
using Xunit;
1011

1112
namespace Kros.KORM.UnitTests.Integration
@@ -195,6 +196,40 @@ private void ExplicitTransactionCommitDataAfterOtherTransactionEndWithRollback(T
195196
}
196197
}
197198

199+
[Theory]
200+
[InlineData(true)]
201+
[InlineData(false)]
202+
public async Task ExplicitTransactionShould_CommitDataAfterOtherTransactionEndWithRollbackAsync(bool openConnection)
203+
{
204+
await DoTestWithConnectionAsync(openConnection, ExplicitTransactionCommitDataAfterOtherTransactionEndWithRollbackAsync, CreateDatabase);
205+
}
206+
207+
private async Task ExplicitTransactionCommitDataAfterOtherTransactionEndWithRollbackAsync(TestDatabase korm)
208+
{
209+
using (var transaction = korm.BeginTransaction())
210+
{
211+
var dbSet = korm.Query<Invoice>().AsDbSet();
212+
213+
dbSet.Add(CreateTestData());
214+
dbSet.CommitChanges();
215+
216+
await transaction.RollbackAsync();
217+
218+
DatabaseShouldBeEmpty(korm);
219+
}
220+
using (var transaction = korm.BeginTransaction())
221+
{
222+
var dbSet = korm.Query<Invoice>().AsDbSet();
223+
224+
dbSet.Add(CreateTestData());
225+
dbSet.CommitChanges();
226+
227+
await transaction.CommitAsync();
228+
229+
DatabaseShouldContainInvoices(korm.ConnectionString, CreateTestData());
230+
}
231+
}
232+
198233
[Theory]
199234
[InlineData(true)]
200235
[InlineData(false)]
@@ -369,6 +404,30 @@ private void ExplicitTransactionCommit(TestDatabase database)
369404
}
370405
}
371406

407+
[Theory]
408+
[InlineData(true)]
409+
[InlineData(false)]
410+
public async Task ExplicitTransactionShould_KeepMasterConnectionStateWhenCommitWasCalledAsync(bool openConnection)
411+
{
412+
await DoTestWithConnectionAsync(openConnection, ExplicitTransactionCommitAsync, CreateDatabase);
413+
}
414+
415+
private async Task ExplicitTransactionCommitAsync(TestDatabase database)
416+
{
417+
using (var korm = new Database(database.ConnectionString))
418+
using (var transaction = korm.BeginTransaction())
419+
{
420+
var dbSet = korm.Query<Invoice>().AsDbSet();
421+
422+
dbSet.Add(CreateTestData());
423+
dbSet.CommitChanges();
424+
425+
await transaction.CommitAsync();
426+
427+
DatabaseShouldContainInvoices(database.ConnectionString, CreateTestData());
428+
}
429+
}
430+
372431
[Theory]
373432
[InlineData(true)]
374433
[InlineData(false)]
@@ -509,17 +568,34 @@ public void ExplicitTransactionShould_NotThrowCommandTimeoutExceptionWhenIsSetSu
509568

510569
#region Helpers
511570

512-
private void DoTestWithConnection(
571+
private static void DoTestWithConnection(
513572
bool openConnection,
514573
Action<TestDatabase> testAction,
515574
Func<TestDatabase> createDatabaseAction)
516575
{
517-
using (var database = createDatabaseAction())
576+
using TestDatabase database = createDatabaseAction();
577+
if (openConnection)
578+
{
579+
database.Connection.Open();
580+
}
581+
582+
testAction(database);
583+
database.Connection.State.Should().Be(openConnection ? ConnectionState.Open : ConnectionState.Closed);
584+
}
585+
586+
private static async Task DoTestWithConnectionAsync(
587+
bool openConnection,
588+
Func<TestDatabase, Task> testAction,
589+
Func<TestDatabase> createDatabaseAction)
590+
{
591+
using TestDatabase database = createDatabaseAction();
592+
if (openConnection)
518593
{
519-
if (openConnection) database.Connection.Open();
520-
testAction(database);
521-
database.Connection.State.Should().Be(openConnection ? ConnectionState.Open : ConnectionState.Closed);
594+
database.Connection.Open();
522595
}
596+
597+
await testAction(database);
598+
database.Connection.State.Should().Be(openConnection ? ConnectionState.Open : ConnectionState.Closed);
523599
}
524600

525601
private void DatabaseShouldContainInvoices(Database korm, IEnumerable<Invoice> expected)

tests/Kros.KORM.UnitTests/Query/DbSetShould.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ public class Person
324324

325325
private class FakeProvider : IQueryProvider
326326
{
327-
async Task IQueryProvider.ExecuteInTransactionAsync(Func<Task> action) => await action();
327+
async Task IQueryProvider.ExecuteInTransactionAsync(Func<CancellationToken, Task> action, CancellationToken cancellationToken) => await action(cancellationToken);
328328
int IQueryProvider.ExecuteNonQueryCommand(IDbCommand command) => command.ExecuteNonQuery();
329329
bool IQueryProvider.SupportsIdentity() => false;
330330
bool IQueryProvider.SupportsPrepareCommand() => true;

0 commit comments

Comments
 (0)