【问题标题】:Adding OR expressions in a loop in Linq在 Linq 的循环中添加 OR 表达式
【发布时间】:2010-08-11 15:47:38
【问题描述】:

我想将可变数量的 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?

谢谢。

【问题讨论】:

    标签: c# .net linq


    【解决方案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;
            }
        }
    }
    

    【讨论】:

    • Replacer 到底在做什么?
    • @Seabiscuit:它基本上使结果表达式树中的所有参数表达式都引用相同的参数表达式,而不是为顶层和每个“子表达式”分别设置一个。
    • 我想我明白你的意思了。但它提出了更多的问题。如果我理解正确,Replacer 的职责是将相同的参数表达式分配给firstPredicatesecondPredicate。我不清楚的是为什么在源的Where 扩展方法中(这里是strings),firstPredicatesecondPredicate 中的参数不一样?
    • 乔恩,假设你上面的例子有三个(或更多)OR 语句,你怎么能使用Expression.OrElse 来创建OR 语句的非嵌套序列。在我看来,前两个必须分组到左侧,可以说,第三个表达式将添加到右侧的OR 序列中。但随后前两个表达式将作为一个组进行评估,然后将评估第三个表达式,否定 OP 的问题。
    • @Seabiscuit:不,它会评估第一个表达式,然后是第二个,然后是第三个等 - 由于短路。毕竟,普通 C# 中的 (a || b || c) 只是 ((a || b) || c)。这不像是三元 || 运算符。
    【解决方案2】:

    一个未优化的版本(祈祷后端将进行必要的提升和优化)。

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

    【讨论】:

      【解决方案3】:
        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);
      }
      

      【讨论】:

      • 我也有类似的想法,但不幸的是,Linq2Sql 失败了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 1970-01-01
      • 2017-04-26
      • 1970-01-01
      相关资源
      最近更新 更多