在规范模式中组合规范时,LINQ 表达式无法转换为 Sql
LINQ expressions can not be translated to Sql when combining specifications in Specification Pattern
我正在使用规范模式执行数据库过滤,并避免在内存中执行此操作 (I roughly followed this article)。我的基本规范 class 是这样的:
public abstract class Specification<T> : ISpecification<T>{
public abstract Expression<Func<T, bool>> FilterExpr();
public bool IsSatisfied(T entity)
{
Func<T, bool> func = this.FilterExpr().Compile();
return func(entity);
}
public Specification<T> And(Specification<T> otherSpec)
{
return new CombinedSpecification<T>(this, otherSpec);
}
}
从这个基本规范 class 中派生出多个强类型规范,它们各自运行良好。但是,当尝试组合此类规范并评估我的存储库中的 CombinedSpecification 时,问题就出现了 class:
System.InvalidOperationException: The LINQ expression 'DbSet()
.Where(c => c.ClientCode == __client_0 && c.Status == "Efective")' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
这里是 CombinedSpecification class:
internal class CombinedSpecification<T> : Specification<T>
{
private Specification<T> leftSpec;
private Specification<T> rightSpec;
public CombinedSpecification(Specification<T> aSpec, Specification<T> otherSpec)
{
this.leftSpec = aSpec;
this.rightSpec = otherSpec;
}
public override Expression<Func<T, bool>> FilterExpr()
{
Expression<Func<T, bool>> firstExpr = this.leftSpec.FilterExpr();
Expression<Func<T, bool>> secondExpr = this.rightSpec.FilterExpr();
BinaryExpression combined = Expression.AndAlso(firstExpr.Body, secondExpr.Body);
return Expression.Lambda<Func<T, bool>>(combined, firstExpr.Parameters.Single());
}
}
需要说明的是,按单一规范过滤效果很好,但 LinQ 表达式在组合它们时似乎无法翻译(这对我来说似乎很奇怪,因为我没有使用 [= 不支持的任何方法31=]据我所知)。我避免显示存储库 class 以减少这个问题的数量,但无论如何这里是我的 Find() 方法的相关行:
public IEnumerable<TEntity> Find(Specification<TEntity> spec)
{
IQueryable<TEntity> result = this.dbSet;
if (spec != null)
{
result = result.Where(spec.FilterExpr());
}
return result.ToList();
}
在此先感谢您的帮助,我希望我的第一个问题已经说清楚了!
问题在于,在您的 lambda 表达式组合实现中,您使用了一个组合了两个 lambda 表达式主体的主体,但仅包含第一个 lambda 表达式的参数 (firstExpr.Parameters.Single()
)。
这样第二个主体仍然引用一个不同的参数(即使它在视觉上看起来可能相同,lambda表达式中的参数由reference,而不是像你在编译时创建它们时所想的那样按名称命名。
换句话说,就是您在错误消息中看到的内容
c => c.ClientCode == __client_0 && c.Status == "Efective"
其实是这样的
p0 => p0.ClientCode == __client_0 && p1.Status == "Efective"
这当然是无效的,无法翻译(或编译 - 尝试调用 Compile()
,你会得到运行时异常)。
您需要确保两个操作数以及生成的 lambda 表达式使用同一个参数实例。
您仍然可以重复使用其中一个操作数的参数,但您必须将另一个操作数的主体重新绑定到同一参数。这通常是通过小型自定义 ExpressionVisitor
实现的,它会在表达式树中找到所有出现的参数,并将它们替换为另一个参数(或表达式)——非常类似于 string.Replace
,但对于表达式:
public static partial class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression target, ParameterExpression parameter, Expression value)
=> new ParameterReplacer { Parameter = parameter, Value = value }.Visit(target);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Parameter;
public Expression Value;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Parameter ? Value : node;
}
}
并用类似这样的东西修改组合实现
public override Expression<Func<T, bool>> FilterExpr()
{
var firstExpr = this.leftSpec.FilterExpr();
var secondExpr = this.rightSpec.FilterExpr();
var parameter = firstExpr.Parameters[0]; // <--
var combined = Expression.AndAlso(
firstExpr.Body,
secondExpr.Body.ReplaceParameter(secondExpr.Parameters[0], parameter) // <--
);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}
我正在使用规范模式执行数据库过滤,并避免在内存中执行此操作 (I roughly followed this article)。我的基本规范 class 是这样的:
public abstract class Specification<T> : ISpecification<T>{
public abstract Expression<Func<T, bool>> FilterExpr();
public bool IsSatisfied(T entity)
{
Func<T, bool> func = this.FilterExpr().Compile();
return func(entity);
}
public Specification<T> And(Specification<T> otherSpec)
{
return new CombinedSpecification<T>(this, otherSpec);
}
}
从这个基本规范 class 中派生出多个强类型规范,它们各自运行良好。但是,当尝试组合此类规范并评估我的存储库中的 CombinedSpecification 时,问题就出现了 class:
System.InvalidOperationException: The LINQ expression 'DbSet() .Where(c => c.ClientCode == __client_0 && c.Status == "Efective")' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
这里是 CombinedSpecification class:
internal class CombinedSpecification<T> : Specification<T>
{
private Specification<T> leftSpec;
private Specification<T> rightSpec;
public CombinedSpecification(Specification<T> aSpec, Specification<T> otherSpec)
{
this.leftSpec = aSpec;
this.rightSpec = otherSpec;
}
public override Expression<Func<T, bool>> FilterExpr()
{
Expression<Func<T, bool>> firstExpr = this.leftSpec.FilterExpr();
Expression<Func<T, bool>> secondExpr = this.rightSpec.FilterExpr();
BinaryExpression combined = Expression.AndAlso(firstExpr.Body, secondExpr.Body);
return Expression.Lambda<Func<T, bool>>(combined, firstExpr.Parameters.Single());
}
}
需要说明的是,按单一规范过滤效果很好,但 LinQ 表达式在组合它们时似乎无法翻译(这对我来说似乎很奇怪,因为我没有使用 [= 不支持的任何方法31=]据我所知)。我避免显示存储库 class 以减少这个问题的数量,但无论如何这里是我的 Find() 方法的相关行:
public IEnumerable<TEntity> Find(Specification<TEntity> spec)
{
IQueryable<TEntity> result = this.dbSet;
if (spec != null)
{
result = result.Where(spec.FilterExpr());
}
return result.ToList();
}
在此先感谢您的帮助,我希望我的第一个问题已经说清楚了!
问题在于,在您的 lambda 表达式组合实现中,您使用了一个组合了两个 lambda 表达式主体的主体,但仅包含第一个 lambda 表达式的参数 (firstExpr.Parameters.Single()
)。
这样第二个主体仍然引用一个不同的参数(即使它在视觉上看起来可能相同,lambda表达式中的参数由reference,而不是像你在编译时创建它们时所想的那样按名称命名。
换句话说,就是您在错误消息中看到的内容
c => c.ClientCode == __client_0 && c.Status == "Efective"
其实是这样的
p0 => p0.ClientCode == __client_0 && p1.Status == "Efective"
这当然是无效的,无法翻译(或编译 - 尝试调用 Compile()
,你会得到运行时异常)。
您需要确保两个操作数以及生成的 lambda 表达式使用同一个参数实例。
您仍然可以重复使用其中一个操作数的参数,但您必须将另一个操作数的主体重新绑定到同一参数。这通常是通过小型自定义 ExpressionVisitor
实现的,它会在表达式树中找到所有出现的参数,并将它们替换为另一个参数(或表达式)——非常类似于 string.Replace
,但对于表达式:
public static partial class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression target, ParameterExpression parameter, Expression value)
=> new ParameterReplacer { Parameter = parameter, Value = value }.Visit(target);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Parameter;
public Expression Value;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Parameter ? Value : node;
}
}
并用类似这样的东西修改组合实现
public override Expression<Func<T, bool>> FilterExpr()
{
var firstExpr = this.leftSpec.FilterExpr();
var secondExpr = this.rightSpec.FilterExpr();
var parameter = firstExpr.Parameters[0]; // <--
var combined = Expression.AndAlso(
firstExpr.Body,
secondExpr.Body.ReplaceParameter(secondExpr.Parameters[0], parameter) // <--
);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}