< Summary

Information
Class: Elsa.Persistence.EFCore.Store<T1, T2>
Assembly: Elsa.Persistence.EFCore.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore.Common/Store.cs
Line coverage
45%
Covered lines: 120
Uncovered lines: 141
Coverable lines: 261
Total lines: 697
Line coverage: 45.9%
Branch coverage
43%
Covered branches: 52
Total branches: 120
Branch coverage: 43.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
CreateDbContextAsync()100%11100%
AddAsync()100%210%
AddAsync()100%44100%
AddManyAsync()100%210%
AddManyAsync()75%4473.33%
<AddManyAsync()100%22100%
SaveAsync()100%210%
SaveAsync()100%1010100%
SaveManyAsync()100%210%
SaveManyAsync()75%4484.61%
<SaveManyAsync()100%1212100%
HandleDbExceptionAsync()50%2266.66%
ExecuteBulkWriteWithSqlServerRetryAsync()50%11650%
ShouldRetrySqlServerBulkWrite(...)0%4260%
GetSqlServerBulkWriteRetryDelay(...)100%210%
UpdateAsync(...)100%210%
UpdateAsync()0%2040%
UpdatePartialAsync()0%2040%
FindAsync()100%210%
FindAsync()0%4260%
FindAsync()100%210%
FindAsync()100%11100%
FindAsync()100%210%
FindAsync()100%210%
FindManyAsync()100%210%
FindManyAsync()0%4260%
FindManyAsync()100%210%
FindManyAsync()0%156120%
ListAsync(...)100%210%
ListAsync()0%4260%
DeleteAsync()0%620%
DeleteWhereAsync()100%22100%
DeleteWhereAsync()100%22100%
QueryAsync()100%11100%
QueryAsync()100%210%
QueryAsync()100%11100%
QueryAsync()87.5%8890.9%
QueryAsync()100%11100%
QueryAsync()75%4487.5%
CountAsync()100%210%
CountAsync()0%2040%
AnyAsync()100%210%
AnyAsync()0%620%
CountAsync()100%210%
CountAsync()0%2040%
CountAsync()100%210%
CountAsync()0%2040%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore.Common/Store.cs

