Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 490
AJ17 Avatar asked Oct 02 '12 03:10

AJ17


2 Answers

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.

like image 134
chase Avatar answered Nov 02 '22 04:11

chase


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.

like image 45
Maarten Avatar answered Nov 02 '22 05:11

Maarten