| | | 1 | | using ArturRios.Heimdall.Data.Configuration; |
| | | 2 | | using ArturRios.Heimdall.Domain.Persistence; |
| | | 3 | | using Microsoft.EntityFrameworkCore.Storage; |
| | | 4 | | |
| | | 5 | | namespace ArturRios.Heimdall.Data.Persistence; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// <see cref="IWriteTransactions" /> on the request's <see cref="AppDbContext" />: a database |
| | | 9 | | /// transaction every write made through that context joins until it is committed or disposed. |
| | | 10 | | /// </summary> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// <para> |
| | | 13 | | /// The context runs without a retrying execution strategy, so a transaction the caller opens |
| | | 14 | | /// is allowed. <see cref="ApplicationCredentialSlots" /> wraps its own in the strategy because |
| | | 15 | | /// it owns the whole unit; here the unit is the handler's and cannot be replayed by EF Core. |
| | | 16 | | /// </para> |
| | | 17 | | /// <para> |
| | | 18 | | /// The rollback and the clearing of the change tracker never throw: they run because something |
| | | 19 | | /// already failed, and must not replace that failure with their own. |
| | | 20 | | /// </para> |
| | | 21 | | /// </remarks> |
| | 269 | 22 | | public sealed class WriteTransactions(AppDbContext context) : IWriteTransactions |
| | | 23 | | { |
| | | 24 | | public async Task<IWriteTransaction> BeginAsync() => |
| | 122 | 25 | | new WriteTransaction(context, await context.Database.BeginTransactionAsync()); |
| | | 26 | | |
| | 122 | 27 | | private sealed class WriteTransaction(AppDbContext context, IDbContextTransaction transaction) |
| | | 28 | | : IWriteTransaction |
| | | 29 | | { |
| | | 30 | | private bool _committed; |
| | | 31 | | |
| | | 32 | | public async Task CommitAsync() |
| | | 33 | | { |
| | 92 | 34 | | await transaction.CommitAsync(); |
| | 92 | 35 | | _committed = true; |
| | 92 | 36 | | } |
| | | 37 | | |
| | | 38 | | public async ValueTask DisposeAsync() |
| | | 39 | | { |
| | 122 | 40 | | if (!_committed) |
| | | 41 | | { |
| | | 42 | | try |
| | | 43 | | { |
| | 30 | 44 | | await transaction.RollbackAsync(CancellationToken.None); |
| | 30 | 45 | | } |
| | 0 | 46 | | catch |
| | | 47 | | { |
| | | 48 | | // Already rolled back, or the connection is gone; disposing finishes the cleanup. |
| | 0 | 49 | | } |
| | | 50 | | |
| | | 51 | | // Nothing the transaction wrote was kept, so nothing the tracker holds about it is true: |
| | | 52 | | // a failed save's entities are still pending, and the next save would replay them. |
| | 30 | 53 | | context.ChangeTracker.Clear(); |
| | | 54 | | } |
| | | 55 | | |
| | 122 | 56 | | await transaction.DisposeAsync(); |
| | 122 | 57 | | } |
| | | 58 | | } |
| | | 59 | | } |