如何修改基于表达式的筛选器以避免在Entity Framework Core 3.0中进行客户端评估

时间:2019-10-09 10:30:45

标签: c# 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 时,由于表达式不能转换为SQL查询,因此代码抛出了InvalidOperationException(尽管它只过滤了一个与数据库列匹配的属性):

  

System.InvalidOperationException:'LINQ表达式   '哪里(       来源:DbSet,       谓词:(f)=>调用(__filtering_0,f [DataLog]))'无法翻译。可以采用以下形式重写查询:   翻译,或通过插入   调用AsEnumerable(),AsAsyncEnumerable(),ToList()或   ToListAsync()。有关详情,请参见https://go.microsoft.com/fwlink/?linkid=2101038   更多信息。

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

1 个答案:

答案 0 :(得分:2)

恭喜,您已经发现EF Core 3.0中的一项重大更改-https://docs.microsoft.com/en-us/office/vba/language/concepts/getting-started/64-bit-visual-basic-for-applications-overview

  

旧行为

     

在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);