< Summary

Information
Class: ArturRios.Data.MongoDb.Transactions.MongoUnitOfWork
Assembly: ArturRios.Data.MongoDb
File(s): /home/ci/agent/workspace/dotnet-data-docs_main/app/src/ArturRios.Data.MongoDb/Transactions/MongoUnitOfWork.cs
Line coverage
84%
Covered lines: 50
Uncovered lines: 9
Coverable lines: 59
Total lines: 140
Line coverage: 84.7%
Branch coverage
81%
Covered branches: 13
Total branches: 16
Branch coverage: 81.2%
Method coverage
100%
Covered methods: 9
Fully covered methods: 5
Total methods: 9
Method coverage: 100%
Full method coverage: 55.5%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExecuteInTransactionAsync(...)100%11100%
ExecuteInTransactionAsync(...)100%22100%
ExecuteInTransaction(...)100%1160%
ExecuteInTransaction(...)100%22100%
RunAsync()66.66%6694.11%
Run(...)83.33%7672.22%
AbortQuietlyAsync()100%11100%
AbortQuietly(...)100%1180%

File(s)

/home/ci/agent/workspace/dotnet-data-docs_main/app/src/ArturRios.Data.MongoDb/Transactions/MongoUnitOfWork.cs

#LineLine coverage
 1using ArturRios.Data.MongoDb.Repositories;
 2using ArturRios.Output;
 3using MongoDB.Driver;
 4
 5namespace ArturRios.Data.MongoDb.Transactions;
 6
 7/// <summary>
 8///     MongoDB implementation of the unit of work. Opens a client session, sets it as the context's
 9///     ambient session so repository operations enlist, and commits/aborts the transaction.
 10/// </summary>
 11/// <param name="client">The Mongo client.</param>
 12/// <param name="context">The Mongo context whose ambient session is managed.</param>
 3613public class MongoUnitOfWork(IMongoClient client, MongoContext context) : IMongoUnitOfWork, IAsyncMongoUnitOfWork
 14{
 15    /// <inheritdoc />
 16    public Task<ProcessOutput> ExecuteInTransactionAsync(Func<Task> work, CancellationToken ct = default) =>
 1417        RunAsync(async () =>
 1418        {
 1019            await work().ConfigureAwait(false);
 620            return ProcessOutput.New;
 2821        }, error => ProcessOutput.New.WithError(error), ct);
 22
 23    /// <inheritdoc />
 24    public Task<DataOutput<TResult>> ExecuteInTransactionAsync<TResult>(Func<Task<TResult>> work,
 25        CancellationToken ct = default) =>
 1026        RunAsync(async () => DataOutput<TResult>.New.WithData(await work().ConfigureAwait(false)),
 1427            error => DataOutput<TResult>.New.WithError(error), ct);
 28
 29    /// <inheritdoc />
 30    public ProcessOutput ExecuteInTransaction(Action work) =>
 431        Run(() =>
 432        {
 033            work();
 034            return ProcessOutput.New;
 835        }, error => ProcessOutput.New.WithError(error));
 36
 37    /// <inheritdoc />
 38    public DataOutput<TResult> ExecuteInTransaction<TResult>(Func<TResult> work) =>
 1239        Run(() => DataOutput<TResult>.New.WithData(work()), error => DataOutput<TResult>.New.WithError(error));
 40
 41    // Every step that can fail — opening the session, starting the transaction (which throws on a standalone
 42    // server), the work and the commit — runs inside the guard, so a failure is enveloped like any other and
 43    // the finally always restores the caller's ambient session before the session is disposed. The session is
 44    // made ambient only once its transaction has started.
 45    private async Task<TOutput> RunAsync<TOutput>(Func<Task<TOutput>> work, Func<string, TOutput> fail,
 46        CancellationToken ct)
 47    {
 2248        var previousSession = context.Session;
 2249        IClientSessionHandle? session = null;
 50        try
 51        {
 2252            session = await client.StartSessionAsync(cancellationToken: ct).ConfigureAwait(false);
 2253            session.StartTransaction();
 1254            context.Session = session;
 1255            var output = await work().ConfigureAwait(false);
 856            await session.CommitTransactionAsync(ct).ConfigureAwait(false);
 857            return output;
 58        }
 1459        catch (Exception ex)
 60        {
 1461            if (session is not null)
 62            {
 1463                await AbortQuietlyAsync(session).ConfigureAwait(false);
 64            }
 65
 1466            if (ex is OperationCanceledException)
 67            {
 068                throw;
 69            }
 70
 1471            return fail(MongoErrors.Describe(ex));
 72        }
 73        finally
 74        {
 2275            context.Session = previousSession;
 2276            session?.Dispose();
 77        }
 2278    }
 79
 80    private TOutput Run<TOutput>(Func<TOutput> work, Func<string, TOutput> fail)
 81    {
 1082        var previousSession = context.Session;
 1083        IClientSessionHandle? session = null;
 84        try
 85        {
 1086            session = client.StartSession();
 1087            session.StartTransaction();
 088            context.Session = session;
 089            var output = work();
 090            session.CommitTransaction();
 091            return output;
 92        }
 1093        catch (Exception ex)
 94        {
 1095            if (session is not null)
 96            {
 1097                AbortQuietly(session);
 98            }
 99
 10100            if (ex is OperationCanceledException)
 101            {
 0102                throw;
 103            }
 104
 10105            return fail(MongoErrors.Describe(ex));
 106        }
 107        finally
 108        {
 10109            context.Session = previousSession;
 10110            session?.Dispose();
 10111        }
 10112    }
 113
 114    // Abort must never mask the failure that triggered it: the server aborts the transaction itself
 115    // on a write conflict, so an explicit abort can fail on a transaction that is already gone. It
 116    // also runs untied to the caller's token, which may already be canceled.
 117    private static async Task AbortQuietlyAsync(IClientSessionHandle session)
 118    {
 119        try
 120        {
 14121            await session.AbortTransactionAsync(CancellationToken.None).ConfigureAwait(false);
 4122        }
 10123        catch
 124        {
 125            // Never started, already aborted, or the session is gone. Disposing the session completes the cleanup.
 10126        }
 14127    }
 128
 129    private static void AbortQuietly(IClientSessionHandle session)
 130    {
 131        try
 132        {
 10133            session.AbortTransaction();
 0134        }
 10135        catch
 136        {
 137            // Never started, already aborted, or the session is gone. Disposing the session completes the cleanup.
 10138        }
 10139    }
 140}