【问题标题】:How can I conditionally apply a Linq operator?如何有条件地应用 Linq 运算符?
【发布时间】:2008-08-14 15:20:26
【问题描述】:

我们正在开发日志查看器。用户可以选择按用户、严重性等进行过滤。在 Sql 日子里,我会添加到查询字符串中,但我想用 Linq 来做。如何有条件地添加 where 子句?

【问题讨论】:

    标签: c# linq linq-to-sql


    【解决方案1】:

    如果您只想在通过某些条件时进行过滤,请执行以下操作

    var logs = from log in context.Logs
               select log;
    
    if (filterBySeverity)
        logs = logs.Where(p => p.Severity == severity);
    
    if (filterByUser)
        logs = logs.Where(p => p.User == user);
    

    这样做将使您的表达式树完全符合您的要求。这样,创建的 SQL 将正是您所需要的。

    【讨论】:

    • 嗨,您对使用 where 子句 OR 代替 AND 有什么建议吗?
    • 是的……这有点难。我见过的最好的方法是通过规范模式并将谓词拉入规范,然后调用规范。或(someOtherSpecification)。基本上你必须编写自己的表达式树。示例代码和解释在这里:codeinsanity.com/archive/2008/08/13/…
    • 我有一个愚蠢的问题,如果这些日志是从数据库中获取的,我们是否获取所有日志然后在内存中过滤它们?如果是这样,那么我如何将条件传递给数据库
    • 它没有在内存中过滤它们。它正在构建一个查询并发送数据库中的所有条件(至少对于大多数 linq-to-x 提供程序而言)
    • 收到此错误LINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
    【解决方案2】:

    如果您需要基于列表/数组进行过滤,请使用以下内容:

        public List<Data> GetData(List<string> Numbers, List<string> Letters)
        {
            if (Numbers == null)
                Numbers = new List<string>();
    
            if (Letters == null)
                Letters = new List<string>();
    
            var q = from d in database.table
                    where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                    where (Letters.Count == 0 || Letters.Contains(d.Letter))
                    select new Data
                    {
                        Number = d.Number,
                        Letter = d.Letter,
                    };
            return q.ToList();
    
        }
    

    【讨论】:

    • 这是迄今为止最好、最正确的答案。有条件的 ||仅比较第一部分,如果第一部分为真则跳过第二部分...做得很好!
    • 此构造在生成的 SQL 查询中包含表达式的“或”部分。接受的答案将产生更有效的陈述。当然,这取决于数据提供者的优化。 LINQ-to-SQL 可能有更好的优化,但 LINQ-to-Entities 没有。
    【解决方案3】:

    我最终使用了一个类似于 Daren 的答案,但使用了 IQueryable 接口:

    IQueryable<Log> matches = m_Locator.Logs;
    
    // Users filter
    if (usersFilter)
        matches = matches.Where(l => l.UserName == comboBoxUsers.Text);
    
     // Severity filter
     if (severityFilter)
         matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);
    
     Logs = (from log in matches
             orderby log.EventTime descending
             select log).ToList();
    

    在访问数据库之前构建查询。该命令直到 .ToList() 最后才会运行。

    【讨论】:

      【解决方案4】:

      谈到条件 linq,我非常喜欢过滤器和管道模式。
      http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

      基本上,您为每个接受 IQueryable 和参数的过滤器案例创建一个扩展方法。

      public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
      {
          return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
      }
      

      【讨论】:

        【解决方案5】:

        我使用扩展方法解决了这个问题,允许在流畅的表达式中间有条件地启用 LINQ。这消除了使用if 语句分解表达式的需要。

        .If()扩展方法:

        public static IQueryable<TSource> If<TSource>(
                this IQueryable<TSource> source,
                bool condition,
                Func<IQueryable<TSource>, IQueryable<TSource>> branch)
            {
                return condition ? branch(source) : source;
            }
        

        这允许你这样做:

        return context.Logs
             .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
             .If(filterByUser, q => q.Where(p => p.User == user))
             .ToList();
        

        这里还有一个IEnumerable&lt;T&gt; 版本,可以处理大多数其他 LINQ 表达式:

        public static IEnumerable<TSource> If<TSource>(
            this IEnumerable<TSource> source,
            bool condition,
            Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
            {
                return condition ? branch(source) : source;
            }
        

        【讨论】:

          【解决方案6】:

          这样做:

          bool lastNameSearch = true/false; // depending if they want to search by last name,
          

          where 声明中包含此内容:

          where (lastNameSearch && name.LastNameSearch == "smith")
          

          表示在创建最终查询时,如果 lastNameSearchfalse,则查询将完全省略任何用于姓氏搜索的 SQL。

          【讨论】:

          • 取决于数据提供者。 LINQ-to-Entities 并没有优化它。
          【解决方案7】:

          另一种选择是使用类似 PredicateBuilder 讨论的 here。 它允许您编写如下代码:

          var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");
          
          var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                            .And (Product.IsSelling());
          
          var query = from p in Data.Products.Where (newKids.Or (classics))
                      select p;
          

          请注意,我只能使用 Linq 2 SQL。 EntityFramework 不实现 Expression.Invoke,这是此方法工作所必需的。我对此问题有疑问here

          【讨论】:

          • 对于那些在存储库顶部使用业务逻辑层以及 AutoMapper 等工具在数据传输对象和实体模型之间进行映射的人来说,这是一个很好的方法。使用谓词构建器将允许您在将 IQueryable 发送到 AutoMapper 进行展平之前动态修改它,即将列表放入内存。请注意,它还支持实体框架。
          【解决方案8】:

          这不是最漂亮的事情,但您可以使用 lambda 表达式并选择性地传递您的条件。在 TSQL 中,我做了很多以下操作来使参数成为可选参数:

          WHERE 字段 = @FieldVar 或 @FieldVar 为空

          您可以使用以下 lambda(检查身份验证的示例)复制相同的样式:

          MyDataContext db = new MyDataContext();

          void RunQuery(string param1, string param2, int?param3){

          功能检查用户 = 用户 =>

          ((param1.Length > 0)? user.Param1 == param1 : 1 == 1) &&

          ((param2.Length > 0)? user.Param2 == param2 : 1 == 1) &&

          ((param3 != null)? user.Param3 == param3 : 1 == 1);

          User foundUser = db.Users.SingleOrDefault(checkUser);

          }

          【讨论】:

            【解决方案9】:

            我最近有一个类似的要求,最终在他的 MSDN 中找到了这个。 CSharp Samples for Visual Studio 2008

            下载的 DynamicQuery 示例中包含的类允许您在运行时以以下格式创建动态查询:

            var query =
            db.Customers.
            Where("City = @0 and Orders.Count >= @1", "London", 10).
            OrderBy("CompanyName").
            Select("new(CompanyName as Name, Phone)");
            

            使用它,您可以在运行时动态构建查询字符串并将其传递给 Where() 方法:

            string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
            var q = from c in db.Customers.Where(queryString, null)
                    orderby c.CompanyName
                    select c;
            

            【讨论】:

              【解决方案10】:

              你可以创建和使用这个扩展方法

              public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
              {
                  return isToExecute ? source.Where(predicate) : source;
              }
              

              【讨论】:

                【解决方案11】:

                只需使用 C# 的 && 运算符:

                var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")
                

                编辑:啊,需要更仔细地阅读。您想知道如何有条件地添加附加子句。在那种情况下,我不知道。 :) 我可能会做的只是准备几个查询,然后执行正确的查询,这取决于我最终需要什么。

                【讨论】:

                  【解决方案12】:

                  您可以使用外部方法:

                  var results =
                      from rec in GetSomeRecs()
                      where ConditionalCheck(rec)
                      select rec;
                  
                  ...
                  
                  bool ConditionalCheck( typeofRec input ) {
                      ...
                  }
                  

                  这可行,但不能分解为表达式树,这意味着 Linq to SQL 将对每条记录运行检查代码。

                  或者:

                  var results =
                      from rec in GetSomeRecs()
                      where 
                          (!filterBySeverity || rec.Severity == severity) &&
                          (!filterByUser|| rec.User == user)
                      select rec;
                  

                  这可能适用于表达式树,这意味着 Linq to SQL 将得到优化。

                  【讨论】:

                    【解决方案13】:

                    好吧,我认为您可以将过滤条件放入谓词的通用列表中:

                        var list = new List<string> { "me", "you", "meyou", "mow" };
                    
                        var predicates = new List<Predicate<string>>();
                    
                        predicates.Add(i => i.Contains("me"));
                        predicates.Add(i => i.EndsWith("w"));
                    
                        var results = new List<string>();
                    
                        foreach (var p in predicates)
                            results.AddRange(from i in list where p.Invoke(i) select i);               
                    

                    这会产生一个包含“me”、“meyou”和“mow”的列表。

                    您可以通过对所有谓词进行 OR 运算的完全不同的函数中的谓词执行 foreach 来优化它。

                    【讨论】:

                      猜你喜欢
                      • 2019-07-16
                      • 2017-11-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-03-23
                      • 1970-01-01
                      • 2018-11-03
                      相关资源
                      最近更新 更多