< Summary

Information
Class: ArturRios.Data.Relational.Core.Repositories.EfRepository<T>
Assembly: ArturRios.Data.Relational.Core
File(s): D:\Repositories\dotnet-data\src\ArturRios.Data.Relational.Core\Repositories\EfRepository.cs
Line coverage
78%
Covered lines: 94
Uncovered lines: 26
Coverable lines: 120
Total lines: 208
Line coverage: 78.3%
Branch coverage
70%
Covered branches: 7
Total branches: 10
Branch coverage: 70%
Method coverage
90%
Covered methods: 20
Fully covered methods: 17
Total methods: 22
Method coverage: 90.9%
Full method coverage: 77.2%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Set()100%11100%
GetAllAsync(...)100%11100%
GetByIdAsync(...)100%11100%
CreateAsync(...)100%11100%
CreateRangeAsync(...)100%11100%
UpdateAsync(...)100%11100%
UpdateRangeAsync(...)100%210%
DeleteAsync(...)100%11100%
DeleteRangeAsync(...)100%11100%
Query()100%11100%
GetAll()100%11100%
GetById(...)100%11100%
Create(...)100%11100%
CreateRange(...)100%22100%
Update(...)100%11100%
UpdateRange(...)100%210%
Delete(...)100%11100%
DeleteRange(...)50%22100%
Fail(...)66.66%7671.42%
Guarded(...)100%1170%
GuardedAsync()100%1140%

File(s)

D:\Repositories\dotnet-data\src\ArturRios.Data.Relational.Core\Repositories\EfRepository.cs

