AJ17
AJ17

Reputation: 308

How to create an Expression AND clause from two expressions

I am trying to create a where clause for my view using LINQ.

I was able to create single column where clause and I would like now to create multiple column where clauses..

I have seen code to implement in .Net 4 and above, but since I have to use .Net 3.5, I need a quick work around for this. so what I am trying to do is....

 Expression leftexp = {tag=>((tag.id=2)||(tag.id=3))}
 Expression rightexp = {tag=>((tag.uid="MU")||(tag.uid="ST"))}

from these two expressions i would like to create

 BinaryExpression be = {tag=>((tag.id=2)||(tag.id=3))} && 
                       {tag=>((tag.uid="MU")||(tag.uid="ST"))} 

something like this which i could pass to my where clause in LINQ.

I tried to use Expression.And(leftexp,rightexp)

but got the error..

The binary operator And is not defined for the types
'System.Func2[WebApplication1.View_MyView,System.Boolean]' and 'System.Func2[WebApplication1.View_MyView,System.Boolean]'.

Expression is new for me and might have looked at too much of code so a bit confused to how to go about doing this... would really appreciate if you could point me in the right direction.

Upvotes: 8

Views: 11727

Answers (2)

chase
chase

Reputation: 1718

Rewriting expressions has been made easy with the addition of ExpressionVisitor to BCL. With some helpers the task gets almost trivial.

Here's a visitor class I use to apply a delegate to the tree nodes:

internal sealed class ExpressionDelegateVisitor : ExpressionVisitor {

    private readonly Func<Expression , Expression> m_Visitor;
    private readonly bool m_Recursive;

    public static Expression Visit ( Expression exp , Func<Expression , Expression> visitor , bool recursive ) {
        return new ExpressionDelegateVisitor ( visitor , recursive ).Visit ( exp );
    }

    private ExpressionDelegateVisitor ( Func<Expression , Expression> visitor , bool recursive ) {
        if ( visitor == null ) throw new ArgumentNullException ( nameof(visitor) );
        m_Visitor = visitor;
        m_Recursive = recursive;
    }

    public override Expression Visit ( Expression node ) {
        if ( m_Recursive ) {
            return base.Visit ( m_Visitor ( node ) );
        }
        else {
            var visited = m_Visitor ( node );
            if ( visited == node ) return base.Visit ( visited );
            return visited;
        }
    }

}

And here are the helper methods to simplify the rewriting:

public static class SystemLinqExpressionsExpressionExtensions {

    public static Expression Visit ( this Expression self , Func<Expression , Expression> visitor , bool recursive = false ) {
        return ExpressionDelegateVisitor.Visit ( self , visitor , recursive );
    }

    public static Expression Replace ( this Expression self , Expression source , Expression target ) {
        return self.Visit ( x => x == source ? target : x );
    }

    public static Expression<Func<T , bool>> CombineAnd<T> ( this Expression<Func<T , bool>> self , Expression<Func<T , bool>> other ) {
        var parameter = Expression.Parameter ( typeof ( T ) , "a" );
        return Expression.Lambda<Func<T , bool>> (
            Expression.AndAlso (
                self.Body.Replace ( self.Parameters[0] , parameter ) ,
                other.Body.Replace ( other.Parameters[0] , parameter )
            ) ,
            parameter
        );
    }

}

Which allows to combine the expressions like this:

static void Main () {
    Expression<Func<int , bool>> leftExp = a => a > 3;
    Expression<Func<int , bool>> rightExp = a => a < 7;
    var andExp = leftExp.CombineAnd ( rightExp );
}

UPDATE:

In case ExpressionVisitor's not available, its source had been published a while ago here. Our library used that implementation until we've migrated to .NET 4.

Upvotes: 10

Maarten
Maarten

Reputation: 22955

You cannot do that without rewriting both complete expression trees into a complete new one.

Reason: the parameter-expression objects must be the same for the whole expression tree. If you combine the two, you have two parameter-expression objects for the same parameter, which will not work.

It shows with the following code:

Expression<Func<Tab, bool>> leftexp = tag => ((tag.id == 2) || (tag.id == 3));
Expression<Func<Tab, bool>> rightexp = tag => ((tag.uid == "MU") || (tag.uid == "ST"));

Expression binaryexp = Expression.AndAlso(leftexp.Body, rightexp.Body);
ParameterExpression[] parameters = new ParameterExpression[1] {
    Expression.Parameter(typeof(Tab), leftexp.Parameters.First().Name)
};
Expression<Func<Tab, bool>> lambdaExp = Expression.Lambda<Func<Tab, bool>>(binaryexp, parameters);

var lambda = lambdaExp.Compile();

This fails on the lambdaExp.Compile() call, which gives the following exception:

Lambda Parameter not in scope

This is caused by the fact that basically I'm re-using the leftexp and rightexp expression, but they have different parameter-expressions, both which are not given by me to the Expression.Lambda<Func<Tab>>(...) call. Deep down into the leftexp and rightexp there are parameter-expression objects which must match the one given to the Expression.Lambda<Func<Tab>>(...) call.

To solve this you have recreate the complete expression using a new (single) parameter-expression for parameter tag.

See here for more information about the problem.

Upvotes: 2

Related Questions