< Summary

Information
Class: ArturRios.Data.Relational.Core.Transactions.EfUnitOfWork
Assembly: ArturRios.Data.Relational.Core
File(s): /home/runner/work/dotnet-data/dotnet-data/src/ArturRios.Data.Relational.Core/Transactions/EfUnitOfWork.cs
Line coverage
46%
Covered lines: 30
Uncovered lines: 34
Coverable lines: 64
Total lines: 163
Line coverage: 46.8%
Branch coverage
75%
Covered branches: 6
Total branches: 8
Branch coverage: 75%
Method coverage
37%
Covered methods: 6
Fully covered methods: 1
Total methods: 16
Method coverage: 37.5%
Full method coverage: 6.2%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExecuteInTransactionAsync()100%4481.81%
ExecuteInTransactionAsync()50%6454.54%
BeginTransactionAsync()100%210%
ExecuteInTransaction(...)100%1172.72%
ExecuteInTransaction(...)100%210%
RollbackQuietlyAsync()100%1160%
RollbackQuietly(...)100%1160%
BeginTransaction()100%210%
.ctor(...)100%210%
Commit()100%210%
Rollback()100%210%
CommitAsync(...)100%210%
RollbackAsync(...)100%210%
Dispose()100%210%
DisposeAsync()100%210%

File(s)

/home/runner/work/dotnet-data/dotnet-data/src/ArturRios.Data.Relational.Core/Transactions/EfUnitOfWork.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Configuration;
 2using ArturRios.Data.Relational.Core.Repositories;
 3using ArturRios.Output;
 4using Microsoft.EntityFrameworkCore.Storage;
 5
 6namespace ArturRios.Data.Relational.Core.Transactions;
 7
 8/// <summary>
 9///     Entity Framework Core implementation of <see cref="IUnitOfWork" /> and <see cref="IAsyncUnitOfWork" />.
 10///     Repository saves issued within the delegate flush but do not commit until the transaction commits.
 11/// </summary>
 12/// <param name="context">The application's <see cref="BaseDbContext" />.</param>
 2413public class EfUnitOfWork(BaseDbContext context) : IUnitOfWork, IAsyncUnitOfWork
 14{
 15    /// <inheritdoc />
 16    public async Task<ProcessOutput> ExecuteInTransactionAsync(Func<Task> work, CancellationToken ct = default)
 17    {
 418        var tx = await context.Database.BeginTransactionAsync(ct).ConfigureAwait(false);
 19
 420        await using var txScope = tx.ConfigureAwait(false);
 21
 22        try
 23        {
 424            await work().ConfigureAwait(false);
 025            await tx.CommitAsync(ct).ConfigureAwait(false);
 026            return ProcessOutput.New;
 27        }
 428        catch (Exception ex)
 29        {
 430            await RollbackQuietlyAsync(tx).ConfigureAwait(false);
 31
 432            if (ex is OperationCanceledException)
 33            {
 234                throw;
 35            }
 36
 237            return ProcessOutput.New.WithError(RelationalErrors.Describe(ex));
 38        }
 239    }
 40
 41    /// <inheritdoc />
 42    public async Task<DataOutput<TResult>> ExecuteInTransactionAsync<TResult>(Func<Task<TResult>> work,
 43        CancellationToken ct = default)
 44    {
 445        var tx = await context.Database.BeginTransactionAsync(ct).ConfigureAwait(false);
 46
 447        await using var txScope = tx.ConfigureAwait(false);
 48
 49        try
 50        {
 451            var result = await work().ConfigureAwait(false);
 452            await tx.CommitAsync(ct).ConfigureAwait(false);
 53
 454            return DataOutput<TResult>.New.WithData(result);
 55        }
 056        catch (Exception ex)
 57        {
 058            await RollbackQuietlyAsync(tx).ConfigureAwait(false);
 59
 060            if (ex is OperationCanceledException)
 61            {
 062                throw;
 63            }
 64
 065            return DataOutput<TResult>.New.WithError(RelationalErrors.Describe(ex));
 66        }
 467    }
 68
 69    /// <inheritdoc />
 70    public async Task<IDbTransactionHandle> BeginTransactionAsync(CancellationToken ct = default) =>
 071        new EfTransactionHandle(await context.Database.BeginTransactionAsync(ct).ConfigureAwait(false));
 72
 73    /// <inheritdoc />
 74    public ProcessOutput ExecuteInTransaction(Action work)
 75    {
 876        using var tx = context.Database.BeginTransaction();
 77        try
 78        {
 879            work();
 280            tx.Commit();
 81
 282            return ProcessOutput.New;
 83        }
 084        catch (OperationCanceledException)
 85        {
 086            RollbackQuietly(tx);
 87
 088            throw;
 89        }
 690        catch (Exception ex)
 91        {
 692            RollbackQuietly(tx);
 93
 694            return ProcessOutput.New.WithError(RelationalErrors.Describe(ex));
 95        }
 896    }
 97
 98    /// <inheritdoc />
 99    public DataOutput<TResult> ExecuteInTransaction<TResult>(Func<TResult> work)
 100    {
 0101        using var tx = context.Database.BeginTransaction();
 102        try
 103        {
 0104            var result = work();
 0105            tx.Commit();
 106
 0107            return DataOutput<TResult>.New.WithData(result);
 108        }
 0109        catch (OperationCanceledException)
 110        {
 0111            RollbackQuietly(tx);
 112
 0113            throw;
 114        }
 0115        catch (Exception ex)
 116        {
 0117            RollbackQuietly(tx);
 118
 0119            return DataOutput<TResult>.New.WithError(RelationalErrors.Describe(ex));
 120        }
 0121    }
 122
 123    // Rollback must never mask the failure that triggered it: it runs untied to the caller's
 124    // token (which may already be canceled, making Rollback throw before it rolls anything back)
 125    // and swallows its own errors. Disposing the transaction rolls back whatever is left.
 126    private static async Task RollbackQuietlyAsync(IDbContextTransaction transaction)
 127    {
 128        try
 129        {
 4130            await transaction.RollbackAsync(CancellationToken.None).ConfigureAwait(false);
 4131        }
 0132        catch
 133        {
 134            // Already rolled back, or the connection is gone. Dispose completes the cleanup.
 0135        }
 4136    }
 137
 138    private static void RollbackQuietly(IDbContextTransaction transaction)
 139    {
 140        try
 141        {
 6142            transaction.Rollback();
 6143        }
 0144        catch
 145        {
 146            // Already rolled back, or the connection is gone. Dispose completes the cleanup.
 0147        }
 6148    }
 149
 150    /// <inheritdoc />
 151    public IDbTransactionHandle BeginTransaction() =>
 0152        new EfTransactionHandle(context.Database.BeginTransaction());
 153
 0154    private sealed class EfTransactionHandle(IDbContextTransaction transaction) : IDbTransactionHandle
 155    {
 0156        public void Commit() => transaction.Commit();
 0157        public void Rollback() => transaction.Rollback();
 0158        public Task CommitAsync(CancellationToken ct = default) => transaction.CommitAsync(ct);
 0159        public Task RollbackAsync(CancellationToken ct = default) => transaction.RollbackAsync(ct);
 0160        public void Dispose() => transaction.Dispose();
 0161        public ValueTask DisposeAsync() => transaction.DisposeAsync();
 162    }
 163}