< Summary

Information
Class: ArturRios.Data.Relational.Core.Repositories.EfRepository<T1, T2>
Assembly: ArturRios.Data.Relational.Core
File(s): /home/ci/agent/workspace/dotnet-data-docs_main/app/src/ArturRios.Data.Relational.Core/Repositories/EfRepository.cs
Line coverage
80%
Covered lines: 100
Uncovered lines: 25
Coverable lines: 125
Total lines: 265
Line coverage: 80%
Branch coverage
92%
Covered branches: 13
Total branches: 14
Branch coverage: 92.8%
Method coverage
92%
Covered methods: 23
Fully covered methods: 19
Total methods: 25
Method coverage: 92%
Full method coverage: 76%

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(...)100%11100%
Write(...)100%2262.5%
WriteAsync()100%2262.5%
DiscardPendingChanges()100%66100%
Guarded(...)100%1160%
GuardedAsync()100%1160%

File(s)

/home/ci/agent/workspace/dotnet-data-docs_main/app/src/ArturRios.Data.Relational.Core/Repositories/EfRepository.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Configuration;
 2using ArturRios.Data.Relational.Core.Entities;
 3using ArturRios.Data.Relational.Core.Interfaces;
 4using ArturRios.Output;
 5using Microsoft.EntityFrameworkCore;
 6
 7namespace ArturRios.Data.Relational.Core.Repositories;
 8
 9/// <summary>
 10///     Provider-agnostic Entity Framework Core implementation of the repository contracts.
 11///     Every write auto-saves; inside an active unit-of-work transaction,
 12///     saves flush without committing. Infrastructure failures are returned as <see cref="DataOutput{T}" /> errors;
 13///     a failed write discards the changes still pending in the context, so the context stays usable.
 14/// </summary>
 15/// <typeparam name="T">The entity type.</typeparam>
 16/// <typeparam name="TKey">The entity's primary key type.</typeparam>
 17/// <param name="context">The application's <see cref="BaseDbContext" />.</param>
 11418public class EfRepository<T, TKey>(BaseDbContext context) : IRepository<T, TKey>, IAsyncRepository<T, TKey>
 19    where T : Entity<TKey> where TKey : IEquatable<TKey>
 20{
 21    /// <summary>Message returned when an optimistic-concurrency conflict is detected.</summary>
 22    protected const string ConcurrencyMessage = RelationalErrors.ConcurrencyMessage;
 23
 24    /// <summary>Message returned when a persistence operation fails with no finer classification.</summary>
 25    protected const string PersistenceMessage = RelationalErrors.GenericMessage;
 26
 27    /// <summary>Message returned when a write violates a unique constraint.</summary>
 28    protected const string UniqueViolationMessage = RelationalErrors.UniqueViolationMessage;
 29
 30    /// <summary>The tracked entity set for <typeparamref name="T" />.</summary>
 20031    protected DbSet<T> Set => context.Set<T>();
 32
 33    /// <inheritdoc />
 34    public Task<DataOutput<IEnumerable<T>>> GetAllAsync(CancellationToken ct = default) =>
 1635        GuardedAsync<IEnumerable<T>>(async () => await Set.ToListAsync(ct).ConfigureAwait(false));
 36
 37    /// <inheritdoc />
 38    public Task<DataOutput<T?>> GetByIdAsync(TKey id, CancellationToken ct = default) =>
 1639        GuardedAsync(async () => await Set.FirstOrDefaultAsync(e => e.Id.Equals(id), ct).ConfigureAwait(false));
 40
 41    /// <inheritdoc />
 42    public Task<DataOutput<TKey>> CreateAsync(T entity, CancellationToken ct = default) =>
 2043        WriteAsync(async () =>
 2044        {
 2045            await Set.AddAsync(entity, ct).ConfigureAwait(false);
 2046            await context.SaveChangesAsync(ct).ConfigureAwait(false);
 2047
 1848            return entity.Id;
 3849        });
 50
 51    /// <inheritdoc />
 52    public Task<DataOutput<IEnumerable<TKey>>>
 53        CreateRangeAsync(IEnumerable<T> entities, CancellationToken ct = default) =>
 454        WriteAsync<IEnumerable<TKey>>(async () =>
 455        {
 456            var list = entities.ToList();
 457            await Set.AddRangeAsync(list, ct).ConfigureAwait(false);
 458            await context.SaveChangesAsync(ct).ConfigureAwait(false);
 459
 1260            return list.Select(e => e.Id).ToList();
 861        });
 62
 63    /// <inheritdoc />
 64    public Task<DataOutput<T>> UpdateAsync(T entity, CancellationToken ct = default) =>
 265        WriteAsync(async () =>
 266        {
 267            Set.Update(entity);
 268            await context.SaveChangesAsync(ct).ConfigureAwait(false);
 269
 270            return entity;
 471        });
 72
 73    /// <inheritdoc />
 74    public Task<DataOutput<IEnumerable<T>>> UpdateRangeAsync(IEnumerable<T> entities, CancellationToken ct = default) =>
 075        WriteAsync<IEnumerable<T>>(async () =>
 076        {
 077            var list = entities.ToList();
 078            Set.UpdateRange(list);
 079            await context.SaveChangesAsync(ct).ConfigureAwait(false);
 080
 081            return list;
 082        });
 83
 84    /// <inheritdoc />
 85    public Task<DataOutput<TKey>> DeleteAsync(T entity, CancellationToken ct = default) =>
 486        WriteAsync(async () =>
 487        {
 488            Set.Remove(entity);
 489            await context.SaveChangesAsync(ct).ConfigureAwait(false);
 490
 491            return entity.Id;
 892        });
 93
 94    /// <inheritdoc />
 95    public Task<DataOutput<IEnumerable<TKey>>> DeleteRangeAsync(IEnumerable<TKey> ids, CancellationToken ct = default) =
 296        WriteAsync<IEnumerable<TKey>>(async () =>
 297        {
 298            var idList = ids.ToList();
 299            var matches = await Set.Where(e => idList.Contains(e.Id)).ToListAsync(ct).ConfigureAwait(false);
 2100            Set.RemoveRange(matches);
 2101            await context.SaveChangesAsync(ct).ConfigureAwait(false);
 2102
 6103            return matches.Select(e => e.Id).ToList();
 4104        });
 105
 106    /// <inheritdoc cref="Query" />
 2107    public IQueryable<T> Query() => Set.AsQueryable();
 108
 109    /// <inheritdoc />
 110    public DataOutput<IEnumerable<T>> GetAll() =>
 36111        Guarded(IEnumerable<T> () => Set.ToList());
 112
 113    /// <inheritdoc />
 114    public DataOutput<T?> GetById(TKey id) =>
 52115        Guarded(() => Set.FirstOrDefault(e => e.Id.Equals(id)));
 116
 117    /// <inheritdoc />
 64118    public DataOutput<TKey> Create(T entity) => Write(() =>
 64119    {
 64120        Set.Add(entity);
 64121        context.SaveChanges();
 56122        return entity.Id;
 64123    });
 124
 125    /// <inheritdoc />
 8126    public DataOutput<IEnumerable<TKey>> CreateRange(IEnumerable<T> entities) => Write(IEnumerable<TKey> () =>
 8127    {
 8128        var list = entities.ToList();
 8129        Set.AddRange(list);
 8130        context.SaveChanges();
 24131        return list.Select(e => e.Id).ToList();
 8132    });
 133
 134    /// <inheritdoc />
 20135    public DataOutput<T> Update(T entity) => Write(() =>
 20136    {
 20137        Set.Update(entity);
 20138        context.SaveChanges();
 12139        return entity;
 20140    });
 141
 142    /// <inheritdoc />
 0143    public DataOutput<IEnumerable<T>> UpdateRange(IEnumerable<T> entities) => Write(IEnumerable<T> () =>
 0144    {
 0145        var list = entities.ToList();
 0146        Set.UpdateRange(list);
 0147        context.SaveChanges();
 0148        return list;
 0149    });
 150
 151    /// <inheritdoc />
 4152    public DataOutput<TKey> Delete(T entity) => Write(() =>
 4153    {
 4154        Set.Remove(entity);
 4155        context.SaveChanges();
 2156        return entity.Id;
 4157    });
 158
 159    /// <inheritdoc />
 4160    public DataOutput<IEnumerable<TKey>> DeleteRange(IEnumerable<TKey> ids) => Write(IEnumerable<TKey> () =>
 4161    {
 4162        var idList = ids.ToList();
 4163        var matches = Set.Where(e => idList.Contains(e.Id)).ToList();
 4164        Set.RemoveRange(matches);
 4165        context.SaveChanges();
 10166        return matches.Select(e => e.Id).ToList();
 4167    });
 168
 169    /// <summary>
 170    ///     Maps an exception caught by a guard to an error envelope. Provider text - constraint
 171    ///     and index names, columns, SQL fragments, conflicting values - is classified but never
 172    ///     returned; EF Core logs the full exception for operators.
 173    /// </summary>
 174    private static DataOutput<TResult> Fail<TResult>(Exception ex) =>
 24175        DataOutput<TResult>.New.WithError(RelationalErrors.Describe(ex));
 176
 177    /// <summary>
 178    ///     Runs a write, converting failures to envelope errors. A failed save leaves its changes
 179    ///     pending in the change tracker, and the caller keeps using the same scoped context after an
 180    ///     error envelope, so the pending changes are discarded: otherwise the next save on the context
 181    ///     would replay the rejected change and fail again.
 182    /// </summary>
 183    private DataOutput<TResult> Write<TResult>(Func<TResult> operation)
 184    {
 185        try
 186        {
 100187            var output = Guarded(operation);
 100188            if (!output.Success)
 189            {
 18190                DiscardPendingChanges();
 191            }
 192
 100193            return output;
 194        }
 0195        catch (OperationCanceledException)
 196        {
 0197            DiscardPendingChanges();
 0198            throw;
 199        }
 100200    }
 201
 202    /// <inheritdoc cref="Write{TResult}" />
 203    private async Task<DataOutput<TResult>> WriteAsync<TResult>(Func<Task<TResult>> operation)
 204    {
 205        try
 206        {
 32207            var output = await GuardedAsync(operation).ConfigureAwait(false);
 32208            if (!output.Success)
 209            {
 2210                DiscardPendingChanges();
 211            }
 212
 32213            return output;
 214        }
 0215        catch (OperationCanceledException)
 216        {
 0217            DiscardPendingChanges();
 0218            throw;
 219        }
 32220    }
 221
 222    private void DiscardPendingChanges()
 223    {
 84224        foreach (var entry in context.ChangeTracker.Entries()
 32225                     .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
 20226                     .ToList())
 227        {
 22228            entry.State = EntityState.Detached;
 229        }
 20230    }
 231
 232    /// <summary>Runs a synchronous data operation, converting failures to envelope errors.</summary>
 233    protected static DataOutput<TResult> Guarded<TResult>(Func<TResult> operation)
 234    {
 235        try
 236        {
 144237            return DataOutput<TResult>.New.WithData(operation());
 238        }
 0239        catch (OperationCanceledException)
 240        {
 0241            throw;
 242        }
 243        catch (Exception ex)
 244        {
 22245            return Fail<TResult>(ex);
 246        }
 144247    }
 248
 249    /// <summary>Runs an asynchronous data operation, converting failures to envelope errors.</summary>
 250    protected static async Task<DataOutput<TResult>> GuardedAsync<TResult>(Func<Task<TResult>> operation)
 251    {
 252        try
 253        {
 48254            return DataOutput<TResult>.New.WithData(await operation().ConfigureAwait(false));
 255        }
 0256        catch (OperationCanceledException)
 257        {
 0258            throw;
 259        }
 260        catch (Exception ex)
 261        {
 2262            return Fail<TResult>(ex);
 263        }
 48264    }
 265}