#LineLine coverage
 1using System.Data.Common;
 2using ArturRios.Data.Relational.Core.Configuration;
 3using ArturRios.Data.Relational.Core.Entities;
 4using ArturRios.Data.Relational.Core.Interfaces;
 5using ArturRios.Output;
 6using Microsoft.EntityFrameworkCore;
 7
 8namespace ArturRios.Data.Relational.Core.Repositories;
 9
 10/// <summary>
 11///     Provider-agnostic Entity Framework Core implementation of the repository contracts.
 12///     Every write auto-saves; inside an active unit-of-work transaction,
 13///     saves flush without committing. Infrastructure failures are returned as <see cref="DataOutput{T}" /> errors.
 14/// </summary>
 15/// <typeparam name="T">The entity type.</typeparam>
 16/// <param name="context">The application's <see cref="BaseDbContext" />.</param>
 2517public class EfRepository<T>(BaseDbContext context) : IRepository<T>, IAsyncRepository<T>
 18    where T : Entity
 19{
 20    /// <summary>Message returned when an optimistic-concurrency conflict is detected.</summary>
 21    protected const string ConcurrencyMessage =
 22        "Concurrency conflict: the record was modified or removed by another process.";
 23
 24    /// <summary>Message prefix returned when a persistence operation fails.</summary>
 25    protected const string PersistenceMessage = "A data-access error occurred:";
 26
 27    /// <summary>The tracked entity set for <typeparamref name="T" />.</summary>
 4428    protected DbSet<T> Set => context.Set<T>();
 29
 30    /// <inheritdoc />
 31    public Task<DataOutput<IEnumerable<T>>> GetAllAsync(CancellationToken ct = default) =>
 632        GuardedAsync<IEnumerable<T>>(async () => await Set.ToListAsync(ct));
 33
 34    /// <inheritdoc />
 35    public Task<DataOutput<T?>> GetByIdAsync(long id, CancellationToken ct = default) =>
 436        GuardedAsync(async () => await Set.FirstOrDefaultAsync(e => e.Id == id, ct));
 37
 38    /// <inheritdoc />
 39    public Task<DataOutput<long>> CreateAsync(T entity, CancellationToken ct = default) =>
 540        GuardedAsync(async () =>
 541        {
 542            await Set.AddAsync(entity, ct);
 543            await context.SaveChangesAsync(ct);
 544
 545            return entity.Id;
 1046        });
 47
 48    /// <inheritdoc />
 49    public Task<DataOutput<IEnumerable<long>>>
 50        CreateRangeAsync(IEnumerable<T> entities, CancellationToken ct = default) =>
 251        GuardedAsync<IEnumerable<long>>(async () =>
 252        {
 253            var list = entities.ToList();
 254            await Set.AddRangeAsync(list, ct);
 255            await context.SaveChangesAsync(ct);
 256
 657            return list.Select(e => e.Id).ToList();
 458        });
 59
 60    /// <inheritdoc />
 61    public Task<DataOutput<T>> UpdateAsync(T entity, CancellationToken ct = default) =>
 162        GuardedAsync(async () =>
 163        {
 164            Set.Update(entity);
 165            await context.SaveChangesAsync(ct);
 166
 167            return entity;
 268        });
 69
 70    /// <inheritdoc />
 71    public Task<DataOutput<IEnumerable<T>>> UpdateRangeAsync(IEnumerable<T> entities, CancellationToken ct = default) =>
 072        GuardedAsync<IEnumerable<T>>(async () =>
 073        {
 074            var list = entities.ToList();
 075            Set.UpdateRange(list);
 076            await context.SaveChangesAsync(ct);
 077
 078            return list;
 079        });
 80
 81    /// <inheritdoc />
 82    public Task<DataOutput<long>> DeleteAsync(T entity, CancellationToken ct = default) =>
 183        GuardedAsync(async () =>
 184        {
 185            Set.Remove(entity);
 186            await context.SaveChangesAsync(ct);
 187
 188            return entity.Id;
 289        });
 90
 91    /// <inheritdoc />
 92    public Task<DataOutput<IEnumerable<long>>> DeleteRangeAsync(IEnumerable<long> ids, CancellationToken ct = default) =
 193        GuardedAsync<IEnumerable<long>>(async () =>
 194        {
 195            var idList = ids.ToList();
 196            var matches = await Set.Where(e => idList.Contains(e.Id)).ToListAsync(ct);
 197            Set.RemoveRange(matches);
 198            await context.SaveChangesAsync(ct);
 199
 3100            return matches.Select(e => e.Id).ToList();
 2101        });
 102
 103    /// <inheritdoc cref="Query" />
 1104    public IQueryable<T> Query() => Set.AsQueryable();
 105
 106    /// <inheritdoc />
 107    public DataOutput<IEnumerable<T>> GetAll() =>
 10108        Guarded(IEnumerable<T> () => Set.ToList());
 109
 110    /// <inheritdoc />
 111    public DataOutput<T?> GetById(long id) =>
 10112        Guarded(() => Set.FirstOrDefault(e => e.Id == id));
 113
 114    /// <inheritdoc />
 8115    public DataOutput<long> Create(T entity) => Guarded(() =>
 8116    {
 8117        Set.Add(entity);
 8118        context.SaveChanges();
 8119        return entity.Id;
 16120    });
 121
 122    /// <inheritdoc />
 3123    public DataOutput<IEnumerable<long>> CreateRange(IEnumerable<T> entities) => Guarded(IEnumerable<long> () =>
 3124    {
 3125        var list = entities.ToList();
 3126        Set.AddRange(list);
 3127        context.SaveChanges();
 9128        return list.Select(e => e.Id).ToList();
 6129    });
 130
 131    /// <inheritdoc />
 3132    public DataOutput<T> Update(T entity) => Guarded(() =>
 3133    {
 3134        Set.Update(entity);
 3135        context.SaveChanges();
 2136        return entity;
 5137    });
 138
 139    /// <inheritdoc />
 0140    public DataOutput<IEnumerable<T>> UpdateRange(IEnumerable<T> entities) => Guarded(IEnumerable<T> () =>
 0141    {
 0142        var list = entities.ToList();
 0143        Set.UpdateRange(list);
 0144        context.SaveChanges();
 0145        return list;
 0146    });
 147
 148    /// <inheritdoc />
 1149    public DataOutput<long> Delete(T entity) => Guarded(() =>
 1150    {
 1151        Set.Remove(entity);
 1152        context.SaveChanges();
 1153        return entity.Id;
 2154    });
 155
 156    /// <inheritdoc />
 1157    public DataOutput<IEnumerable<long>> DeleteRange(IEnumerable<long> ids) => Guarded(IEnumerable<long> () =>
 1158    {
 1159        var idList = ids.ToList();
 1160        var matches = Set.Where(e => idList.Contains(e.Id)).ToList();
 1161        Set.RemoveRange(matches);
 1162        context.SaveChanges();
 3163        return matches.Select(e => e.Id).ToList();
 2164    });
 165
 166    /// <summary>Maps an exception caught by a guard to an error envelope.</summary>
 2167    private static DataOutput<TResult> Fail<TResult>(Exception ex) => ex switch
 2168    {
 1169        DbUpdateConcurrencyException => DataOutput<TResult>.New.WithError(ConcurrencyMessage),
 0170        DbUpdateException => DataOutput<TResult>.New.WithError($"{PersistenceMessage} {ex.GetBaseException().Message}"),
 0171        DbException => DataOutput<TResult>.New.WithError($"{PersistenceMessage} {ex.Message}"),
 1172        _ => DataOutput<TResult>.New.WithError($"{PersistenceMessage} {ex.GetBaseException().Message}")
 2173    };
 174
 175    /// <summary>Runs a synchronous data operation, converting failures to envelope errors.</summary>
 176    protected static DataOutput<TResult> Guarded<TResult>(Func<TResult> operation)
 26177    {
 178        try
 26179        {
 26180            return DataOutput<TResult>.New.WithData(operation());
 181        }
 0182        catch (OperationCanceledException)
 0183        {
 0184            throw;
 185        }
 2186        catch (Exception ex)
 2187        {
 2188            return Fail<TResult>(ex);
 189        }
 26190    }
 191
 192    /// <summary>Runs an asynchronous data operation, converting failures to envelope errors.</summary>
 193    protected static async Task<DataOutput<TResult>> GuardedAsync<TResult>(Func<Task<TResult>> operation)
 15194    {
 195        try
 15196        {
 15197            return DataOutput<TResult>.New.WithData(await operation());
 198        }
 0199        catch (OperationCanceledException)
 0200        {
 0201            throw;
 202        }
 0203        catch (Exception ex)
 0204        {
 0205            return Fail<TResult>(ex);
 206        }
 15207    }
 208}