8

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.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
AJ17
  • 308
  • 5
  • 17
  • Can't you call `Where()` twice in a chain, once for each condition? I think that might be simpler than combining the expressions. – svick Oct 02 '12 at 08:41
  • Yes I guess it would work, but I am looking to run 1-10 different conditions in my where clause. So I dont think this method will be practical. – AJ17 Oct 02 '12 at 23:31

2 Answers2

9

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.

chase
  • 1,623
  • 1
  • 16
  • 21
2

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.

Community
  • 1
  • 1
Maarten
  • 22,527
  • 3
  • 47
  • 68
  • 3
    So, how exactly would you create that new expression? – svick Oct 02 '12 at 08:43
  • for my example, I think I will have to construct the expressions part by part, thats the only way I can think of using the same parameter expression. I don't think there is a direct way to pass the parameterexpression into an expression. – AJ17 Oct 03 '12 at 01:30
  • @svick, I have done some rewriting of expression trees, but with other purposes. One example of this can be found here:http://pastebin.com/KyhwKHBJ, which I used to convert an Expression> to Expression where T1 and T2 have similar properties. Warning: this code is not 'complete' in the sense that not all possibilities in lambda expression are supported. Use this as a start. – Maarten Oct 04 '12 at 06:38