使用OrElse和AndAlso表达式方法时出现异常

16

我试图以编程方式构建一个表达式树。

我的输入中,有一个条件类的列表,其形式如下:

public class Filter
{
    public string field { get; set; }
    public string operator { get; set; }
    public string value { get; set; }
}

在构建Expression对象时,我按照以下方式为每个条件创建一个Expression

foreach ( Filter sf in rules ) {
    Expression ex = sf.ToExpression( query );
    if ( mainExpression == null ) {
        mainExpression = ex;
    }
    else {
        if ( logicalCondition == "AND" ) {
            mainExpression = Expression.And( mainExpression, ex );
        }
        else if ( logicalCondition == "OR" ) {
            mainExpression = Expression.Or( mainExpression, ex );
        }
    }
}

Filter.ToExpression() 方法的实现方式如下:

public override Expression ToExpression( IQueryable query ) {
    ParameterExpression parameter = Expression.Parameter( query.ElementType, "p" );
    MemberExpression memberAccess = null;
    foreach ( var property in field.Split( '.' ) )
        memberAccess = MemberExpression.Property( memberAccess ?? ( parameter as Expression ), property );
    ConstantExpression filter = Expression.Constant( Convert.ChangeType( value, memberAccess.Type ) );
    WhereOperation condition = (WhereOperation)StringEnum.Parse( typeof( WhereOperation ), operator );
    LambdaExpression lambda = BuildLambdaExpression( memberAccess, filter, parameter, condition, value );
    return lambda;
}

当我只有一个条件时,一切正常,但是当我尝试使用AndOrAndAlsoOrElse静态方法结合表达式时,我会收到一个InvalidOperationException异常,其内容为:

二元运算符“Or”未为类型“System.Func2[MyObject,System.Boolean]'和'System.Func2[MyObject,System.Boolean]'定义。

我有点困惑。有人能更好地解释一下这个异常的原因并提出解决方案吗?

非常感谢!

1个回答

39
你正在将 a => a == 3a => a == 4 合并成 (a => a == 3) || (a => a == 4),但你应该尝试将其改为 a => (a == 3 || a == 4)。这不难手动完成,但是 有人已经为您完成了。寻找“合并表达式”。

编辑:根据要求,以下是手动完成此操作的简单示例。

编辑2:它使用ExpressionVisitor,这是.NET 4中的新功能,但是在MSDN上,您可以找到适用于早期版本的可用实现。我假设MSDN代码不符合您的“第三方”要求。您只需要将protected virtual Expression Visit(Expression exp)方法更改为public。由于Enumerable.Zip对您不可用且不必要,因此现在已经消失。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace DemoApp
{
    <include ExpressionVisitor definition here for .NET 3.5>

    public class ExpressionParameterReplacer : ExpressionVisitor
    {
        public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
        {
            ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
            for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
                ParameterReplacements.Add(fromParameters[i], toParameters[i]);
        }
        private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements
        {
            get;
            set;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            ParameterExpression replacement;
            if (ParameterReplacements.TryGetValue(node, out replacement))
                node = replacement;
            return base.VisitParameter(node);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, bool>> exprA = a => a == 3;
            Expression<Func<int, bool>> exprB = b => b == 4;
            Expression<Func<int, bool>> exprC =
                Expression.Lambda<Func<int, bool>>(
                    Expression.OrElse(
                        exprA.Body,
                        new ExpressionParameterReplacer(exprB.Parameters, exprA.Parameters).Visit(exprB.Body)),
                    exprA.Parameters);
            Console.WriteLine(exprA.ToString());
            Console.WriteLine(exprB.ToString());
            Console.WriteLine(exprC.ToString());
            Func<int, bool> funcA = exprA.Compile();
            Func<int, bool> funcB = exprB.Compile();
            Func<int, bool> funcC = exprC.Compile();
            Debug.Assert(funcA(3) && !funcA(4) && !funcA(5));
            Debug.Assert(!funcB(3) && funcB(4) && !funcB(5));
            Debug.Assert(funcC(3) && funcC(4) && !funcC(5));
        }
    }
}

你好,感谢你的回答。我不能使用第三方代码来解决这个问题。你能否更好地解释一下如何手动完成呢?再次感谢! - Lorenzo
@Lorenzo 当然,我已经添加了一个基于我作为示例使用的两个表达式的程序。 - user743382
嗨,我已经尝试实现您的解决方案。我确实了解ExpressionVisitor来自LinqKit源代码,并且已经能够看到它的工作原理。现在的问题是:IEnumerable<ParameterExpression>Zip方法来自哪里?我正在使用.NET 3.5,但找不到该方法:( - Lorenzo
@Lorenzo 噢,我使用的是带有ExpressionVisitor类和Enumerable.Zip扩展方法的.NET 4。我会尝试着让3.5也能工作,但那会更加复杂。 - user743382
@Lorenzo事实证明,微软在MSDN上发布了一个功能齐全的“ExpressionVisitor”,因此我测试了它是否有效(它是有效的),并提到了它。而Enumerable.Zip并不是必需的,所以我将其删除,并添加了一个简单的for循环来代替它。 - user743382
没错!我一直在抱怨为什么不直接执行返回bool值的lambda函数,然后进行OR运算 :) 其实我只需要稍微重新组织一下代码,生成一个带有所有OR运算的布尔表达式,并将其置于lambda函数中就可以了! - Lzh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接