在使用Expression.AndAlso聚合表达式列表时遇到问题

时间:2013-12-04 16:02:00

标签: c# linq lambda expression

我是明确构建LINQ表达式的新手,我正试图弄清楚如何组合IEnumerable>>单个表达式>通过使用Aggregate和Expression.AndAlso。

我觉得我越来越近了,但我显然错过了一些东西。

public static Expression<Func<T, bool>> CombineExpressions<T>(
IEnumerable<Expression<Func<T, bool>>> expressions)
{

  if (expressions == null || expressions.Count() == 0)
  {
    return t => true;
  }
  var combined = expressions
                .Cast<Expression>()
                .Aggregate((a, b) => Expression.AndAlso(a, b));

  ParameterExpression pe = Expression.Parameter(typeof(T), "x");

  return Expression.Lambda<Func<T, bool>>(combined, pe);
}

当我调用此方法时,我得到以下异常:

System.ArgumentException: 
       Expression of type 'System.Func`2[SomeEntity,System.Boolean]'
       cannot be used for return type 'System.Boolean'

请帮忙!

2 个答案:

答案 0 :(得分:6)

您在这里遇到的问题是您需要组合功能(技术上,表示功能的表达式)。调用两个函数时AndAlso没有意义;它需要在两个直接解析为布尔值的表达式上调用。您需要获取每个函数的 body ,而不是整个函数,并将AndAlso body 放在一起。

但抓住尸体是不够的;如果你这样做,你会遇到每个身体参数不同的问题。您需要将每个函数参数的所有用法替换为您为结果函数创建的新参数。

要处理替换这些参数,我们可以使用以下帮助器类和一个调用它的辅助方法来进行替换:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

鉴于这种辅助方法,你已完成了大部分工作;剩下的不多了:

public static Expression<Func<T, bool>> CombineExpressions<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions)
{
    if (expressions == null || expressions.Count() == 0)
    {
        return t => true;
    }
    ParameterExpression param = Expression.Parameter(typeof(T), "x");
    var combined = expressions
                    .Select(func => func.Body.Replace(func.Parameters[0], param))
                    .Aggregate((a, b) => Expression.AndAlso(a, b));

    return Expression.Lambda<Func<T, bool>>(combined, param);
}

当然,另一种方法是创建一个PredicateBuilder类,它可以AndOr任意两个函数,每个函数采用一个公共类型并返回一个布尔值。它们每个都比你的例子稍微简单一些:

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                         Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
    }
}

如果我们先花时间制作这个可重复使用的类型,那么将它从两个函数推广到N是非常简单的:

public static Expression<Func<T, bool>> CombineExpressions<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions)
{
    if (expressions == null || expressions.Count() == 0)
    {
        return t => true;
    }
    return expressions.Aggregate((a, b) => a.And(b));
}

答案 1 :(得分:3)

为什么重新发明轮子?优秀的LinqKit库已经为其PredicateBuilder

提供了此功能

用你的例子:

return expressions.Aggregate((accumulate, current) => accumulate.And(current));