< 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
40%
Covered lines: 86
Uncovered lines: 127
Coverable lines: 213
Total lines: 636
Line coverage: 40.3%
Branch coverage
46%
Covered branches: 52
Total branches: 112
Branch coverage: 46.4%
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()83.33%6687.5%
SaveAsync()100%210%
SaveAsync()91.66%121288.88%
SaveManyAsync()100%210%
SaveManyAsync()85%292071.42%
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()100%88100%
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]
 493420public class Store<TDbContext, TEntity>(IDbContextFactory<TDbContext> dbContextFactory, IServiceProvider serviceProvider
 21{
 22    // ReSharper disable once StaticMemberInGenericType
 23    // Justification: This is a static member that is used to ensure that only one thread can access the database for TE
 424    private static readonly SemaphoreSlim Semaphore = new(1, 1);
 25
 26    /// <summary>
 27    /// Creates a new instance of the database context.
 28    /// </summary>
 29    /// <param name="cancellationToken">The cancellation token.</param>
 30    /// <returns>The database context.</returns>
 4668331    public async Task<TDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default) => await dbContext
 32
 33    /// <summary>
 34    /// Adds the specified entity.
 35    /// </summary>
 36    /// <param name="entity">The entity to add.</param>
 37    /// <param name="cancellationToken">The cancellation token.</param>
 38    public async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default)
 39    {
 040        await AddAsync(entity, null, cancellationToken);
 041    }
 42
 43    /// <summary>
 44    /// Adds the specified entity.
 45    /// </summary>
 46    /// <param name="entity">The entity to add.</param>
 47    /// <param name="onAdding">The callback to invoke before adding the entity.</param>
 48    /// <param name="cancellationToken">The cancellation token.</param>
 49    public async Task AddAsync(TEntity entity, Func<TDbContext, TEntity, CancellationToken, ValueTask>? onAdding, Cancel
 50    {
 5151        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 52
 5153        if (onAdding != null)
 5154            await onAdding(dbContext, entity, cancellationToken);
 55
 5156        var set = dbContext.Set<TEntity>();
 5157        await set.AddAsync(entity, cancellationToken);
 5158        await dbContext.SaveChangesAsync(cancellationToken);
 5159    }
 60
 61    /// <summary>
 62    /// Adds the specified entities.
 63    /// </summary>
 64    /// <param name="entities">The entities to save.</param>
 65    /// <param name="cancellationToken">The cancellation token.</param>
 66    public async Task AddManyAsync(
 67        IEnumerable<TEntity> entities,
 68        CancellationToken cancellationToken = default)
 69    {
 070        await AddManyAsync(entities, null, cancellationToken);
 071    }
 72
 73    /// <summary>
 74    /// Adds the specified entities.
 75    /// </summary>
 76    /// <param name="entities">The entities to save.</param>
 77    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 78    /// <param name="cancellationToken">The cancellation token.</param>
 79    public async Task AddManyAsync(
 80        IEnumerable<TEntity> entities,
 81        Func<TDbContext, TEntity, CancellationToken, ValueTask>? onSaving = null,
 82        CancellationToken cancellationToken = default)
 83    {
 38784        var entityList = entities.ToList();
 85
 38786        if (entityList.Count == 0)
 087            return;
 88
 38789        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 90
 38791        if (onSaving != null)
 92        {
 567193            var savingTasks = entityList.Select(entity => onSaving(dbContext, entity, cancellationToken).AsTask()).ToLis
 38794            await Task.WhenAll(savingTasks);
 95        }
 96
 38797        await dbContext.BulkInsertAsync(entityList, cancellationToken);
 38798    }
 99
 100    /// <summary>
 101    /// Saves the entity.
 102    /// </summary>
 103    /// <param name="entity">The entity to save.</param>
 104    /// <param name="keySelector">The key selector to get the primary key property.</param>
 105    /// <param name="cancellationToken">The cancellation token.</param>
 0106    public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, string>> keySelector, CancellationToken cancell
 107
 108    /// <summary>
 109    /// Saves the entity.
 110    /// </summary>
 111    /// <param name="entity">The entity to save.</param>
 112    /// <param name="keySelector">The key selector to get the primary key property.</param>
 113    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 114    /// <param name="cancellationToken">The cancellation token.</param>
 115    public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, string>> keySelector, Func<TDbContext, TEntity,
 116    {
 483117        await Semaphore.WaitAsync(cancellationToken); // Asynchronous wait
 118
 119        try
 120        {
 483121            await using var dbContext = await CreateDbContextAsync(cancellationToken);
 122
 483123            if (onSaving != null)
 483124                await onSaving(dbContext, entity, cancellationToken);
 125
 483126            var set = dbContext.Set<TEntity>();
 483127            var lambda = keySelector.BuildEqualsExpression(entity);
 483128            var exists = await set.AnyAsync(lambda, cancellationToken);
 483129            set.Entry(entity).State = exists ? EntityState.Modified : EntityState.Added;
 483130            await dbContext.SaveChangesAsync(cancellationToken);
 482131        }
 1132        catch (Exception ex)
 133        {
 1134            var handler = serviceProvider.GetService<IDbExceptionHandler>();
 135
 1136            if (handler != null)
 137            {
 0138                var context = new DbUpdateExceptionContext(ex, cancellationToken);
 0139                await handler.HandleAsync(context);
 140            }
 141
 1142            throw;
 143        }
 144        finally
 145        {
 483146            Semaphore.Release();
 147        }
 482148    }
 149
 150    /// <summary>
 151    /// Saves the specified entities.
 152    /// </summary>
 153    /// <param name="entities">The entities to save.</param>
 154    /// <param name="keySelector">The key selector to get the primary key property.</param>
 155    /// <param name="cancellationToken">The cancellation token.</param>
 0156    public async Task SaveManyAsync(IEnumerable<TEntity> entities, Expression<Func<TEntity, string>> keySelector, Cancel
 157
 158    /// <summary>
 159    /// Saves the specified entities.
 160    /// </summary>
 161    /// <param name="entities">The entities to save.</param>
 162    /// <param name="keySelector">The key selector to get the primary key property.</param>
 163    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 164    /// <param name="cancellationToken">The cancellation token.</param>
 165    public async Task SaveManyAsync(
 166        IEnumerable<TEntity> entities,
 167        Expression<Func<TEntity, string>> keySelector,
 168        Func<TDbContext, TEntity, CancellationToken, ValueTask>? onSaving = null,
 169        CancellationToken cancellationToken = default)
 170    {
 3186171        var entityList = entities.ToList();
 172
 3186173        if (entityList.Count == 0)
 1066174            return;
 175
 2120176        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 177
 2120178        if (onSaving != null)
 179        {
 6617180            var savingTasks = entityList.Select(entity => onSaving(dbContext, entity, cancellationToken).AsTask()).ToLis
 2120181            await Task.WhenAll(savingTasks);
 182        }
 183
 184        // When doing a custom SQL query (Bulk Upsert), none of the installed query filters will be applied. Hence, we a
 2120185        var tenantId = serviceProvider.GetRequiredService<ITenantAccessor>().Tenant?.Id;
 13234186        foreach (var entity in entityList)
 187        {
 4497188            if (entity is Entity entityWithTenant)
 189            {
 190                // Don't touch tenant-agnostic entities (marked with "*")
 4497191                if (entityWithTenant.TenantId == Tenant.AgnosticTenantId)
 192                    continue;
 193
 194                // Apply current tenant ID to entities without one
 4497195                if (entityWithTenant.TenantId == null && tenantId != null)
 2800196                    entityWithTenant.TenantId = tenantId;
 197            }
 198        }
 199
 200        try
 201        {
 2120202            await dbContext.BulkUpsertAsync(entityList, keySelector, cancellationToken);
 2120203        }
 0204        catch (Exception ex)
 205        {
 0206            var handler = serviceProvider.GetService<IDbExceptionHandler>();
 207
 0208            if (handler != null)
 209            {
 0210                var context = new DbUpdateExceptionContext(ex, cancellationToken);
 0211                await handler.HandleAsync(context);
 212            }
 213
 0214            throw;
 215        }
 3186216    }
 217
 218    /// <summary>
 219    /// Updates the entity.
 220    /// </summary>
 221    /// <param name="entity">The entity to update.</param>
 222    /// <param name="cancellationToken">The cancellation token.</param>
 223    public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
 224    {
 0225        return UpdateAsync(entity, null, cancellationToken);
 226    }
 227
 228    /// <summary>
 229    /// Updates the entity.
 230    /// </summary>
 231    /// <param name="entity">The entity to update.</param>
 232    /// <param name="onSaving">The callback to invoke before saving the entity.</param>
 233    /// <param name="cancellationToken">The cancellation token.</param>
 234    public async Task UpdateAsync(TEntity entity, Func<TDbContext, TEntity, CancellationToken, ValueTask>? onSaving, Can
 235    {
 0236        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 237
 0238        if (onSaving != null)
 0239            await onSaving(dbContext, entity, cancellationToken);
 240
 0241        var set = dbContext.Set<TEntity>();
 0242        set.Entry(entity).State = EntityState.Modified;
 0243        await dbContext.SaveChangesAsync(cancellationToken);
 0244    }
 245
 246    /// <summary>
 247    /// Updates specific properties of an entity in the database.
 248    /// </summary>
 249    /// <param name="entity">The entity to update.</param>
 250    /// <param name="properties">An array of expressions indicating the properties to update.</param>
 251    /// <param name="cancellationToken">The cancellation token.</param>
 252    /// <returns>A task that represents the asynchronous operation.</returns>
 253    public async Task UpdatePartialAsync(TEntity entity, Expression<Func<TEntity, object>>[] properties, CancellationTok
 254    {
 0255        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0256        dbContext.Attach(entity);
 257
 0258        foreach (var property in properties)
 0259            dbContext.Entry(entity).Property(property).IsModified = true;
 260
 0261        await dbContext.SaveChangesAsync(cancellationToken);
 0262    }
 263
 264    /// <summary>
 265    /// Finds the entity matching the specified predicate.
 266    /// </summary>
 267    /// <param name="predicate">The predicate to use.</param>
 268    /// <param name="cancellationToken">The cancellation token.</param>
 269    /// <returns>The entity if found, otherwise <c>null</c>.</returns>
 0270    public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken
 271
 272    /// <summary>
 273    /// Finds the entity matching the specified predicate.
 274    /// </summary>
 275    /// <param name="predicate">The predicate to use.</param>
 276    /// <param name="onLoading">A callback to run after the entity is loaded</param>
 277    /// <param name="cancellationToken">The cancellation token.</param>
 278    /// <returns></returns>
 279    public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, Func<TDbContext, TEntity?, TEntity?
 280    {
 0281        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0282        var set = dbContext.Set<TEntity>().AsNoTracking();
 0283        var entity = await set.FirstOrDefaultAsync(predicate, cancellationToken);
 284
 0285        if (entity == null)
 0286            return null;
 287
 0288        if (onLoading != null)
 0289            entity = onLoading.Invoke(dbContext, entity);
 290
 0291        return entity;
 0292    }
 293
 294    /// <summary>
 295    /// Finds a single entity using a query
 296    /// </summary>
 297    /// <param name="query">The query to use</param>
 298    /// <param name="onLoading">A callback to run after the entity is loaded</param>
 299    /// <param name="cancellationToken">The cancellation token</param>
 300    /// <returns>The entity if found, otherwise <c>null</c></returns>
 301    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbContext, TEntity
 302    {
 0303        return await FindAsync(query, onLoading, false, cancellationToken);
 0304    }
 305
 306    /// <summary>
 307    /// Finds a single entity using a query
 308    /// </summary>
 309    /// <param name="query">The query to use</param>
 310    /// <param name="onLoading">A callback to run after the entity is loaded</param>
 311    /// <param name="tenantAgnostic">Define is the request should be tenant agnostic or not</param>
 312    /// <param name="cancellationToken">The cancellation token</param>
 313    /// <returns>The entity if found, otherwise <c>null</c></returns>
 314    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbContext, TEntity
 315    {
 2316        return await QueryAsync(query, onLoading, tenantAgnostic, cancellationToken).FirstOrDefault();
 2317    }
 318
 319    /// <summary>
 320    /// Finds a single entity using a query
 321    /// </summary>
 322    /// <param name="query">The query to use</param>
 323    /// <param name="cancellationToken">The cancellation token</param>
 324    /// <returns>The entity if found, otherwise <c>null</c></returns>
 325    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel
 326    {
 0327        return await FindAsync(query, false, cancellationToken);
 0328    }
 329
 330    /// <summary>
 331    /// Finds a single entity using a query
 332    /// </summary>
 333    /// <param name="query">The query to use</param>
 334    /// <param name="tenantAgnostic">Define is the request should be tenant agnostic or not</param>
 335    /// <param name="cancellationToken">The cancellation token</param>
 336    /// <returns>The entity if found, otherwise <c>null</c></returns>
 337    public async Task<TEntity?> FindAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, bool tenantAgnostic = fa
 338    {
 0339        return await QueryAsync(query, tenantAgnostic, cancellationToken).FirstOrDefault();
 0340    }
 341
 342    /// <summary>
 343    /// Finds a list of entities using a query
 344    /// </summary>
 0345    public async Task<IEnumerable<TEntity>> FindManyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken c
 346
 347    /// <summary>
 348    /// Finds a list of entities using a query
 349    /// </summary>
 350    public async Task<IEnumerable<TEntity>> FindManyAsync(Expression<Func<TEntity, bool>> predicate, Action<TDbContext, 
 351    {
 0352        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0353        var set = dbContext.Set<TEntity>().AsNoTracking();
 0354        var entities = await set.Where(predicate).ToListAsync(cancellationToken);
 355
 0356        if (onLoading != null)
 0357            foreach (var entity in entities)
 0358                onLoading(dbContext, entity);
 359
 0360        return entities;
 0361    }
 362
 363    /// <summary>
 364    /// Finds a list of entities using a query
 365    /// </summary>
 366    public async Task<Page<TEntity>> FindManyAsync<TKey>(
 367        Expression<Func<TEntity, bool>> predicate,
 368        Expression<Func<TEntity, TKey>> orderBy,
 369        OrderDirection orderDirection = OrderDirection.Ascending,
 370        PageArgs? pageArgs = null,
 371        CancellationToken cancellationToken = default) =>
 0372        await FindManyAsync(predicate, orderBy, orderDirection, pageArgs, null, cancellationToken);
 373
 374    /// <summary>
 375    /// Returns a list of entities using a query
 376    /// </summary>
 377    public async Task<Page<TEntity>> FindManyAsync<TKey>(
 378        Expression<Func<TEntity, bool>>? predicate,
 379        Expression<Func<TEntity, TKey>>? orderBy,
 380        OrderDirection orderDirection = OrderDirection.Ascending,
 381        PageArgs? pageArgs = null,
 382        Func<TDbContext, TEntity?, TEntity?>? onLoading = null,
 383        CancellationToken cancellationToken = default)
 384    {
 0385        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0386        var set = dbContext.Set<TEntity>().AsNoTracking();
 387
 0388        if (predicate != null)
 0389            set = set.Where(predicate);
 390
 0391        if (orderBy != null)
 0392            set = orderDirection switch
 0393            {
 0394                OrderDirection.Ascending => set.OrderBy(orderBy),
 0395                OrderDirection.Descending => set.OrderByDescending(orderBy),
 0396                _ => set.OrderBy(orderBy)
 0397            };
 398
 0399        var page = await set.PaginateAsync(pageArgs);
 400
 0401        if (onLoading != null)
 0402            page = page with
 0403            {
 0404                Items = page.Items.Select(x => onLoading(dbContext, x)!).ToList()
 0405            };
 406
 0407        return page;
 0408    }
 409
 410    public Task<IEnumerable<TEntity>> ListAsync(CancellationToken cancellationToken = default)
 411    {
 0412        return ListAsync(null, cancellationToken);
 413    }
 414
 415    public async Task<IEnumerable<TEntity>> ListAsync(Action<TDbContext, TEntity?>? onLoading = null, CancellationToken 
 416    {
 0417        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0418        var set = dbContext.Set<TEntity>().AsNoTracking();
 0419        var entities = await set.ToListAsync(cancellationToken);
 420
 0421        if (onLoading != null)
 0422            foreach (var entity in entities)
 0423                onLoading(dbContext, entity);
 424
 0425        return entities;
 0426    }
 427
 428    /// <summary>
 429    /// Finds a single entity using a query.
 430    /// </summary>
 431    /// <returns>True if the entity was found, otherwise false.</returns>
 432    public async Task<bool> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default)
 433    {
 0434        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0435        var set = dbContext.Set<TEntity>();
 0436        set.Attach(entity).State = EntityState.Deleted;
 0437        return await dbContext.SaveChangesAsync(cancellationToken) == 1;
 0438    }
 439
 440    /// <summary>
 441    /// Deletes entities using a predicate.
 442    /// </summary>
 443    /// <returns>The number of entities deleted.</returns>
 444    public async Task<long> DeleteWhereAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationTo
 445    {
 4446        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 4447        var set = dbContext.Set<TEntity>().AsNoTracking();
 4448        return await set.Where(predicate).ExecuteDeleteAsync(cancellationToken);
 4449    }
 450
 451    /// <summary>
 452    /// Deletes entities using a query.
 453    /// </summary>
 454    /// <returns>The number of entities deleted.</returns>
 455    public async Task<long> DeleteWhereAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken can
 456    {
 117457        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 117458        var set = dbContext.Set<TEntity>().AsNoTracking();
 117459        var queryable = query(set.AsQueryable());
 117460        return await queryable.ExecuteDeleteAsync(cancellationToken);
 117461    }
 462
 463    /// <summary>
 464    /// Queries the database using a query.
 465    /// </summary>
 466    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Cancellatio
 467    {
 464468        return await QueryAsync(query, null, false, cancellationToken);
 464469    }
 470
 471    /// <summary>
 472    /// Queries the database using a query.
 473    /// </summary>
 474    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, bool tenant
 475    {
 0476        return await QueryAsync(query, null, tenantAgnostic, cancellationToken);
 0477    }
 478
 479    /// <summary>
 480    /// Queries the database using a query and a selector.
 481    /// </summary>
 482    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbCon
 483    {
 22876484        return await QueryAsync(query, onLoading, false, cancellationToken);
 22876485    }
 486
 487    /// <summary>
 488    /// Queries the database using a query and a selector.
 489    /// </summary>
 490    public async Task<IEnumerable<TEntity>> QueryAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Func<TDbCon
 491    {
 41552492        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 41552493        var asNoTracking = onLoading == null;
 41552494        var set = asNoTracking ? dbContext.Set<TEntity>().AsNoTracking() : dbContext.Set<TEntity>();
 41552495        var queryable = query(set.AsQueryable());
 496
 41552497        if (ignoreQueryFilters)
 7498            queryable = queryable.IgnoreQueryFilters();
 499
 41552500        var entities = await queryable.ToListAsync(cancellationToken);
 501
 41552502        if (onLoading != null)
 503        {
 130584504            var loadingTasks = entities.Select(entity => onLoading(dbContext, entity, cancellationToken).AsTask()).ToLis
 41088505            await Task.WhenAll(loadingTasks);
 506        }
 507
 41552508        return entities;
 41552509    }
 510
 511    /// <summary>
 512    /// Queries the database using a query and a selector.
 513    /// </summary>
 514    public async Task<IEnumerable<TResult>> QueryAsync<TResult>(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Ex
 515    {
 8516        return await QueryAsync(query, selector, false, cancellationToken);
 8517    }
 518
 519    /// <summary>
 520    /// Queries the database using a query and a selector.
 521    /// </summary>
 522    public async Task<IEnumerable<TResult>> QueryAsync<TResult>(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, Ex
 523    {
 8524        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 8525        var set = dbContext.Set<TEntity>().AsNoTracking();
 8526        var queryable = query(set.AsQueryable());
 527
 8528        if (ignoreQueryFilters)
 0529            queryable = queryable.IgnoreQueryFilters();
 530
 8531        queryable = query(queryable);
 8532        return await queryable.Select(selector).ToListAsync(cancellationToken);
 8533    }
 534
 535    /// <summary>
 536    /// Counts the number of entities matching a query.
 537    /// </summary>
 538    public async Task<long> CountAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancellat
 539    {
 0540        return await CountAsync(query, false, cancellationToken);
 0541    }
 542
 543    /// <summary>
 544    /// Counts the number of entities matching a query.
 545    /// </summary>
 546    public async Task<long> CountAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, bool ignoreQueryFilters = f
 547    {
 0548        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0549        var set = dbContext.Set<TEntity>().AsNoTracking();
 0550        var queryable = query(set.AsQueryable());
 551
 0552        if (ignoreQueryFilters)
 0553            queryable = queryable.IgnoreQueryFilters();
 554
 0555        queryable = query(queryable);
 0556        return await queryable.LongCountAsync(cancellationToken: cancellationToken);
 0557    }
 558
 559    /// <summary>
 560    /// Checks if any entities exist.
 561    /// </summary>
 562    public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = de
 563    {
 0564        return await AnyAsync(predicate, false, cancellationToken);
 0565    }
 566
 567    /// <summary>
 568    /// Checks if any entities exist.
 569    /// </summary>
 570    public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, bool ignoreQueryFilters = false, Cancell
 571    {
 0572        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0573        var set = dbContext.Set<TEntity>().AsNoTracking();
 0574        return await set.AnyAsync(predicate, cancellationToken);
 0575    }
 576
 577    /// <summary>
 578    /// Counts the number of entities matching a predicate.
 579    /// </summary>
 580    /// <param name="predicate">The predicate.</param>
 581    /// <param name="cancellationToken">The cancellation token.</param>
 582    public async Task<long> CountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = 
 583    {
 0584        return await CountAsync(predicate, false, cancellationToken);
 0585    }
 586
 587    /// <summary>
 588    /// Counts the number of entities matching a predicate.
 589    /// </summary>
 590    /// <param name="predicate">The predicate.</param>
 591    /// <param name="ignoreQueryFilters">Whether to ignore query filters.</param>
 592    /// <param name="cancellationToken">The cancellation token.</param>
 593    public async Task<long> CountAsync(Expression<Func<TEntity, bool>> predicate, bool ignoreQueryFilters = false, Cance
 594    {
 0595        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0596        var queryable = dbContext.Set<TEntity>().AsNoTracking();
 597
 0598        if (ignoreQueryFilters)
 0599            queryable = queryable.IgnoreQueryFilters();
 600
 0601        return await queryable.CountAsync(predicate, cancellationToken);
 0602    }
 603
 604    /// <summary>
 605    /// Counts the distinct number of entities matching a predicate.
 606    /// </summary>
 607    /// <param name="predicate">The predicate.</param>
 608    /// <param name="propertySelector">The property selector to distinct by.</param>
 609    /// <param name="cancellationToken">The cancellation token.</param>
 610    public async Task<long> CountAsync<TProperty>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TP
 611    {
 0612        return await CountAsync(predicate, propertySelector, false, cancellationToken);
 0613    }
 614
 615    /// <summary>
 616    /// Counts the distinct number of entities matching a predicate.
 617    /// </summary>
 618    /// <param name="predicate">The predicate.</param>
 619    /// <param name="propertySelector">The property selector to distinct by.</param>
 620    /// <param name="ignoreQueryFilters">Whether to ignore query filters.</param>
 621    /// <param name="cancellationToken">The cancellation token.</param>
 622    public async Task<long> CountAsync<TProperty>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TP
 623    {
 0624        await using var dbContext = await CreateDbContextAsync(cancellationToken);
 0625        var queryable = dbContext.Set<TEntity>().AsNoTracking();
 626
 0627        if (ignoreQueryFilters)
 0628            queryable = queryable.IgnoreQueryFilters();
 629
 0630        return await queryable
 0631            .Where(predicate)
 0632            .Select(propertySelector)
 0633            .Distinct()
 0634            .CountAsync(cancellationToken);
 0635    }
 636}