如何修改基于表达式的过滤器以避免 Entity Framework Core 3.0 中的客户端计算

Nes*_*tor 3 c# entity-framework-core-3.0

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

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();
}
Run Code Online (Sandbox Code Playgroud)

这就是我的使用方式:

public async Task<DataLog> GetDataLogByID(Guid dataLogID) => await GetDataAsync<DataLog>(dataLog => dataLog.ID == dataLogID);
Run Code Online (Sandbox Code Playgroud)

(不)幸运的是,当我升级到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 。

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

phu*_*uzi 5

恭喜,您发现了 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();
}
Run Code Online (Sandbox Code Playgroud)

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

public async Task<DataLog> GetDataLogByID(Guid dataLogID) =>
    await GetDataAsync<DataLog>(dataLog => dataLog.ID == dataLogID);
Run Code Online (Sandbox Code Playgroud)