我正在重构一个 .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 可能不是一个好的做法,无论是代码/数据库调用性能还是代码质量。我欢迎任何建议和修复。
谢谢大家。
我认为,在您的具体情况下,
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);
}