Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I combine LINQ expressions into one?

Tags:

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.

like image 658
gilles27 Avatar asked Dec 17 '09 15:12

gilles27


2 Answers

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 ArgumentNullException("expressions");     }     if(expressions.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.

like image 81
jason Avatar answered Oct 14 '22 22:10

jason


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); } 
like image 42
Jon Skeet Avatar answered Oct 14 '22 22:10

Jon Skeet