【问题标题】:Exception using OrElse and AndAlso expression methods使用 OrElse 和 AndAlso 表达式方法的异常
【发布时间】:2012-03-03 03:30:10
【问题描述】:

我正在尝试以编程方式构建表达式树。

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

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]' and 'System.Func2[MyObject,System.Boolean]'。

我有点困惑。有人可以更好地解释异常的原因并提出解决方案吗?

非常感谢!

【问题讨论】:

    标签: entity-framework linq linq-to-entities linq-expressions dynamic-linq


    【解决方案1】:

    您将a => a == 3a => a == 4 组合成(a => a == 3) || (a => a == 4),但您应该尝试将其变为a => (a == 3 || a == 4)。手动做这件事并不难,但是someone has done it for you already。寻找“组合表达式”。

    编辑:根据要求,提供如何手动执行此操作的简单示例。

    编辑 2:它使用 ExpressionVisitor,这是 .NET 4 的新功能,但 at MSDN you can find a usable implementation for earlier versions。出于您的目的,我假设 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 当然,我已经添加了一个基于我用作示例的两个表达式的程序。
    • 您好,我已尝试实施您的解决方案。我确实明白 ExpressionVisitor 来自 LinqKit 源,我已经能够看到它是如何工作的。现在的问题是:IEnumerable&lt;ParameterExpression&gt;Zip 方法从何而来?我正在使用 .NET 3.5,但找不到该方法:(
    • @Lorenzo 啊,我使用的是 .NET 4,它带有 ExpressionVisitor 类和 Enumerable.Zip 扩展方法。我会看看我是否也能得到适用于 3.5 的东西,但这会有点复杂。
    • @Lorenzo 事实证明,MS 在 MSDN 上发布了一个功能性的ExpressionVisitor,所以我测试了它是否有效(它确实有效)并提到了它。而Enumerable.Zip 并不是真正需要的,所以我放弃了它并在其位置添加了一个简单的for 循环。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 2010-09-08
    • 2011-06-13
    • 2012-01-14
    • 1970-01-01
    相关资源
    最近更新 更多