.NET 6升级后EF.Functions.Like方法报错:不支持客户端评估
你遇到的InvalidOperationException: The Like method is not supported because the query has switched to client-evaluation异常,核心原因是你将表达式树编译成了委托,导致EF Core无法解析EF.Functions.Like并转换为SQL语句,只能退化为客户端求值,而.NET 6的EF Core默认禁止这种无法翻译的客户端求值行为(ASP.NET Core 3.1的EF Core对这类情况限制更宽松)。
具体来说,代码中的var predicateCompiled = predicate.Compile();把Expression<Func<Evidence, bool>>转换成了Func<Evidence, bool>,当你把这个委托传给Where方法时,EF Core无法分析其中的表达式逻辑,自然无法将EF.Functions.Like翻译成SQL的LIKE操作符,只能在内存中过滤数据,而此时EF.Functions.Like无法在客户端环境中执行,最终抛出异常。
解决方案
1. 移除Compile()调用,直接使用表达式树
EF Core的Where方法支持直接传入Expression<Func<T, bool>>,这样EF Core可以完整解析表达式树并转换为对应的SQL语句。修改你的查询方法:
public static IQueryable<Evidence> QueryEvidences(this EvidenceContext Context, EvidenceQuery query) { var predicate = PredicateBuilder.True<Evidence>(); predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.ReleasedToOwner && x.EvidenceStatus != EvidenceStatus.Transfer); if (query.EvidenceStatus is not EvidenceStatus.Disposed) { predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.Disposed); } if (query.EvidenceStatus is not EvidenceStatus.Incoming) { predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.Incoming); } var predicateAndOr = query.IsOr ? PredicateBuilder.False<Evidence>() : PredicateBuilder.True<Evidence>(); if (!string.IsNullOrWhiteSpace(query.CaseNumber)) { predicateAndOr = predicateAndOr.AndOr(x => EF.Functions.Like(x.CaseFileNumber, $"%{query.CaseNumber}%"), query.IsOr); } predicate = predicate.And(predicateAndOr); // 直接使用predicate表达式树,不要调用Compile() return query.IncludeRequests ? Context.Evidences.Include(x => x.Requests).AsNoTracking().Where(predicate).OrderByDescending(x => x.Modified) : Context.Evidences.Where(predicate).OrderByDescending(x => x.Modified); }
注:
IQueryable是延迟执行的,方法无需标记为async,异步操作应该在最终执行查询时(比如调用ToListAsync())使用。
2. 优化PredicateBuilder(可选但推荐)
你当前的PredicateBuilder使用了Expression.Invoke拼接表达式,虽然大部分场景下EF Core可以处理,但复杂场景下可能影响表达式翻译。可以替换为更可靠的参数替换式拼接:
public static class PredicateBuilder { public static Expression<Func<T, bool>> True<T>() => _ => true; public static Expression<Func<T, bool>> False<T>() => _ => false; public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) { var combined = Expression.Lambda<Func<T, bool>>( Expression.AndAlso(left.Body, new ParameterReplacer(right.Parameters[0], left.Parameters[0]).Visit(right.Body)), left.Parameters); return combined; } public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) { var combined = Expression.Lambda<Func<T, bool>>( Expression.OrElse(left.Body, new ParameterReplacer(right.Parameters[0], left.Parameters[0]).Visit(right.Body)), left.Parameters); return combined; } public static Expression<Func<T, bool>> AndOr<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2, bool or = false) { return or ? expr1.Or(expr2) : expr1.And(expr2); } private class ParameterReplacer : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly ParameterExpression _newParam; public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam) { _oldParam = oldParam; _newParam = newParam; } protected override Expression VisitParameter(ParameterExpression node) { return node == _oldParam ? _newParam : base.VisitParameter(node); } } }
这个版本通过替换参数直接拼接表达式体,避免了Expression.Invoke,让EF Core更容易翻译表达式树。
验证
修改后,EF Core会将EF.Functions.Like(x.CaseFileNumber, $"%{query.CaseNumber}%")正确转换为SQL的WHERE CaseFileNumber LIKE '%xxx%',完全在数据库端执行查询,不会触发客户端求值。
内容的提问来源于stack exchange,提问作者Muhammad Touseef

