"The parameter was not bound in the specified LINQ to Entities query expression." 规范模式和
"The parameter was not bound in the specified LINQ to Entities query expression." Specification Pattern And
我遵循描述的规范模式实现 here。我有一个像这样的存储库方法:
public IEnumerable<MyDto> Find(Specification<Dto> specification)
{
return myDbContext.MyDtos.Where(specification.ToExpression()).Take(20).ToList();
}
如果我使用普通的非复合规范,它工作得很好,但以下情况失败并显示消息 "The parameter 'r' was not bound in the specified LINQ to Entities query expression.":
Specification<MyDto> spec = new Spec1(someCriterion)
.And(new Spec2(someCriterion))
.And(new Spec3(someCriterion))
// etc...
var myDtos = repo.Find(spec);
据我目前所知,这与所有表达式的参数引用不同有关,但我不确定如何解决此问题。
供参考,这是 AndSpecification<T>
class 在我的代码中的样子:
public class AndSpecification<T> : Specification<T>
{
private readonly Specification<T> _left;
private readonly Specification<T> _right;
public AndSpecification(Specification<T> left, Specification<T> right)
{
_left = left;
_right = right;
}
public override Expression<Func<T, bool>> ToExpression()
{
Expression<Func<T, bool>> leftExpression = _left.ToExpression();
Expression<Func<T, bool>> rightExpression = _right.ToExpression();
BinaryExpression andExpression = Expression.AndAlso(
leftExpression.Body, rightExpression.Body);
return Expression.Lambda<Func<T, bool>>(
andExpression, leftExpression.Parameters.Single());
}
}
问题出在您的 ToExpression
方法中。
leftExpression
和 rightExpression
都是 LambdaExpression
,并且每个都有自己独特的 T
参数。
当您从 ToExpression
创建您 return 的 LambdaExpression 时,您说这应该使用来自 leftExpression
的参数。但是 rightExpression
中使用的参数呢? rightExpression.Body
包含使用 rightExpression.Parameters[0]
的表达式,它们仍然会继续引用对象 rightExpression.Parameters[0]
,即使在您使用 rightExpression.Body
并将其放入另一个表达式后也是如此。
您需要重写 rightExpression
以使用与 leftExpression
相同的参数。最简单的方法是使用 ExpressionVisitor
.
首先,创建一个 ExpressionVisitor
,它只是用另一个参数替换一个参数:
public class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression target;
private readonly ParameterExpression replacement;
public ParameterReplaceVisitor(ParameterExpression target, ParameterExpression replacement) =>
(this.target, this.replacement) = (target, replacement);
protected override Expression VisitParameter(ParameterExpression node) =>
node == target ? replacement : base.VisitParameter(node);
}
然后用这个重写你的rightExpression.Body
,所以它使用与leftExpression
相同的参数对象:
var visitor = new ParameterReplaceVisitor(rightExpression.Parameters[0], leftExpression.Parameters[0]);
var rewrittenRightBody = visitor.Visit(rightExpression.Body.Visit);
var andExpression = Expression.AndAlso(leftExpression.Body, rewrittenRightBody);
return Expression.Lambda<Func<T, bool>>(
andExpression, leftExpression.Parameters[0]);
我遵循描述的规范模式实现 here。我有一个像这样的存储库方法:
public IEnumerable<MyDto> Find(Specification<Dto> specification)
{
return myDbContext.MyDtos.Where(specification.ToExpression()).Take(20).ToList();
}
如果我使用普通的非复合规范,它工作得很好,但以下情况失败并显示消息 "The parameter 'r' was not bound in the specified LINQ to Entities query expression.":
Specification<MyDto> spec = new Spec1(someCriterion)
.And(new Spec2(someCriterion))
.And(new Spec3(someCriterion))
// etc...
var myDtos = repo.Find(spec);
据我目前所知,这与所有表达式的参数引用不同有关,但我不确定如何解决此问题。
供参考,这是 AndSpecification<T>
class 在我的代码中的样子:
public class AndSpecification<T> : Specification<T>
{
private readonly Specification<T> _left;
private readonly Specification<T> _right;
public AndSpecification(Specification<T> left, Specification<T> right)
{
_left = left;
_right = right;
}
public override Expression<Func<T, bool>> ToExpression()
{
Expression<Func<T, bool>> leftExpression = _left.ToExpression();
Expression<Func<T, bool>> rightExpression = _right.ToExpression();
BinaryExpression andExpression = Expression.AndAlso(
leftExpression.Body, rightExpression.Body);
return Expression.Lambda<Func<T, bool>>(
andExpression, leftExpression.Parameters.Single());
}
}
问题出在您的 ToExpression
方法中。
leftExpression
和 rightExpression
都是 LambdaExpression
,并且每个都有自己独特的 T
参数。
当您从 ToExpression
创建您 return 的 LambdaExpression 时,您说这应该使用来自 leftExpression
的参数。但是 rightExpression
中使用的参数呢? rightExpression.Body
包含使用 rightExpression.Parameters[0]
的表达式,它们仍然会继续引用对象 rightExpression.Parameters[0]
,即使在您使用 rightExpression.Body
并将其放入另一个表达式后也是如此。
您需要重写 rightExpression
以使用与 leftExpression
相同的参数。最简单的方法是使用 ExpressionVisitor
.
首先,创建一个 ExpressionVisitor
,它只是用另一个参数替换一个参数:
public class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression target;
private readonly ParameterExpression replacement;
public ParameterReplaceVisitor(ParameterExpression target, ParameterExpression replacement) =>
(this.target, this.replacement) = (target, replacement);
protected override Expression VisitParameter(ParameterExpression node) =>
node == target ? replacement : base.VisitParameter(node);
}
然后用这个重写你的rightExpression.Body
,所以它使用与leftExpression
相同的参数对象:
var visitor = new ParameterReplaceVisitor(rightExpression.Parameters[0], leftExpression.Parameters[0]);
var rewrittenRightBody = visitor.Visit(rightExpression.Body.Visit);
var andExpression = Expression.AndAlso(leftExpression.Body, rewrittenRightBody);
return Expression.Lambda<Func<T, bool>>(
andExpression, leftExpression.Parameters[0]);