如何修改基于表达式的过滤器以避免 Entity Framework Core 3.0 中的客户端评估
How to modify expression-based filters to avoid client-side evaluation in 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);
(Un)幸运的是,当我升级到 Entity Framework Core 3.0 时,代码抛出了一个 InvalidOperationException
,因为无法将表达式转换为SQL 查询(虽然它只过滤匹配数据库列的 属性):
System.InvalidOperationException: 'The LINQ expression
'Where(
source: DbSet,
predicate: (f) => Invoke(__filtering_0, f[DataLog]) )' could not be translated. Either rewrite the query in a form that can be
translated, or switch to client evaluation explicitly by inserting a
call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.
那么你能告诉我,我应该如何修改代码以确保所有(大部分)处理都在服务器端进行?保持通用代码同时符合标准的最佳做法是什么?
恭喜,您发现了 EF Core 3.0 中的重大变化之一 - LINQ queries are no longer evaluated on the client
Old behavior
Before 3.0, when EF Core couldn't convert an expression that was part of a query to either SQL or a parameter, it automatically evaluated the expression on the client. By default, client evaluation of potentially expensive expressions only triggered a warning.
New behavior
Starting with 3.0, EF Core only allows expressions in the top-level projection (the last Select() call in the query) to be evaluated on the client. When expressions in any other part of the query can't be converted to either SQL or a parameter, an exception is thrown.
查看文档(link 上面)了解更多信息,但升级前遇到的警告现在生成 InvalidOperationExceptions 并且与 SQLite 无关,你会得到相同的SQL 服务器问题。
解决此问题的唯一方法是确保您的过滤 expression/func 可以转换为适当的 SQL... 或恢复为 EF Core < 3.0
更新
您可以尝试 not 包装传递的 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);
我有以下代码用于将基于 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);
(Un)幸运的是,当我升级到 Entity Framework Core 3.0 时,代码抛出了一个 InvalidOperationException
,因为无法将表达式转换为SQL 查询(虽然它只过滤匹配数据库列的 属性):
System.InvalidOperationException: 'The LINQ expression 'Where( source: DbSet, predicate: (f) => Invoke(__filtering_0, f[DataLog]) )' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
那么你能告诉我,我应该如何修改代码以确保所有(大部分)处理都在服务器端进行?保持通用代码同时符合标准的最佳做法是什么?
恭喜,您发现了 EF Core 3.0 中的重大变化之一 - LINQ queries are no longer evaluated on the client
Old behavior
Before 3.0, when EF Core couldn't convert an expression that was part of a query to either SQL or a parameter, it automatically evaluated the expression on the client. By default, client evaluation of potentially expensive expressions only triggered a warning.
New behavior
Starting with 3.0, EF Core only allows expressions in the top-level projection (the last Select() call in the query) to be evaluated on the client. When expressions in any other part of the query can't be converted to either SQL or a parameter, an exception is thrown.
查看文档(link 上面)了解更多信息,但升级前遇到的警告现在生成 InvalidOperationExceptions 并且与 SQLite 无关,你会得到相同的SQL 服务器问题。
解决此问题的唯一方法是确保您的过滤 expression/func 可以转换为适当的 SQL... 或恢复为 EF Core < 3.0
更新
您可以尝试 not 包装传递的 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);