Расширить IQueryable <T>Где () как ИЛИ вместо отношения И - PullRequest
8 голосов
/ 31 мая 2009

Я использую свои собственные методы расширения IQueryable <> для создания цепочечных запросов, таких как FindAll (). FindInZip (12345) .NameStartsWith ("XYZ"). OrderByHowIWantIt () и т. Д., Которые затем при отложенном выполнении создают один запрос на основе моей цепочки методов расширения.

Однако проблема в том, что все Where в цепочке расширений (FindXYZ, FindInZip и т. Д.) Всегда объединяются как AND, что означает, что я не могу сделать что-то вроде этого:

FindAll (). FirstNameStartsWith ("X"). OrLastNameStartsWith ("Z"), потому что я не знаю, как я могу вставить ИЛИ в отдельный метод Where.

Есть идеи, как мне это решить?


дополнительные; До сих пор я понимаю, как связывать выражения как или если я обертываю их (например, CompileAsOr (FirstNameStartsWith ("A"). LastNameStartsWith ("Z"). OrderBy (..))

То, что я пытаюсь сделать, хотя и немного сложнее (и PredicateBuilder здесь не помогает ...) в том, что я хочу, чтобы более поздний IQueryable в основном получал доступ к условиям Where, которые были созданы ранее, без необходимости их переноса для создания Или между ними.

Поскольку каждый метод расширения возвращает IQueryable <>, я понимаю, что он должен знать где-то о текущем состоянии условий запроса, что наводит меня на мысль, что должен быть какой-то автоматизированный способ или создание ИЛИ во всех предыдущих условиях Где без Обернуть, что вы хотите Or'd.

Ответы [ 4 ]

9 голосов
/ 31 мая 2009

Я предполагаю, что различные части запроса известны только во время выполнения, то есть вы не можете просто использовать || в where ...

Один ленивый вариант - Concat - но это приводит к плохому TSQL и т. Д .; тем не менее, я склонен писать вместо Expression с. Подход зависит от того, какой поставщик, поскольку LINQ-to-SQL поддерживает различные опции для EF (например), что имеет реальный эффект (поскольку вы не можете использовать подвыражения с EF). Можете ли вы сказать нам, что?


Вот код, который должен работать с LINQ-to-SQL; если вы строите массив (или список, и вызываете .ToArray()) выражений, он должен работать нормально; Примером является LINQ-to-Objects, но он все равно должен работать:

    static void Main()
    {
        var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();

        var predicates = new List<Expression<Func<int, bool>>>();
        predicates.Add(i => i % 3 == 0);
        predicates.Add(i => i >= 8);           

        foreach (var item in data.WhereAny(predicates.ToArray()))
        {
            Console.WriteLine(item);
        }
    }

    public static IQueryable<T> WhereAny<T>(
        this IQueryable<T> source,
        params Expression<Func<T,bool>>[] predicates)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (predicates == null) throw new ArgumentNullException("predicates");
        if (predicates.Length == 0) return source.Where(x => false); // no matches!
        if (predicates.Length == 1) return source.Where(predicates[0]); // simple

        var param = Expression.Parameter(typeof(T), "x");
        Expression body = Expression.Invoke(predicates[0], param);
        for (int i = 1; i < predicates.Length; i++)
        {
            body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
        }
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return source.Where(lambda);
    }
8 голосов
/ 31 мая 2009

Использование PredicateBuilder<T>. Это, вероятно, то, что вы хотите.

0 голосов
/ 12 сентября 2018

В идеальном мире я лично думаю, что операторы || и && будут самыми простыми и удобочитаемыми. Однако он не скомпилируется.

оператор '||' нельзя применять к операндам типа 'Expression<Func<YourClass,bool>>' и 'Expression<Func<YourClass,bool>>'

Поэтому я использую метод расширения для этого. В вашем примере это будет выглядеть так: .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now)).

Вместо:

.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now)).

Пример выражения:

private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
      return post => post.PostedOn >= cutoffDate;
};

Метод расширения:

public  static  class PredicateExtensions
{
     ///  <summary>
     /// Begin an expression chain
     ///  </summary>
     ///  <typeparam id="T""></typeparam>
     ///  <param id="value"">Default return value if the chanin is ended early</param>
     ///  <returns>A lambda expression stub</returns>
     public  static Expression<Func<T,  bool>> Begin<T>(bool value =  false)
    {
         if (value)
             return parameter =>  true;  //value cannot be used in place of true/false

         return parameter =>  false;
    }

     public  static Expression<Func<T,  bool>> And<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.AndAlso);
    }

     public  static Expression<Func<T,  bool>> Or<T>(this Expression<Func<T,  bool>> left, Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.OrElse);
    }

     #region private

     private  static Expression<Func<T,  bool>> CombineLambdas<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right, ExpressionType expressionType)
    {
         //Remove expressions created with Begin<T>()
         if (IsExpressionBodyConstant(left))
             return (right);

        ParameterExpression p = left.Parameters[0];

        SubstituteParameterVisitor visitor =  new SubstituteParameterVisitor();
        visitor.Sub[right.Parameters[0]] = p;

        Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
         return Expression.Lambda<Func<T,  bool>>(body, p);
    }

     private  static  bool IsExpressionBodyConstant<T>(Expression<Func<T,  bool>> left)
    {
         return left.Body.NodeType == ExpressionType.Constant;
    }

     internal  class SubstituteParameterVisitor : ExpressionVisitor
    {
         public Dictionary<Expression, Expression> Sub =  new Dictionary<Expression, Expression>();

         protected  override Expression VisitParameter(ParameterExpression node)
        {
            Expression newValue;
             if (Sub.TryGetValue(node,  out newValue))
            {
                 return newValue;
            }
             return node;
        }
    }

     #endregion
} 

Действительно хорошая статья о LINQ Queries by Extending Expressions. Также источник метода расширения, который я использую.

https://www.red -gate.com / простой разговор / DotNet / сетчатая рамка / давая чёткость-к-Linq-запросам-по-выдвигая-выражение /

0 голосов
/ 15 сентября 2016
    List<string> fruits =
        new List<string> { "apple", "passionfruit", "banana", "mango",
               "orange", "blueberry", "grape", "strawberry" };

    var query = fruits.AsQueryable();

    // Get all strings whose length is less than 6.
    query = query.Where(fruit => fruit.Length < 6);

    // Hope to get others where length is more than 8.  But you can't, they're gone.
    query = query.Where(fruit => 1 == 1 || fruit.Length > 8);

    foreach (string fruit in query)
        Console.WriteLine(fruit);
...