首页 > 解决方案 > 如何修改基于表达式的过滤器以避免在 Entity Framework Core 3.0 中进行客户端评估

问题描述

我有以下代码用于将Func基于过滤器的过滤器转换为Expression和过滤Entity Framework Core 2.2中的数据:

public async Task<TType> GetDataAsync<TType>(Func<TType, bool> filtering = null) where TType : class
{
  Expression<Func<TType, bool>> filteringExpression = (type) => filtering(type);
  if (filtering != null)
    //return await myContext.Set<TType>().FirstOrDefaultAsync(filteringExpression);
    return await myContext.Set<TType>().Where(filteringExpression ).FirstOrDefaultAsync();
  return await myContext.Set<TType>().FirstOrDefaultAsync();
}

这就是我使用它的方式:

public async Task<DataLog> GetDataLogByID(Guid dataLogID) => await GetDataAsync<DataLog>(dataLog => dataLog.ID == dataLogID);

(不)幸运的是,当我升级到Entity Framework Core 3.0时,代码抛出了一个InvalidOperationException,因为表达式无法转换为 SQL 查询(尽管它只过滤与数据库列匹配的属性):

System.InvalidOperationException: '无法翻译 LINQ 表达式'Where(source: DbSet, predicate: (f) => Invoke(__filtering_0, f[DataLog]) )'。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038

那么你能告诉我,我应该如何修改代码以确保所有(大部分)处理都留在服务器端?保持通用代码但符合标准的最佳实践是什么?

标签: c#entity-framework-core-3.0

解决方案


恭喜,您发现了 EF Core 3.0 中的一项重大更改 -不再在客户端上评估 LINQ 查询

旧行为

在 3.0 之前,当 EF Core 无法将作为查询的一部分的表达式转换为 SQL 或参数时,它会自动评估客户端上的表达式。默认情况下,客户端对可能昂贵的表达式的评估只会触发警告。

新行为

从 3.0 开始,EF Core 仅允许在客户端评估顶级投影(查询中的最后一个 Select() 调用)中的表达式。当查询的任何其他部分中的表达式无法转换为 SQL 或参数时,将引发异常。

有关更多信息,请参阅文档(上面的链接),但是您在升级之前遇到的警告现在正在生成 InvalidOperationExceptions 并且与 SQLite 无关,您会遇到与 SQL Server 相同的问题。

解决此问题的唯一方法是确保您的过滤表达式/函数可以转换为适当的 SQL... 或恢复为 EF Core < 3.0

更新

您可以尝试包装传递的 Func 并将参数类型更改为Expression<Func<TType, bool>>(它不需要对调用该方法的代码进行任何更改)

public async Task<TType> GetDataAsync<TType>(Expression<Func<TType, bool>> filter = null)
    where TType : class
{
    var query = myContext.Set<TType>();

    if (filter != null)
        query = query.Where(filter);

    return await query.FirstOrDefaultAsync();
}

刚刚注意到调用GetDataAsync似乎不正确,并且有一个额外的类型参数Guid,应该从这个例子中删除。

public async Task<DataLog> GetDataLogByID(Guid dataLogID) =>
    await GetDataAsync<DataLog>(dataLog => dataLog.ID == dataLogID);

推荐阅读