将我的存储库从使用 Ardalis 重构为仅使用 .NET 6 库,并努力解决“Linq 表达式无法翻译”的问题

问题描述 投票:0回答:1

我正在重构一个 .NET 6/entity Framework core 7 项目,该项目用于通过 Ardalis 和 Ardalis 规范处理存储库调用,以向存储库调用添加条件。

此重构的想法是仅使用 Linq 表达式和 IQueryable 而不是 Ardalis 规范。我有一个通用的抽象存储库类,所有存储库都可以继承它。

在纸面上,我对结果很满意,我的代码编译得很好,不幸的是,当通过 Postman 对我的 API 路由进行一些测试时,我收到错误:“Linq Expression 无法翻译。要么以以下形式重写查询:可以进行翻译,或者通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用来显式切换到客户端评估”

我将向您展示我最初的代码:

public abstract class GenericDbContextRepository<T, TKey> : IRepository<T, TKey> where T : class
{
    protected readonly DbContext GenericDbContext;
    protected readonly DbSet<T> DbSet;

    protected GenericDbContextRepository(DbContext DbContext)
    {
        GenericDbContext = DbContext;
        DbSet = GenericDbContext.Set<T>();
    }

    public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> expression, IQueryable<T> query)
    {
        var originalQuery = DbSet.AsQueryable();
        var finalQuery = originalQuery.Concat(query);
        return await finalQuery.Where(expression).ToListAsync();
    }
}

例如,我的存储库“UserStoreRepository”将调用 FindAsync 并且它将传递以下 IQueryable :

public static IQueryable<UserStore> GetUserStoreWithItemsQuery()
{
    IQueryable<UserStore> initialQuery = Enumerable.Empty<UserStore>().AsQueryable();
    return initialQuery
        .OrderByDescending(x => x.CreationDate)
        .Take(1)
        .Include(UserStore => UserStore.Items);
}

第一次尝试时,我收到以下错误:

" 无法翻译 Linq 表达式 'EnumerableQuery{}'。请以可翻译的形式重写查询,或者通过插入对 'AsEnumerable'、'AsAsyncEnumerable'、'ToList' 的调用来显式切换到客户端计算,或“ToListAsync””

解决此问题的第一个想法就是按照错误消息中的建议并在 GetUserStoreWithItemsQuery() 函数中使用 AsEnumerable() ,我最终得到以下代码:

public static IQueryable<UserStore> GetUserStoreWithItemsQuery()
{
    IQueryable<UserStore> initialQuery = Enumerable.Empty<UserStore>().AsQueryable();
    
    return initialQuery
        .AsEnumerable()
        .OrderByDescending(x => x.CreationDate)
        .Take(1)
        .AsQueryable()
        //Include can only be used on IQueryable, not on IEnumerable
        .Include(UserStore => UserStore.Items);
}

Linq 表达式的翻译在这里有效,我的 FindAsync() 函数最终出现了一个新错误:

" 无法翻译 Linq 表达式 'DbSet{}'。请以可翻译的形式重写查询,或者通过插入对 'AsEnumerable'、'AsAsyncEnumerable'、'ToList' 的调用来显式切换到客户端计算,或“ToListAsync””

我想对 AsEnumerable() 应用相同的修复:

public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> expression, IQueryable<T> query)
{
    var originalQuery = DbSet.AsEnumerable();
    var finalQuery = originalQuery.Concat(query.AsEnumerable());
    return await finalQuery.AsQueryable().Where(expression).ToListAsync();
}

但现在我有以下错误:

源“IQueryable”的提供程序未实现“IAsyncQueryProvider”。只有实现“IAsyncQueryProvider”的提供程序才能用于实体框架异步操作。

我可能可以“绕过”并实现“IAsyncQueryProvider”,但我觉得我不必这样做,我猜一定有一种更好、更干净的方法来实现我想要的目标,并且我开始考虑切换到 Enumerable然后回到 IQueryable 可能不是一个好的做法,无论是代码/数据库调用性能还是代码质量。我欢迎任何建议和修复。

谢谢大家。

c# .net sql-server entity-framework linq
1个回答
0
投票

我认为,在您的具体情况下,

FindAsync
方法可能与此类似,请看一下:

public virtual Task<IEnumerable<TEntity>> FindAsync(
    Expression<Func<TEntity, bool>>? predicate = null,
    CancellationToken token = default,
    params Expression<Func<TEntity, object>>[] navigationProperties)
{
    IQueryable<TEntity> dbQuery = _dbSet.AsNoTracking();

    if (predicate != null)
    {
        dbQuery = dbQuery.Where(predicate);
    }

    return await navigationProperties
        .Aggregate(dbQuery, (current, navigationProperty) => current.Include(navigationProperty))
        .ToListAsync(token);
}
© www.soinside.com 2019 - 2024. All rights reserved.