Добавление OR выражений в цикле в Linq

У меня есть переменное число условий OR, которые я хочу скомпоновать в один запрос Linq.

Как это сделать в цикле? В основном, окончательный запрос должен быть:

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

Что-то вроде:

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

Какую инструкцию я могу использовать для замены (существующее условие) в примере выше, не имея окончательного выражения (Q), вложенного Q внутри них?

Спасибо.

Ответ 1

Вам нужно будет построить дерево выражений, представляющее все условия, которые вас интересуют, в сочетании с Expression.OrElse, а затем вызовите Where один раз в конце.

Это может быть несколько сложно, если ваш текущий источник является анонимным, но в противном случае это не должно быть слишком плохо. Здесь образец - может быть более простой способ выполнить замену параметров, но это не так уж плохо. (Хотя ExpressionVisitor работает только в .NET 4... вам придется реализовать что-то подобное себе, если вы хотите использовать его в .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;
        }
    }
}

Ответ 2

  public static IEnumerable<T> GetItemsThatMatchAny<T> (this IEnumerable<T> source, IEnumerable<Func<T,bool>> predicates)
    {      
      return source.Where(t => predicates.Any(predicate => predicate(t)));
    }

Пример генератора предикатов:

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);
}

Ответ 3

Версия с меньшим оптимизацией (молись, чтобы бэкэнд выполнил необходимый подъем и оптимизацию).

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();
}