tags:

views:

68

answers:

3

I have a variable number of OR conditions that I want to put together into one Linq query.

How do I do this in a loop? Basically, the final query is to be:

IQueryable<MyObject> Q;
Q = Q.Where(q => (condition1) || (condition2) || ..... || (condition N));

Something like:

For (int i = 0; i < someNumber; i++) {
  Q = Q.Where(q => (existing conditions) || (q.Value == i)); 
}

What statement can I use to replace (existing condition) in example above without having the final expression (Q) have nested Q's inside them?

Thanks.

+2  A: 

You'd need to build an expression tree representing all the conditions you were interested in, combined with Expression.OrElse, and then call Where a single time at the end.

This may be somewhat tricky if your current source is an anonymous type, but it shouldn't be too bad otherwise. Here's a sample - there may be a simpler way of doing the parameter replacement, but this isn't too bad. (Although ExpressionVisitor only works in .NET 4... you'd have to implement something similar yourself if you wanted to use this in .NET 3.5.)

using System;
using System.Linq;
using System.Linq.Expressions;

public class Test
{
    static void Main()
    {
        IQueryable<string> strings = (new[] { "Jon", "Tom", "Holly", 
             "Robin", "William" }).AsQueryable();


        Expression<Func<string, bool>> firstPredicate = p => p.Contains("ll");
        Expression<Func<string, bool>> secondPredicate = p => p.Length == 3;
        Expression combined = Expression.OrElse(firstPredicate.Body,
                                                secondPredicate.Body);

        ParameterExpression param = Expression.Parameter(typeof(string), "p");
        ParameterReplacer replacer = new ParameterReplacer(param);
        combined = replacer.Visit(combined);

        var lambda = Expression.Lambda<Func<string, bool>>(combined, param);

        var query = strings.Where(lambda);

        foreach (string x in query)
        {
            Console.WriteLine(x);
        }
    }

    // Helper class to replace all parameters with the specified one
    class ParameterReplacer : ExpressionVisitor
    {
        private readonly ParameterExpression parameter;

        internal ParameterReplacer(ParameterExpression parameter)
        {
            this.parameter = parameter;
        }

        protected override Expression VisitParameter
            (ParameterExpression node)
        {
            return parameter;
        }
    }
}
Jon Skeet
+1  A: 
  public static IEnumerable<T> GetItemsThatMatchAny<T> (this IEnumerable<T> source, IEnumerable<Func<T,bool>> predicates)
    {      
      return source.Where(t => predicates.Any(predicate => predicate(t)));
    }

An example of a predicate generator:

private static IEnumerable<Func<MyClass, bool>> GetPredicates (int num)
{
   var predicates = new Func<MyClass, bool>[] {m => m.Foo == 3, m => m.Bar =="x", m => DateTime.Now.DayOfWeek == DayOfWeek.Sunday};

   return predicates.Take (num);
}
Ani
I have a similar idea, but unfortunately this fails with Linq2Sql.
leppie
Yes, you're right; I missed the IQueryable bit.
Ani
A: 

A less-than-optimized version (pray that the backend will do the necessary lifting and optimization).

public static IQueryable<T> Any<T>(this IQueryable<T> q, 
  params Expression<Func<T, bool>>[] preds)
{
  var par = Expression.Parameter(typeof(T), "x");

  Expression body = Expression.Constant(false);

  foreach (var pred in preds)
  {
    body = Expression.OrElse(body, Expression.Invoke(pred, par));
  }

  var ff = Expression.Lambda(body, par) as Expression<Func<T, bool>>;

  return q.Where(ff);
}

static void Main(string[] args)
{
  var q = new[] { "jim", "bob", "Jon", "leppie" }.AsQueryable();

  Expression<Func<string, bool>>[] preds =
  {
    x => x == "Jon",
    x => x == "Skeet",
    x => x == "leppie"
  };

  var result = q.Any(preds).ToArray();
}
leppie