#LineLine coverage
 1using System.Linq.Expressions;
 2using Elsa.Common.Entities;
 3using Elsa.Common.Models;
 4using Elsa.Common.Multitenancy;
 5using Elsa.Persistence.EFCore.Extensions;
 6using Elsa.Extensions;
 7using JetBrains.Annotations;
 8using Microsoft.EntityFrameworkCore;
 9using Microsoft.Extensions.DependencyInjection;
 10using Open.Linq.AsyncExtensions;
 11
 12namespace Elsa.Persistence.EFCore;
 13
 14/// <summary>
 15/// A generic repository class around EF Core for accessing entities.
 16/// </summary>
 17/// <typeparam name="TDbContext">The type of the database context.</typeparam>
 18/// <typeparam name="TEntity">The type of the entity.</typeparam>
 19[PublicAPI]
 439020public class Store<TDbContext, TEntity>(IDbContextFactory<TDbContext> dbContextFactory, IServiceProvider serviceProvider
 21{
 22    private const int SqlServerBulkWriteMaxRetryCount = 3;
 623    private static readonly TimeSpan SqlServerBulkWriteBaseDelay = TimeSpan.FromMilliseconds(50);
 24
 25    // ReSharper disable once StaticMemberInGenericType
 26    // Justification: This is a static member that is used to ensure that only one thread can access the database for TE
 627    private static readonly SemaphoreSlim Semaphore = new(1, 1);
 28
 29    /// <summary>
 30    /// Creates a new instance of the database context.
 31    /// </summary>
 32    /// <param name="cancellationToken">The cancellation token.</param>
 33    /// <returns>The database context.</returns>
 2281934    public async Task<TDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default) => await dbContext
 35
 36    /// <summary>
 37    /// Adds the specified entity.
 38    /// </summary>
 39    /// <param name="entity">The entity to add.</param>
 40    /// <param name="cancellationToken">The cancellation token.</param>
 41    public async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default)
 42    {
 043        await AddAsync(entity, null, cancellationToken);
 044    }
 45
 46    /// <summary>
 47    /// Adds the specified entity.
 48    /// </summary>
 49    /// <param name="entity">The entity to add.</param>
 50    /// <param name="onAdding">The callback to invoke before adding the entity.</param>
 51    /// <param name="cancellationToken">The cancellation token.</param>
 52    public async Task AddAsync(TEntity entity, Func<TDbContext, TEntity, CancellationToken, ValueTask>? onAdding, Cancel
 53    {
 5154        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 55
 5156        if (onAdding != null)
 5157            await onAdding(dbContext, entity, cancellationToken);
 58
 5159        var set = dbContext.Set<TEntity>();
 5160        await set.AddAsync(entity, cancellationToken);
 5161        await dbContext.SaveChangesAsync(cancellationToken);
 5162    }
 63
 64    /// <summary>
 65    /// Adds the specified entities.
 66    /// </summary>
 67    /// <param name="entities">The entities to save.</param>
 68    /// <param name="cancellationToken">The cancellation token.</param>
 69    public async Task AddManyAsync(
 70        IEnumerable<TEntity> entities,
 71        CancellationToken cancellationToken = default)
 72    {
 073        await AddManyAsync(entities, null, cancellationToken);
 074    }
 75
 76    /// <summary>
 77    /// Adds the specified entities.
 78    /// </summary>
 79    /// <param name="entities">The entities to save.</param>
 80    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 81    /// <param name="cancellationToken">The cancellation token.</param>
 82    public async Task AddManyAsync(
 83        IEnumerable<TEntity> entities,
 84        Func<TDbContext, TEntity, CancellationToken, ValueTask>? onSaving = null,
 85        CancellationToken cancellationToken = default)
 86    {
 37887        await Semaphore.WaitAsync(cancellationToken);
 88
 89        try
 90        {
 37891            var entityList = entities.ToList();
 92
 37893            if (entityList.Count == 0)
 094                return;
 95
 37896            await ExecuteBulkWriteWithSqlServerRetryAsync(async (dbContext, ct) =>
 37897            {
 37898                if (onSaving != null)
 37899                {
 5608100                    var savingTasks = entityList.Select(entity => onSaving(dbContext, entity, ct).AsTask()).ToList();
 378101                    await Task.WhenAll(savingTasks);
 378102                }
 378103
 378104                await dbContext.BulkInsertAsync(entityList, ct);
 756105            }, cancellationToken);
 378106        }
 0107        catch (Exception ex)
 108        {
 0109            await HandleDbExceptionAsync(ex, cancellationToken);
 0110            throw;
 111        }
 112        finally
 113        {
 378114            Semaphore.Release();
 115        }
 378116    }
 117
 118    /// <summary>
 119    /// Saves the entity.
 120    /// </summary>
 121    /// <param name="entity">The entity to save.</param>
 122    /// <param name="keySelector">The key selector to get the primary key property.</param>
 123    /// <param name="cancellationToken">The cancellation token.</param>
 0124    public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, string>> keySelector, CancellationToken cancell
 125
 126    /// <summary>
 127    /// Saves the entity.
 128    /// </summary>
 129    /// <param name="entity">The entity to save.</param>
 130    /// <param name="keySelector">The key selector to get the primary key property.</param>
 131    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 132    /// <param name="cancellationToken">The cancellation token.</param>
 133    public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, string>> keySelector, Func<TDbContext, TEntity,
 134    {
 473135        await Semaphore.WaitAsync(cancellationToken); // Asynchronous wait
 136
 137        try
 138        {
 473139            await using var dbContext = await CreateDbContextAsync(cancellationToken);
 140
 473141            if (onSaving != null)
 473142                await onSaving(dbContext, entity, cancellationToken);
 143
 473144            var set = dbContext.Set<TEntity>();
 473145            var lambda = keySelector.BuildEqualsExpression(entity);
 473146            var exists = await set.AnyAsync(lambda, cancellationToken);
 473147            set.Entry(entity).State = exists ? EntityState.Modified : EntityState.Added;
 473148            await dbContext.SaveChangesAsync(cancellationToken);
 472149        }
 1150        catch (Exception ex)
 151        {
 1152            await HandleDbExceptionAsync(ex, cancellationToken);
 1153            throw;
 154        }
 155        finally
 156        {
 473157            Semaphore.Release();
 158        }
 472159    }
 160
 161    /// <summary>
 162    /// Saves the specified entities.
 163    /// </summary>
 164    /// <param name="entities">The entities to save.</param>
 165    /// <param name="keySelector">The key selector to get the primary key property.</param>
 166    /// <param name="cancellationToken">The cancellation token.</param>
 0167    public async Task SaveManyAsync(IEnumerable<TEntity> entities, Expression<Func<TEntity, string>> keySelector, Cancel
 168
 169    /// <summary>
 170    /// Saves the specified entities.
 171    /// </summary>
 172    /// <param name="entities">The entities to save.</param>
 173    /// <param name="keySelector">The key selector to get the primary key property.</param>
 174    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 175    /// <param name="cancellationToken">The cancellation token.</param>
 176    public async Task SaveManyAsync(
 177        IEnumerable<TEntity> entities,
 178        Expression<Func<TEntity, string>> keySelector,
 179        Func<TDbContext, TEntity, CancellationToken, ValueTask>? onSaving = null,
 180        CancellationToken cancellationToken = default)
 181    {
 3186182        await Semaphore.WaitAsync(cancellationToken);
 183
 184        try
 185        {
 3186186            var entityList = entities.ToList();
 187
 3186188            if (entityList.Count == 0)
 0189                return;
 190
 3186191            var tenantId = serviceProvider.GetRequiredService<ITenantAccessor>().TenantId;
 192
 3186193            await ExecuteBulkWriteWithSqlServerRetryAsync(async (dbContext, ct) =>
 3186194            {
 3186195                if (onSaving != null)
 3186196                {
 8734197                    var savingTasks = entityList.Select(entity => onSaving(dbContext, entity, ct).AsTask()).ToList();
 3186198                    await Task.WhenAll(savingTasks);
 3186199                }
 3186200
 3186201                // When doing a custom SQL query (Bulk Upsert), none of the installed query filters will be applied. Hen
 17468202                foreach (var entity in entityList)
 3186203                {
 5548204                    if (entity is Entity entityWithTenant)
 3186205                    {
 3186206                        // Don't touch tenant-agnostic entities (marked with "*")
 5548207                        if (entityWithTenant.TenantId == Tenant.AgnosticTenantId)
 3186208                            continue;
 3186209
 3186210                        // Apply current tenant ID to entities without one
 3280211                        if (entityWithTenant.TenantId == null && tenantId != null)
 2725212                            entityWithTenant.TenantId = tenantId;
 3186213                    }
 3186214                }
 3186215
 3186216                await dbContext.BulkUpsertAsync(entityList, keySelector, ct);
 6372217            }, cancellationToken);
 3186218        }
 0219        catch (Exception ex)
 220        {
 0221            await HandleDbExceptionAsync(ex, cancellationToken);
 0222            throw;
 223        }
 224        finally
 225        {
 3186226            Semaphore.Release();
 227        }
 3186228    }
 229
 230    private async Task HandleDbExceptionAsync(Exception exception, CancellationToken cancellationToken)
 231    {
 1232        var handler = serviceProvider.GetService<IDbExceptionHandler>();
 233
 1234        if (handler == null)
 1235            return;
 236
 0237        var context = new DbUpdateExceptionContext(exception, cancellationToken);
 0238        await handler.HandleAsync(context);
 1239    }
 240
 241    private async Task ExecuteBulkWriteWithSqlServerRetryAsync(
 242        Func<TDbContext, CancellationToken, Task> operation,
 243        CancellationToken cancellationToken)
 244    {
 3564245        for (var attempt = 0;; attempt++)
 246        {
 3564247            var providerName = string.Empty;
 248
 249            try
 250            {
 3564251                await using var dbContext = await CreateDbContextAsync(cancellationToken);
 3564252                providerName = dbContext.Database.ProviderName ?? string.Empty;
 3564253                await operation(dbContext, cancellationToken);
 3564254                return;
 0255            }
 0256            catch (Exception ex)
 257            {
 0258                if (ShouldRetrySqlServerBulkWrite(providerName, ex, attempt, cancellationToken))
 259                {
 0260                    await Task.Delay(GetSqlServerBulkWriteRetryDelay(attempt), cancellationToken);
 0261                    continue;
 262                }
 263
 0264                throw;
 265            }
 0266        }
 3564267    }
 268
 269    private static bool ShouldRetrySqlServerBulkWrite(string providerName, Exception exception, int attempt, Cancellatio
 270    {
 0271        return attempt < SqlServerBulkWriteMaxRetryCount
 0272               && !cancellationToken.IsCancellationRequested
 0273               && exception is not OperationCanceledException
 0274               && DbExceptionClassifier.IsSqlServerTransient(providerName, exception);
 275    }
 276
 0277    private static TimeSpan GetSqlServerBulkWriteRetryDelay(int attempt) => TimeSpan.FromMilliseconds(SqlServerBulkWrite
 278
 279    /// <summary>
 280    /// Updates the entity.
 281    /// </summary>
 282    /// <param name="entity">The entity to update.</param>
 283    /// <param name="cancellationToken">The cancellation token.</param>
 284    public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
 285    {
 0286        return UpdateAsync(entity, null, cancellationToken);
 287    }
 288
 289    /// <summary>
 290    /// Updates the entity.
 291    /// </summary>
 292    /// <param name="entity">The entity to update.</param>
 293    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 294    /// <param name="cancellationToken">The cancellation token.</param>
 295    public async Task UpdateAsync(TEntity entity, Func<TDbContext, TEntity, CancellationToken, ValueTask>? onSaving, Can
 296    {
 0297        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 298
 0299        if (onSaving != null)
 0300            await onSaving(dbContext, entity, cancellationToken);
 301
 0302        var set = dbContext.Set<TEntity>();
 0303        set.Entry(entity).State = EntityState.Modified;
 0304        await dbContext.SaveChangesAsync(cancellationToken);
 0305    }
 306
 307    /// <summary>
 308    /// Updates specific properties of an entity in the database.
 309    /// </summary>
 310    /// <param name="entity">The entity to update.</param>
 311    /// <param name="properties">An array of expressions indicating the properties to update.</param>
 312    /// <param name="cancellationToken">The cancellation token.</param>
 313    /// <returns>A task that represents the asynchronous operation.</returns>
 314    public async Task UpdatePartialAsync(TEntity entity, Expression<Func<TEntity, object>>[] properties, CancellationTok
 315    {
 0316        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0317        dbContext.Attach(entity);
 318
 0319        foreach (var property in properties)
 0320            dbContext.Entry(entity).Property(property).IsModified = true;
 321
 0322        await dbContext.SaveChangesAsync(cancellationToken);
 0323    }
 324
 325    /// <summary>
 326    /// Finds the entity matching the specified predicate.
 327    /// </summary>
 328    /// <param name="predicate">The predicate to use.</param>
 329    /// <param name="cancellationToken">The cancellation token.</param>
 330    /// <returns>The entity if found, otherwise <c>null</c>.</returns>
 0331    public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken
 332
 333    /// <summary>
 334    /// Finds the entity matching the specified predicate.
 335    /// </summary>
 336    /// <param name="predicate">The predicate to use.</param>
 337    /// <param name="onLoading">A callback to run after the entity is loaded</param>
 338    /// <param name="cancellationToken">The cancellation token.</param>
 339    /// <returns></returns>
 340    public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, Func<TDbContext, TEntity?, TEntity?
 341    {
 0342        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0343        var set = dbContext.Set<TEntity>().AsNoTracking();
 0344        var entity = await set.FirstOrDefaultAsync(predicate, cancellationToken);
 345
 0346        if (entity == null)
 0347            return null;
 348
 0349        if (onLoading != null)
 0350            entity = onLoading.Invoke(dbContext, entity);
 351
 0352        return entity;
 0353    }
 354
 355    /// <summary>
 356    /// Finds a single entity using a query
 357    /// </summary>
 358    /// <param name="query">The query to use</param>
 359    /// <param name="onLoading">A callback to run after the entity is loaded</param>
 360    /// <param name="cancellationToken">The cancellation token</param>
 361    /// <returns>The entity if found, otherwise <c>null</c></returns>
 362    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbContext, TEntity
 363    {
 0364        return await FindAsync(query, onLoading, false, cancellationToken);
 0365    }
 366
 367    /// <summary>
 368    /// Finds a single entity using a query
 369    /// </summary>
 370    /// <param name="query">The query to use</param>
 371    /// <param name="onLoading">A callback to run after the entity is loaded</param>
 372    /// <param name="tenantAgnostic">Define is the request should be tenant agnostic or not</param>
 373    /// <param name="cancellationToken">The cancellation token</param>
 374    /// <returns>The entity if found, otherwise <c>null</c></returns>
 375    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbContext, TEntity
 376    {
 2377        return await QueryAsync(query, onLoading, tenantAgnostic, cancellationToken).FirstOrDefault();
 2378    }
 379
 380    /// <summary>
 381    /// Finds a single entity using a query
 382    /// </summary>
 383    /// <param name="query">The query to use</param>
 384    /// <param name="cancellationToken">The cancellation token</param>
 385    /// <returns>The entity if found, otherwise <c>null</c></returns>
 386    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel
 387    {
 0388        return await FindAsync(query, false, cancellationToken);
 0389    }
 390
 391    /// <summary>
 392    /// Finds a single entity using a query
 393    /// </summary>
 394    /// <param name="query">The query to use</param>
 395    /// <param name="tenantAgnostic">Define is the request should be tenant agnostic or not</param>
 396    /// <param name="cancellationToken">The cancellation token</param>
 397    /// <returns>The entity if found, otherwise <c>null</c></returns>
 398    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, bool tenantAgnostic = fa
 399    {
 0400        return await QueryAsync(query, tenantAgnostic, cancellationToken).FirstOrDefault();
 0401    }
 402
 403    /// <summary>
 404    /// Finds a list of entities using a query
 405    /// </summary>
 0406    public async Task<IEnumerable<TEntity>> FindManyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken c
 407
 408    /// <summary>
 409    /// Finds a list of entities using a query
 410    /// </summary>
 411    public async Task<IEnumerable<TEntity>> FindManyAsync(Expression<Func<TEntity, bool>> predicate, Action<TDbContext, 
 412    {
 0413        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0414        var set = dbContext.Set<TEntity>().AsNoTracking();
 0415        var entities = await set.Where(predicate).ToListAsync(cancellationToken);
 416
 0417        if (onLoading != null)
 0418            foreach (var entity in entities)
 0419                onLoading(dbContext, entity);
 420
 0421        return entities;
 0422    }
 423
 424    /// <summary>
 425    /// Finds a list of entities using a query
 426    /// </summary>
 427    public async Task<Page<TEntity>> FindManyAsync<TKey>(
 428        Expression<Func<TEntity, bool>> predicate,
 429        Expression<Func<TEntity, TKey>> orderBy,
 430        OrderDirection orderDirection = OrderDirection.Ascending,
 431        PageArgs? pageArgs = null,
 432        CancellationToken cancellationToken = default) =>
 0433        await FindManyAsync(predicate, orderBy, orderDirection, pageArgs, null, cancellationToken);
 434
 435    /// <summary>
 436    /// Returns a list of entities using a query
 437    /// </summary>
 438    public async Task<Page<TEntity>> FindManyAsync<TKey>(
 439        Expression<Func<TEntity, bool>>? predicate,
 440        Expression<Func<TEntity, TKey>>? orderBy,
 441        OrderDirection orderDirection = OrderDirection.Ascending,
 442        PageArgs? pageArgs = null,
 443        Func<TDbContext, TEntity?, TEntity?>? onLoading = null,
 444        CancellationToken cancellationToken = default)
 445    {
 0446        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0447        var set = dbContext.Set<TEntity>().AsNoTracking();
 448
 0449        if (predicate != null)
 0450            set = set.Where(predicate);
 451
 0452        if (orderBy != null)
 0453            set = orderDirection switch
 0454            {
 0455                OrderDirection.Ascending => set.OrderBy(orderBy),
 0456                OrderDirection.Descending => set.OrderByDescending(orderBy),
 0457                _ => set.OrderBy(orderBy)
 0458            };
 459
 0460        var page = await set.PaginateAsync(pageArgs);
 461
 0462        if (onLoading != null)
 0463            page = page with
 0464            {
 0465                Items = page.Items.Select(x => onLoading(dbContext, x)!).ToList()
 0466            };
 467
 0468        return page;
 0469    }
 470
 471    public Task<IEnumerable<TEntity>> ListAsync(CancellationToken cancellationToken = default)
 472    {
 0473        return ListAsync(null, cancellationToken);
 474    }
 475
 476    public async Task<IEnumerable<TEntity>> ListAsync(Action<TDbContext, TEntity?>? onLoading = null, CancellationToken 
 477    {
 0478        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0479        var set = dbContext.Set<TEntity>().AsNoTracking();
 0480        var entities = await set.ToListAsync(cancellationToken);
 481
 0482        if (onLoading != null)
 0483            foreach (var entity in entities)
 0484                onLoading(dbContext, entity);
 485
 0486        return entities;
 0487    }
 488
 489    /// <summary>
 490    /// Finds a single entity using a query.
 491    /// </summary>
 492    /// <returns>True if the entity was found, otherwise false.</returns>
 493    public async Task<bool> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default)
 494    {
 0495        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0496        var set = dbContext.Set<TEntity>();
 0497        set.Attach(entity).State = EntityState.Deleted;
 0498        return await dbContext.SaveChangesAsync(cancellationToken) == 1;
 0499    }
 500
 501    /// <summary>
 502    /// Deletes entities using a predicate.
 503    /// </summary>
 504    /// <returns>The number of entities deleted.</returns>
 505    public async Task<long> DeleteWhereAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationTo
 506    {
 4507        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 4508        var set = dbContext.Set<TEntity>().AsNoTracking();
 4509        return await set.Where(predicate).ExecuteDeleteAsync(cancellationToken);
 4510    }
 511
 512    /// <summary>
 513    /// Deletes entities using a query.
 514    /// </summary>
 515    /// <returns>The number of entities deleted.</returns>
 516    public async Task<long> DeleteWhereAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken can
 517    {
 115518        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 115519        var set = dbContext.Set<TEntity>().AsNoTracking();
 115520        var queryable = query(set.AsQueryable());
 115521        return await queryable.ExecuteDeleteAsync(cancellationToken);
 115522    }
 523
 524    /// <summary>
 525    /// Queries the database using a query.
 526    /// </summary>
 527    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Cancellatio
 528    {
 340529        return await QueryAsync(query, null, false, cancellationToken);
 340530    }
 531
 532    /// <summary>
 533    /// Queries the database using a query.
 534    /// </summary>
 535    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, bool tenant
 536    {
 0537        return await QueryAsync(query, null, tenantAgnostic, cancellationToken);
 0538    }
 539
 540    /// <summary>
 541    /// Queries the database using a query and a selector.
 542    /// </summary>
 543    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbCon
 544    {
 389545        return await QueryAsync(query, onLoading, false, cancellationToken);
 389546    }
 547
 548    /// <summary>
 549    /// Queries the database using a query and a selector.
 550    /// </summary>
 551    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbCon
 552    {
 18510553        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 18510554        var asNoTracking = onLoading == null;
 18510555        var set = asNoTracking ? dbContext.Set<TEntity>().AsNoTracking() : dbContext.Set<TEntity>();
 18510556        var queryable = query(set.AsQueryable());
 557
 18510558        if (ignoreQueryFilters)
 0559            queryable = queryable.IgnoreQueryFilters();
 560
 18510561        var entities = await queryable.ToListAsync(cancellationToken);
 562
 18510563        if (onLoading != null)
 564        {
 35385565            var loadingTasks = entities.Select(entity => onLoading(dbContext, entity, cancellationToken).AsTask()).ToLis
 18170566            await Task.WhenAll(loadingTasks);
 567        }
 568
 18510569        return entities;
 18510570    }
 571
 572    /// <summary>
 573    /// Queries the database using a query and a selector.
 574    /// </summary>
 575    public async Task<IEnumerable<TResult>> QueryAsync<TResult>(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Ex
 576    {
 8577        return await QueryAsync(query, selector, false, cancellationToken);
 8578    }
 579
 580    /// <summary>
 581    /// Queries the database using a query and a selector.
 582    /// </summary>
 583    public async Task<IEnumerable<TResult>> QueryAsync<TResult>(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Ex
 584    {
 8585        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 8586        var set = dbContext.Set<TEntity>().AsNoTracking();
 8587        var queryable = query(set.AsQueryable());
 588
 8589        if (ignoreQueryFilters)
 0590            queryable = queryable.IgnoreQueryFilters();
 591
 8592        queryable = query(queryable);
 8593        return await queryable.Select(selector).ToListAsync(cancellationToken);
 8594    }
 595
 596    /// <summary>
 597    /// Counts the number of entities matching a query.
 598    /// </summary>
 599    public async Task<long> CountAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancellat
 600    {
 0601        return await CountAsync(query, false, cancellationToken);
 0602    }
 603
 604    /// <summary>
 605    /// Counts the number of entities matching a query.
 606    /// </summary>
 607    public async Task<long> CountAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, bool ignoreQueryFilters = f
 608    {
 0609        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0610        var set = dbContext.Set<TEntity>().AsNoTracking();
 0611        var queryable = query(set.AsQueryable());
 612
 0613        if (ignoreQueryFilters)
 0614            queryable = queryable.IgnoreQueryFilters();
 615
 0616        queryable = query(queryable);
 0617        return await queryable.LongCountAsync(cancellationToken: cancellationToken);
 0618    }
 619
 620    /// <summary>
 621    /// Checks if any entities exist.
 622    /// </summary>
 623    public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = de
 624    {
 0625        return await AnyAsync(predicate, false, cancellationToken);
 0626    }
 627
 628    /// <summary>
 629    /// Checks if any entities exist.
 630    /// </summary>
 631    public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, bool ignoreQueryFilters = false, Cancell
 632    {
 0633        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0634        var set = dbContext.Set<TEntity>().AsNoTracking();
 0635        return await set.AnyAsync(predicate, cancellationToken);
 0636    }
 637
 638    /// <summary>
 639    /// Counts the number of entities matching a predicate.
 640    /// </summary>
 641    /// <param name="predicate">The predicate.</param>
 642    /// <param name="cancellationToken">The cancellation token.</param>
 643    public async Task<long> CountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = 
 644    {
 0645        return await CountAsync(predicate, false, cancellationToken);
 0646    }
 647
 648    /// <summary>
 649    /// Counts the number of entities matching a predicate.
 650    /// </summary>
 651    /// <param name="predicate">The predicate.</param>
 652    /// <param name="ignoreQueryFilters">Whether to ignore query filters.</param>
 653    /// <param name="cancellationToken">The cancellation token.</param>
 654    public async Task<long> CountAsync(Expression<Func<TEntity, bool>> predicate, bool ignoreQueryFilters = false, Cance
 655    {
 0656        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0657        var queryable = dbContext.Set<TEntity>().AsNoTracking();
 658
 0659        if (ignoreQueryFilters)
 0660            queryable = queryable.IgnoreQueryFilters();
 661
 0662        return await queryable.CountAsync(predicate, cancellationToken);
 0663    }
 664
 665    /// <summary>
 666    /// Counts the distinct number of entities matching a predicate.
 667    /// </summary>
 668    /// <param name="predicate">The predicate.</param>
 669    /// <param name="propertySelector">The property selector to distinct by.</param>
 670    /// <param name="cancellationToken">The cancellation token.</param>
 671    public async Task<long> CountAsync<TProperty>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TP
 672    {
 0673        return await CountAsync(predicate, propertySelector, false, cancellationToken);
 0674    }
 675
 676    /// <summary>
 677    /// Counts the distinct number of entities matching a predicate.
 678    /// </summary>
 679    /// <param name="predicate">The predicate.</param>
 680    /// <param name="propertySelector">The property selector to distinct by.</param>
 681    /// <param name="ignoreQueryFilters">Whether to ignore query filters.</param>
 682    /// <param name="cancellationToken">The cancellation token.</param>
 683    public async Task<long> CountAsync<TProperty>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TP
 684    {
 0685        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0686        var queryable = dbContext.Set<TEntity>().AsNoTracking();
 687
 0688        if (ignoreQueryFilters)
 0689            queryable = queryable.IgnoreQueryFilters();
 690
 0691        return await queryable
 0692            .Where(predicate)
 0693            .Select(propertySelector)
 0694            .Distinct()
 0695            .CountAsync(cancellationToken);
 0696    }
 697}