< Summary - ZhEaIsNsAaBn.Libraries coverage (affected: 23 packages)

Information
Class: ZhEaIsNsAaBn.Infrastructure.Core.BaseRepository<T1, T2>
Assembly: ZhEaIsNsAaBn.Infrastructure.Core
File(s): /home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.Infrastructure.Core/Repositories/BaseRepository.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 382
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.Infrastructure.Core/Repositories/BaseRepository.cs

#LineLine coverage
 1namespace ZhEaIsNsAaBn.Infrastructure.Core;
 2
 3/// <summary><see cref="BaseRepository{TDbContext, TEntity, TId}"/> for <see cref="int"/>-keyed entities.</summary>
 4/// <typeparam name="TDbContext">The application's context.</typeparam>
 5/// <typeparam name="TEntity">The entity type.</typeparam>
 6public abstract class BaseRepository< TDbContext, TEntity > : BaseRepository<TDbContext, TEntity, int>
 7    where TEntity : class, IBaseEntity
 8    where TDbContext : DbContext
 9{
 10    /// <inheritdoc />
 7211    protected BaseRepository( IDbContextProvider<TDbContext> dbContextProvider ) : base( dbContextProvider ) { }
 12}
 13
 14/// <summary>
 15/// Base for repositories: protected write helpers (single-shot <c>*AndSave</c> methods return <see cref="LayerResponse{
 16/// and paging. Every helper uses the provider's shared context unless a <c>dbContext</c> is passed.
 17/// </summary>
 18/// <remarks>
 19/// A <see cref="DbContext"/> is not thread-safe. For parallel work, give each task its own context
 20/// (<c>GetDbContextAsync( singleInstance: false )</c>), pass it as <c>dbContext</c>, and dispose it when the task ends.
 21/// </remarks>
 22/// <typeparam name="TDbContext">The application's context.</typeparam>
 23/// <typeparam name="TEntity">The entity type.</typeparam>
 24/// <typeparam name="TId">The key type.</typeparam>
 25public abstract class BaseRepository< TDbContext, TEntity, TId >
 26    where TEntity : class, IBaseEntity<TId>
 27    where TDbContext : DbContext
 28    where TId : struct, IEquatable<TId>
 29{
 30    /// <summary>Creates the repository.</summary>
 31    /// <param name="dbContextProvider">The context provider.</param>
 32    /// <exception cref="ArgumentNullException"><paramref name="dbContextProvider"/> is <see langword="null"/>.</excepti
 33    protected BaseRepository( IDbContextProvider<TDbContext> dbContextProvider )
 34    {
 35        ArgumentNullException.ThrowIfNull( dbContextProvider );
 36
 37        DbContextProvider = dbContextProvider;
 38    }
 39
 40
 41    /// <summary>The context provider.</summary>
 42    protected IDbContextProvider<TDbContext> DbContextProvider { get; }
 43
 44
 45    /// <summary>Marks <paramref name="entity"/> as added. Nothing is saved.</summary>
 46    /// <param name="entity">The entity.</param>
 47    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 48    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 49    protected virtual void Add( TEntity entity, TDbContext? dbContext = null, CancellationToken cancellationToken = defa
 50        => ( dbContext ?? GetDbContext( cancellationToken ) ).Set<TEntity>().Add( entity );
 51
 52    /// <summary>Adds <paramref name="entity"/> and saves.</summary>
 53    /// <param name="entity">The entity.</param>
 54    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 55    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 56    /// <returns>The new key, or the classified database error.</returns>
 57    protected virtual LayerResponse<TId> AddAndSave( TEntity entity, TDbContext? dbContext = null, CancellationToken can
 58    {
 59        dbContext ??= GetDbContext( cancellationToken );
 60        dbContext.Set<TEntity>().Add( entity );
 61
 62
 63        var saved = SaveChanges( dbContext );
 64
 65
 66        return saved.IsSuccess ? entity.Id : saved.Errors;
 67    }
 68
 69    /// <summary>Async version of <see cref="AddAndSave"/>.</summary>
 70    /// <param name="entity">The entity.</param>
 71    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 72    /// <param name="cancellationToken">Token to cancel the operation.</param>
 73    /// <returns>The new key, or the classified database error.</returns>
 74    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 75    protected virtual async ValueTask<LayerResponse<TId>> AddAndSaveAsync( TEntity entity, TDbContext? dbContext = null,
 76    {
 77        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 78        await dbContext.Set<TEntity>().AddAsync( entity, cancellationToken ).ConfigureAwait( false );
 79
 80
 81        var saved = await SaveChangesAsync( dbContext, cancellationToken ).ConfigureAwait( false );
 82
 83
 84        return saved.IsSuccess ? entity.Id : saved.Errors;
 85    }
 86
 87    /// <summary>Marks <paramref name="entity"/> as added, letting async value generators run. Nothing is saved.</summar
 88    /// <param name="entity">The entity.</param>
 89    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 90    /// <param name="cancellationToken">Token to cancel the operation.</param>
 91    /// <returns>The entity's change-tracking entry.</returns>
 92    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 93    protected virtual async ValueTask<EntityEntry<TEntity>> AddAsync( TEntity entity, TDbContext? dbContext = null, Canc
 94    {
 95        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 96
 97
 98        return await dbContext.Set<TEntity>().AddAsync( entity, cancellationToken ).ConfigureAwait( false );
 99    }
 100
 101    /// <summary>Marks <paramref name="entities"/> as added. Nothing is saved.</summary>
 102    /// <param name="entities">The entities.</param>
 103    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 104    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 105    protected virtual void AddRange( IEnumerable<TEntity> entities, TDbContext? dbContext = null, CancellationToken canc
 106        => ( dbContext ?? GetDbContext( cancellationToken ) ).Set<TEntity>().AddRange( entities );
 107
 108    /// <summary>Adds <paramref name="entities"/> and saves.</summary>
 109    /// <param name="entities">The entities.</param>
 110    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 111    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 112    /// <returns>The number of rows written, or the classified database error.</returns>
 113    protected virtual LayerResponse<int> AddRangeAndSave( IEnumerable<TEntity> entities, TDbContext? dbContext = null, C
 114    {
 115        dbContext ??= GetDbContext( cancellationToken );
 116        dbContext.Set<TEntity>().AddRange( entities );
 117
 118
 119        return SaveChanges( dbContext );
 120    }
 121
 122    /// <summary>Async version of <see cref="AddRangeAndSave"/>.</summary>
 123    /// <param name="entities">The entities.</param>
 124    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 125    /// <param name="cancellationToken">Token to cancel the operation.</param>
 126    /// <returns>The number of rows written, or the classified database error.</returns>
 127    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 128    protected virtual async Task<LayerResponse<int>> AddRangeAndSaveAsync( IEnumerable<TEntity> entities, TDbContext? db
 129    {
 130        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 131        dbContext.Set<TEntity>().AddRange( entities );
 132
 133
 134        return await SaveChangesAsync( dbContext, cancellationToken ).ConfigureAwait( false );
 135    }
 136
 137    /// <summary>Marks <paramref name="entity"/> as deleted. Nothing is saved.</summary>
 138    /// <param name="entity">The entity.</param>
 139    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 140    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 141    protected virtual void Delete( TEntity entity, TDbContext? dbContext = null, CancellationToken cancellationToken = d
 142        => ( dbContext ?? GetDbContext( cancellationToken ) ).Set<TEntity>().Remove( entity );
 143
 144    /// <summary>Deletes <paramref name="entity"/> and saves.</summary>
 145    /// <param name="entity">The entity.</param>
 146    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 147    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 148    /// <returns>The number of rows written, or the classified database error.</returns>
 149    protected virtual LayerResponse<int> DeleteAndSave( TEntity entity, TDbContext? dbContext = null, CancellationToken 
 150    {
 151        dbContext ??= GetDbContext( cancellationToken );
 152        dbContext.Set<TEntity>().Remove( entity );
 153
 154
 155        return SaveChanges( dbContext );
 156    }
 157
 158    /// <summary>Async version of <see cref="DeleteAndSave"/>.</summary>
 159    /// <param name="entity">The entity.</param>
 160    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 161    /// <param name="cancellationToken">Token to cancel the operation.</param>
 162    /// <returns>The number of rows written, or the classified database error.</returns>
 163    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 164    protected virtual async Task<LayerResponse<int>> DeleteAndSaveAsync( TEntity entity, TDbContext? dbContext = null, C
 165    {
 166        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 167        dbContext.Set<TEntity>().Remove( entity );
 168
 169
 170        return await SaveChangesAsync( dbContext, cancellationToken ).ConfigureAwait( false );
 171    }
 172
 173    /// <summary>Marks <paramref name="entities"/> as deleted. Nothing is saved.</summary>
 174    /// <param name="entities">The entities.</param>
 175    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 176    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 177    protected virtual void DeleteRange( IEnumerable<TEntity> entities, TDbContext? dbContext = null, CancellationToken c
 178        => ( dbContext ?? GetDbContext( cancellationToken ) ).Set<TEntity>().RemoveRange( entities );
 179
 180    /// <summary>Returns the shared context.</summary>
 181    /// <param name="cancellationToken">Token to cancel waiting for the provider.</param>
 182    /// <returns>The shared context.</returns>
 183    protected virtual TDbContext GetDbContext( CancellationToken cancellationToken = default ) => DbContextProvider.GetD
 184
 185    /// <summary>Async version of <see cref="GetDbContext"/>.</summary>
 186    /// <param name="cancellationToken">Token to cancel the operation.</param>
 187    /// <returns>The shared context.</returns>
 188    protected virtual Task<TDbContext> GetDbContextAsync( CancellationToken cancellationToken = default ) => DbContextPr
 189
 190    /// <summary>Returns a page of <paramref name="query"/>, read without tracking.</summary>
 191    /// <param name="query">The query, already filtered and ordered.</param>
 192    /// <param name="page">1-based page number; values below 1 read page 1.</param>
 193    /// <param name="pageSize">Rows per page.</param>
 194    /// <returns>The page.</returns>
 195    /// <exception cref="ArgumentOutOfRangeException"><paramref name="pageSize"/> is not positive.</exception>
 196    protected PagedResult<TEntity> GetPaged( IQueryable<TEntity> query, int page, int pageSize )
 197    {
 198        ArgumentOutOfRangeException.ThrowIfNegativeOrZero( pageSize );
 199
 200        page  = Math.Max( page, 1 );
 201        query = query.AsNoTracking();
 202
 203
 204        var count = query.Count();
 205
 206
 207        return new PagedResult<TEntity>
 208                   {
 209                       PageNumber = page,
 210                       PageSize   = pageSize,
 211                       TotalCount = count,
 212                       TotalPages = (int) Math.Ceiling( (double) count / pageSize ),
 213                       Items      = query.Skip( ( page - 1 ) * pageSize ).Take( pageSize ).ToList()
 214                   };
 215    }
 216
 217    /// <summary>Async version of <see cref="GetPaged"/>.</summary>
 218    /// <param name="query">The query, already filtered and ordered.</param>
 219    /// <param name="page">1-based page number; values below 1 read page 1.</param>
 220    /// <param name="pageSize">Rows per page.</param>
 221    /// <param name="cancellationToken">Token to cancel the operation.</param>
 222    /// <returns>The page.</returns>
 223    /// <exception cref="ArgumentOutOfRangeException"><paramref name="pageSize"/> is not positive.</exception>
 224    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 225    protected async Task<PagedResult<TEntity>> GetPagedAsync( IQueryable<TEntity> query, int page, int pageSize, Cancell
 226    {
 227        ArgumentOutOfRangeException.ThrowIfNegativeOrZero( pageSize );
 228
 229        page  = Math.Max( page, 1 );
 230        query = query.AsNoTracking();
 231
 232
 233        var count = await query.CountAsync( cancellationToken ).ConfigureAwait( false );
 234        var items = await query.Skip( ( page - 1 ) * pageSize ).Take( pageSize ).ToListAsync( cancellationToken ).Config
 235
 236
 237        return new PagedResult<TEntity>
 238                   {
 239                       PageNumber = page,
 240                       PageSize   = pageSize,
 241                       TotalCount = count,
 242                       TotalPages = (int) Math.Ceiling( (double) count / pageSize ),
 243                       Items      = items
 244                   };
 245    }
 246
 247    /// <summary>Returns the entity set of the shared context, for composing queries.</summary>
 248    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 249    /// <returns>The entity set.</returns>
 250    protected virtual IQueryable<TEntity> GetQueryable( CancellationToken cancellationToken = default ) => GetDbContext(
 251
 252    /// <summary>Async version of <see cref="GetQueryable"/>.</summary>
 253    /// <param name="cancellationToken">Token to cancel the operation.</param>
 254    /// <returns>The entity set.</returns>
 255    protected virtual async Task<IQueryable<TEntity>> GetQueryableAsync( CancellationToken cancellationToken = default )
 256        => ( await GetDbContextAsync( cancellationToken ).ConfigureAwait( false ) ).Set<TEntity>();
 257
 258    /// <summary>
 259    /// Returns the entity with key <paramref name="id"/>, <b>tracked</b>: changes to it are saved by the next <c>SaveCh
 260    /// on the same context. Reads are no-tracking everywhere else.
 261    /// </summary>
 262    /// <param name="id">The key.</param>
 263    /// <param name="includes">Navigations to include; <see langword="null"/> for none.</param>
 264    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 265    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 266    /// <returns>The tracked entity, or <see langword="null"/>.</returns>
 267    protected virtual TEntity? GetTracking(
 268            TId id, IEnumerable<Expression<Func<TEntity, object>>>? includes = null, TDbContext? dbContext = null, Cance
 269        )
 270        => ( dbContext ?? GetDbContext( cancellationToken ) ).Query( tracking: true, null, includes?.ToArray() ).FirstOr
 271
 272    /// <summary>Async version of <see cref="GetTracking"/>.</summary>
 273    /// <param name="id">The key.</param>
 274    /// <param name="includes">Navigations to include; <see langword="null"/> for none.</param>
 275    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 276    /// <param name="cancellationToken">Token to cancel the query.</param>
 277    /// <returns>The tracked entity, or <see langword="null"/>.</returns>
 278    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 279    protected virtual async Task<TEntity?> GetTrackingAsync(
 280            TId id, IEnumerable<Expression<Func<TEntity, object>>>? includes = null, TDbContext? dbContext = null, Cance
 281        )
 282    {
 283        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 284
 285
 286        return await dbContext.Query( tracking: true, null, includes?.ToArray() ).FirstOrDefaultAsync( t => t.Id.Equals(
 287    }
 288
 289    /// <summary>Saves <paramref name="dbContext"/> through the shared error ladder.</summary>
 290    /// <param name="dbContext">The context.</param>
 291    /// <returns>The number of rows written, or the classified database error.</returns>
 292    protected static LayerResponse<int> SaveChanges( TDbContext dbContext )
 293    {
 294        ArgumentNullException.ThrowIfNull( dbContext );
 295
 296        return SafeExecution.ExecuteSafe( () => dbContext.SaveChanges(), dbContext.GetDbErrorClassifier() );
 297    }
 298
 299    /// <summary>Async version of <see cref="SaveChanges"/>.</summary>
 300    /// <param name="dbContext">The context.</param>
 301    /// <param name="cancellationToken">Token to cancel the save.</param>
 302    /// <returns>The number of rows written, or the classified database error.</returns>
 303    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 304    protected static Task<LayerResponse<int>> SaveChangesAsync( TDbContext dbContext, CancellationToken cancellationToke
 305    {
 306        ArgumentNullException.ThrowIfNull( dbContext );
 307
 308        return SafeExecution.ExecuteSafeAsync( () => dbContext.SaveChangesAsync( cancellationToken ), dbContext.GetDbErr
 309    }
 310
 311    /// <summary>Marks <paramref name="entity"/> as modified. Nothing is saved.</summary>
 312    /// <param name="entity">The entity.</param>
 313    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 314    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 315    protected virtual void Update( TEntity entity, TDbContext? dbContext = null, CancellationToken cancellationToken = d
 316        => ( dbContext ?? GetDbContext( cancellationToken ) ).Set<TEntity>().Update( entity );
 317
 318    /// <summary>Updates <paramref name="entity"/> and saves.</summary>
 319    /// <param name="entity">The entity.</param>
 320    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 321    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 322    /// <returns>The number of rows written, or the classified database error.</returns>
 323    protected virtual LayerResponse<int> UpdateAndSave( TEntity entity, TDbContext? dbContext = null, CancellationToken 
 324    {
 325        dbContext ??= GetDbContext( cancellationToken );
 326        dbContext.Set<TEntity>().Update( entity );
 327
 328
 329        return SaveChanges( dbContext );
 330    }
 331
 332    /// <summary>Async version of <see cref="UpdateAndSave"/>.</summary>
 333    /// <param name="entity">The entity.</param>
 334    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 335    /// <param name="cancellationToken">Token to cancel the operation.</param>
 336    /// <returns>The number of rows written, or the classified database error.</returns>
 337    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 338    protected virtual async Task<LayerResponse<int>> UpdateAndSaveAsync( TEntity entity, TDbContext? dbContext = null, C
 339    {
 340        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 341        dbContext.Set<TEntity>().Update( entity );
 342
 343
 344        return await SaveChangesAsync( dbContext, cancellationToken ).ConfigureAwait( false );
 345    }
 346
 347    /// <summary>Marks <paramref name="entities"/> as modified. Nothing is saved.</summary>
 348    /// <param name="entities">The entities.</param>
 349    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 350    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 351    protected virtual void UpdateRange( IEnumerable<TEntity> entities, TDbContext? dbContext = null, CancellationToken c
 352        => ( dbContext ?? GetDbContext( cancellationToken ) ).Set<TEntity>().UpdateRange( entities );
 353
 354    /// <summary>Updates <paramref name="entities"/> and saves.</summary>
 355    /// <param name="entities">The entities.</param>
 356    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 357    /// <param name="cancellationToken">Token to cancel waiting for the context.</param>
 358    /// <returns>The number of rows written, or the classified database error.</returns>
 359    protected virtual LayerResponse<int> UpdateRangeAndSave( IEnumerable<TEntity> entities, TDbContext? dbContext = null
 360    {
 361        dbContext ??= GetDbContext( cancellationToken );
 362        dbContext.Set<TEntity>().UpdateRange( entities );
 363
 364
 365        return SaveChanges( dbContext );
 366    }
 367
 368    /// <summary>Async version of <see cref="UpdateRangeAndSave"/>.</summary>
 369    /// <param name="entities">The entities.</param>
 370    /// <param name="dbContext">The context to use; <see langword="null"/> for the shared one.</param>
 371    /// <param name="cancellationToken">Token to cancel the operation.</param>
 372    /// <returns>The number of rows written, or the classified database error.</returns>
 373    /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was cancelled.</exception>
 374    protected virtual async Task<LayerResponse<int>> UpdateRangeAndSaveAsync( IEnumerable<TEntity> entities, TDbContext?
 375    {
 376        dbContext ??= await GetDbContextAsync( cancellationToken ).ConfigureAwait( false );
 377        dbContext.Set<TEntity>().UpdateRange( entities );
 378
 379
 380        return await SaveChangesAsync( dbContext, cancellationToken ).ConfigureAwait( false );
 381    }
 382}