14

I am trying to build an expression tree programmatically.

I have in my input, a list of condition classes which have the following form:

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

When I build the Expression object I create an Expression for every condition in the following way

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 );
        }
    }
}

The Filter.ToExpression() method is implemented like this

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;
}

Everything works when I have a single condition but when I try to combine expressions using one of the And, Or, AndAlso, OrElse static methods I receive an InvalidOperationException that says:

The binary operator Or is not defined for the types 'System.Func2[MyObject,System.Boolean]' and 'System.Func2[MyObject,System.Boolean]'.

I am getting a little bit confused. Can somebody better explain the reasons of the exception and suggest a solution?

Thanks very much!

MrModest
  • 125
  • 6
Lorenzo
  • 28,103
  • 43
  • 117
  • 208

1 Answers1

33

You're combining a => a == 3 and a => a == 4 into (a => a == 3) || (a => a == 4), but you should instead be trying to make it a => (a == 3 || a == 4). This is not too hard to do manually, but someone has done it for you already. Look for "Combining Expressions".

Edit: as requested, a simple example of how to do this manually.

Edit 2: it uses ExpressionVisitor which is new to .NET 4, but at MSDN you can find a usable implementation for earlier versions. I'm assuming MSDN code doesn't qualify as "third party" for your purposes. You only need to change the protected virtual Expression Visit(Expression exp) method to public. And as Enumerable.Zip is unavailable for you and it isn't necessary, it is gone now.

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));
        }
    }
}
  • Hi, thanks for your answer. I can't use third party code to solve this problem. Can you please better explain which would be' the way for doing it manually? Thanks again! – Lorenzo Feb 10 '12 at 17:55
  • @Lorenzo Sure, I've added a program based on the two expressions I used as an example. –  Feb 10 '12 at 18:10
  • Hi, I have tried to implement your solution. I did understand that the `ExpressionVisitor` comes from the LinqKit sources and I have been able to see how it works. The question now is: where the `Zip` method of `IEnumerable` comes from? I am using .NET 3.5 and I am not able to find that method :( – Lorenzo Feb 11 '12 at 13:57
  • @Lorenzo Ah, I was using .NET 4, which comes with both the `ExpressionVisitor` class and the `Enumerable.Zip` extension method. I'll see if I can get something working for 3.5 too, but that'll be a bit more complicated. –  Feb 11 '12 at 14:01
  • @Lorenzo As it turns out, MS has posted a functional `ExpressionVisitor` on MSDN, so I tested to see whether it works (it does) and made a mention of it. And `Enumerable.Zip` isn't really necessary, so I dropped it and added a simple `for` loop in its place. –  Feb 11 '12 at 14:22
  • Exactly! And I was complaining why doesn't it just execute the lambdas that return bool and then ORs them :) All I needed was to restructure my code a bit so that I'm generating a boolean expression with all the ORs, and then put that into a lambda! – Mzn Mar 27 '14 at 11:16