< Summary

Information
Class: ArturRios.Data.Dapper.DapperSqlQuery
Assembly: ArturRios.Data.Dapper
File(s): D:\Repositories\dotnet-data\src\ArturRios.Data.Dapper\DapperSqlQuery.cs
Line coverage
81%
Covered lines: 26
Uncovered lines: 6
Coverable lines: 32
Total lines: 100
Line coverage: 81.2%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage
100%
Covered methods: 14
Fully covered methods: 12
Total methods: 14
Method coverage: 100%
Full method coverage: 85.7%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Connection()100%11100%
get_Transaction()100%22100%
QueryAsync(...)100%11100%
QueryFirstOrDefaultAsync(...)100%11100%
QuerySingleOrDefaultAsync(...)100%11100%
ExecuteScalarAsync(...)100%11100%
Query(...)100%11100%
QueryFirstOrDefault(...)100%11100%
QuerySingleOrDefault(...)100%11100%
ExecuteScalar(...)100%11100%
Guarded(...)100%1170%
Command(...)100%11100%
GuardedAsync()100%1170%

File(s)

D:\Repositories\dotnet-data\src\ArturRios.Data.Dapper\DapperSqlQuery.cs

#LineLine coverage
 1using System.Data.Common;
 2using ArturRios.Data.Relational.Core.Configuration;
 3using ArturRios.Output;
 4using Dapper;
 5using Microsoft.EntityFrameworkCore;
 6using Microsoft.EntityFrameworkCore.Storage;
 7
 8namespace ArturRios.Data.Dapper;
 9
 10/// <summary>
 11///     Dapper-backed read-only query executor. Runs against the <see cref="BaseDbContext" />'s
 12///     connection and enlists in its ambient transaction, so Dapper reads and EF writes share one
 13///     connection and one unit-of-work transaction. Failures are returned as <see cref="DataOutput{T}" />.
 14/// </summary>
 15/// <param name="context">The application's <see cref="BaseDbContext" />.</param>
 1516public class DapperSqlQuery(BaseDbContext context) : ISqlQuery, IAsyncSqlQuery
 17{
 18    /// <summary>Message prefix returned when a query fails.</summary>
 19    protected const string QueryFailedMessage = "A data-access error occurred:";
 20
 21    /// <summary>The context's underlying database connection.</summary>
 1422    protected DbConnection Connection => context.Database.GetDbConnection();
 23
 24    /// <summary>The ambient database transaction, or <see langword="null" /> when none is active.</summary>
 1425    protected DbTransaction? Transaction => context.Database.CurrentTransaction?.GetDbTransaction();
 26
 27    /// <inheritdoc />
 28    public Task<DataOutput<IEnumerable<T>>> QueryAsync<T>(string sql, object? parameters = null,
 29        CancellationToken ct = default) =>
 830        GuardedAsync(async () => await Connection.QueryAsync<T>(Command(sql, parameters, ct)));
 31
 32    /// <inheritdoc />
 33    public Task<DataOutput<T?>> QueryFirstOrDefaultAsync<T>(string sql, object? parameters = null,
 34        CancellationToken ct = default) =>
 235        GuardedAsync(async () => await Connection.QueryFirstOrDefaultAsync<T?>(Command(sql, parameters, ct)));
 36
 37    /// <inheritdoc />
 38    public Task<DataOutput<T?>> QuerySingleOrDefaultAsync<T>(string sql, object? parameters = null,
 39        CancellationToken ct = default) =>
 240        GuardedAsync(async () => await Connection.QuerySingleOrDefaultAsync<T?>(Command(sql, parameters, ct)));
 41
 42    /// <inheritdoc />
 43    public Task<DataOutput<T?>> ExecuteScalarAsync<T>(string sql, object? parameters = null,
 44        CancellationToken ct = default) =>
 245        GuardedAsync(async () => await Connection.ExecuteScalarAsync<T?>(Command(sql, parameters, ct)));
 46
 47    /// <inheritdoc />
 48    public DataOutput<IEnumerable<T>> Query<T>(string sql, object? parameters = null) =>
 649        Guarded(() => Connection.Query<T>(sql, parameters, Transaction));
 50
 51    /// <inheritdoc />
 52    public DataOutput<T?> QueryFirstOrDefault<T>(string sql, object? parameters = null) =>
 453        Guarded(() => Connection.QueryFirstOrDefault<T?>(sql, parameters, Transaction));
 54
 55    /// <inheritdoc />
 56    public DataOutput<T?> QuerySingleOrDefault<T>(string sql, object? parameters = null) =>
 257        Guarded(() => Connection.QuerySingleOrDefault<T?>(sql, parameters, Transaction));
 58
 59    /// <inheritdoc />
 60    public DataOutput<T?> ExecuteScalar<T>(string sql, object? parameters = null) =>
 261        Guarded(() => Connection.ExecuteScalar<T?>(sql, parameters, Transaction));
 62
 63    /// <summary>Runs a synchronous query, converting failures to envelope errors.</summary>
 64    protected static DataOutput<TResult> Guarded<TResult>(Func<TResult> operation)
 765    {
 66        try
 767        {
 768            return DataOutput<TResult>.New.WithData(operation());
 69        }
 070        catch (OperationCanceledException)
 071        {
 072            throw;
 73        }
 274        catch (Exception ex)
 275        {
 276            return DataOutput<TResult>.New.WithError($"{QueryFailedMessage} {ex.GetBaseException().Message}");
 77        }
 778    }
 79
 80    /// <summary>Builds a Dapper command carrying the ambient transaction and cancellation token.</summary>
 81    private CommandDefinition Command(string sql, object? parameters, CancellationToken ct) =>
 782        new(sql, parameters, Transaction, cancellationToken: ct);
 83
 84    /// <summary>Runs an asynchronous query, converting failures to envelope errors.</summary>
 85    protected static async Task<DataOutput<TResult>> GuardedAsync<TResult>(Func<Task<TResult>> operation)
 786    {
 87        try
 788        {
 789            return DataOutput<TResult>.New.WithData(await operation());
 90        }
 091        catch (OperationCanceledException)
 092        {
 093            throw;
 94        }
 295        catch (Exception ex)
 296        {
 297            return DataOutput<TResult>.New.WithError($"{QueryFailedMessage} {ex.GetBaseException().Message}");
 98        }
 799    }
 100}