tags:

views:

291

answers:

2

I've got a form with multiple fields on it (company name, postcode, etc.) which allows a user to search for companies in a database. If the user enters values in more than one field then I need to search on all of those fields. I am using LINQ to query the database.

So far I have managed to write a function which will look at their input and turn it into a List of expressions. I now want to turn that List into a single expression which I can then execute via the LINQ provider.

My initial attempt was as follows

private Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions)
    {
        if (expressions.Count == 0)
        {
            return null;
        }
        if (expressions.Count == 1)
        {
            return expressions[0];
        }
        Expression<Func<Company, bool>> combined = expressions[0];
        expressions.Skip(1).ToList().ForEach(expr => combined = Expression.And(combined, expr));
        return combined;
    }

However this fails with an exception message along the lines of "The binary operator And is not defined for...". Does anyone have any ideas what I need to do to combine these expressions?

EDIT: Corrected the line where I had forgotten to assign the result of and'ing the expressions together to a variable. Thanks for pointing that out folks.

+1  A: 

EDIT: Jason's answer is now fuller than mine was in terms of the expression tree stuff, so I've removed that bit. However, I wanted to leave this:

I assume you're using these for a Where clause... why not just call Where with each expression in turn? That should have the same effect:

var query = ...;
foreach (var condition in conditions)
{
    query = query.Where(condition);
}
Jon Skeet
@Jon Skeet: `combined` will be typed as an `Expression`; you need to do some work to return it as an `Expression<Func<Company, bool>>`.
Jason
I agree your first code is easier to understand, so I will make this the correct answer. However I am actually going to use the second snippet as this is exactly what I need - I was making things far too complex, thanks Jon.
gilles27
Ironically I was editing while both of these comments were written - but as it was this second snippet that was used, I think I'll leave it as it is :)
Jon Skeet
+4  A: 

You can use Enumerable.Aggregate combined with Expression.AndAlso. Here's a generic version:

Expression<Func<T, bool>> AndAll<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions) {

    if(expressions == null) {
        throw new ArgumentNullExpression("expressions");
    }
    if(expression.Count() == 0) {
        return t => true;
    }
    Type delegateType = typeof(Func<,>)
                            .GetGenericTypeDefinition()
                            .MakeGenericType(new[] {
                                typeof(T),
                                typeof(bool) 
                            }
                        );
    var combined = expressions
                       .Cast<Expression>()
                       .Aggregate((e1, e2) => Expression.AndAlso(e1, e2));
    return (Expression<Func<T,bool>>)Expression.Lambda(delegateType, combined);
}

Your current code is never assigning to combined:

expr => Expression.And(combined, expr);

returns a new Expression that is the result of bitwise anding combined and expr but it does not mutate combined.

Jason
+1 for a technically great answer, thanks. I've accepted Jon's as it appears simpler and also his use of Where is actually what I should be doing.
gilles27
@gilles27: Yeah, if you're only using it for the predicate in a `Where` clause, then Jon's answer is the way to go. If you ever need a more general version, my version will help you. :-)
Jason