Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding OR expressions in a loop in Linq

Tags:

c#

.net

linq

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.

like image 862
Jack Avatar asked Aug 11 '10 05:08

Jack


2 Answers

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;
        }
    }
}
like image 58
Jon Skeet Avatar answered Oct 01 '22 11:10

Jon Skeet


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();
}
like image 32
leppie Avatar answered Oct 01 '22 13:10

leppie