【问题标题】:LINQ to SQL - How to efficiently do either an AND or an OR search for multiple criteriaLINQ to SQL - 如何有效地执行 AND 或 OR 搜索多个条件
【发布时间】:2010-03-23 15:07:29
【问题描述】:

我有一个 ASP.NET MVC 站点(它使用 Linq To Sql 作为 ORM)和一个客户想要一个针对定制数据库的搜索工具的情况,他们可以选择执行“AND”搜索(所有条件匹配)或“或”搜索(任何条件匹配)。该查询非常复杂且冗长,我想知道是否有一种简单的方法可以做到这两点,而无需创建和维护两个不同版本的查询。

例如,当前的“AND”搜索看起来像这样(但这是一个非常简化的版本):

private IQueryable<SampleListDto> GetSampleSearchQuery(SamplesCriteria criteria)
{
   var results = from r in Table where
            (r.Id == criteria.SampleId) &&
            (r.Status.SampleStatusId == criteria.SampleStatusId) &&
            (r.Job.JobNumber.StartsWith(criteria.JobNumber)) &&
            (r.Description.Contains(criteria.Description))
        select r;

}

我可以复制这个并将 && 替换为 ||运营商获得'OR'版本,但感觉必须有更好的方法来实现这一点。有人对如何以一种易于维护的高效灵活的方式实现这一点有任何建议吗?谢谢。

【问题讨论】:

    标签: c# asp.net-mvc linq linq-to-sql


    【解决方案1】:

    您可以按照以下方式创建扩展方法

    public static IQueryable<T> BoolWhere<T>(this IQueryable<T> source, Expression<Func<T, TValue>> selector, bool isOr) {
      //use isOr value to determine what expression to build and add to the source
    }
    

    where 'isOr' 将决定是使用'and' 表达式还是'or' 表达式。然后您可以按照以下方式构建您的查询

    bool isOr = true; //or false
    var results = Data.BoolWhere(r => r.Id == criteria.SampleId, isOr)
      .BoolWhere(r => r.Status.SampleStatusId == criteria.SampleStatusId, isOr)
      .BoolWhere(r => r.Job.JobNumber.StartsWith(criteria.JobNumber), isOr)
      .BoolWhere(r => r.Description.Contains(criteria.Description), isOr)
    

    【讨论】:

      【解决方案2】:

      如果你有这些扩展方法:

      public static class BoolExtensions
      {
          public static bool And<TR, TC>(this IEnumerable<Func<TR, TC, bool>> statements, TR value, TC criteria)
          {
              foreach (var statement in statements)
              {
                  if (!statement.Invoke(value, criteria))
                  {
                      return false;
                  }
              }
      
              return true;
          }
      
          public static bool Or<TR, TC>(this IEnumerable<Func<TR, TC, bool>> statements, TR value, TC criteria)
          {
              foreach (var statement in statements)
              {
                  if (statement.Invoke(value, criteria))
                  {
                      return true;
                  }
              }
      
              return false;
          }
      }
      

      然后你可以将你的陈述声明为一个列表:

      List<Func<TypeOfR, TypeOfC, bool>> statements = new List<Func<TypeOfR, TypeOfC, bool>>()
      {
          { (r, c) => r.Id == c.SampleId },
          { (r, c) => r.Status.SampleStatusId == c.SampleStatusId },
          ...
      };
      

      并将您的查询写为:

      var results = from r in Table where
              statements.And(r, criteria)
          select r;
      

      || 版本:

      var results = from r in Table where
              statements.Or(r, criteria)
          select r;
      

      并将语句保留在一个地方。

      【讨论】:

      • 这是另一个聪明的想法,并且非常适合对象。不幸的是,它需要使用 IQueryable 而不是 IEnumerable 才能有机会转换为 SQL。我确实尝试过更改扩展方法,但没有得到任何乐趣。不过,感谢您的建议。
      【解决方案3】:

      【讨论】:

      • 我已经看过了 - 但我希望找到一种方法来保持强类型表达式的优势。
      【解决方案4】:

      也许比 Jens 的想法更容易可视化,如果您只是在寻找组合的 Or 和组合的 And(而不是一些混合),您总是可以将您的相等性表达为测试列表,然后应用 Any 或 All 运算符给它。例如:

      var queries = new List<Func<Table,SampleListDto,bool>>{
            ((a,b) => a.Id == b.SampleId),
            ((a,b) => a.Status.SampleStatusId == b.SampleStatusId),
            ((a,b) => a.Job.JobNumber.StartsWith(b.JobNumber)),
            ((a,b) => a.Description.Contains(b.Description))
      };
      
      var results = Table.Where(t=> queries.All(q => q(t, criteria)); // returns the && case
      // or:  var results = Table.Where(t=>queries.Any(q=>q(t,criteria));
      

      与 Jens 一样,不知道将其转换为 SQL 的效率如何,但如果这成为一个问题,那么您最好还是将其转换为原生 SQL。

      【讨论】:

        【解决方案5】:

        这里是关于 PredicateBuilder

        这应该与 LINQ to SQL 兼容。

        可以创建一个新函数来使用 PredicateBuilder 函数 And or Or:

        private IQueryable<SampleListDto> GetSampleSearchQuery(
            SamplesCriteria criteria,
            Func<Expression<Func<SampleListDto, bool>>,
                Expression<Func<SampleListDto, bool>>,
                Expression<Func<SampleListDto, bool>>> logicExpr) 
        { 
           var results = from r in Table where 
                    logicExpr(r => r.Id == criteria.SampleId,
                    logicExpr(r => r.Status.SampleStatusId == criteria.SampleStatusId,
                    logicExpr(r => r.Job.JobNumber.StartsWith(criteria.JobNumber),
                    logicExpr(r => r.Description.Contains(criteria.Description)))))
                select r; 
        
        }
        

        And 和 Or 函数如下所示:

        private IQueryable<SampleListDto> GetOrSampleSearchQuery(
            SamplesCriteria criteria) 
        { 
            return GetSampleSearchQuery(criteria, PredicateBuilder.Or<SampleListDto>);
        }
        private IQueryable<SampleListDto> GetAndSampleSearchQuery(
            SamplesCriteria criteria)
        {
            return GetSampleSearchQuery(criteria, PredicateBuilder.And<SampleListDto>);
        }
        

        【讨论】:

          【解决方案6】:

          您可以使用 T4 模板来生成每个方法。这样你仍然会有强类型的表达式。右键单击您的项目并选择添加->新建项目->文本模板

          模板会像这样循环:

          <#@ template debug="false" hostspecific="false" language="C#" #>
          <#@ output extension=".cs" #>
          
          namespace YourNamespaceName
          {
              public partial class YourClassName
              {
              <# Generate(true); #>
              <# Generate(false); #>
              }
          }
          <#+
              private void Generate(bool isOr)
              {
                  string op = isOr ? "||" : "&&";
                  string methodName = "GetSampleSearchQuery" + (isOr ? "Or" : "And");
          #>
              private IQueryable<SampleListDto> <#= methodName #>(SamplesCriteria criteria)
              {
                  var results = from r in Table where
                          (r.Id == criteria.SampleId) <#= op #>
                          (r.Status.SampleStatusId == criteria.SampleStatusId) <#= op #>
                          (r.Job.JobNumber.StartsWith(criteria.JobNumber)) <#= op #>
                          (r.Description.Contains(criteria.Description))
                      select r;
                  return results;
              }
          <#+
              }
          #>
          

          只需将您的类更改为部分类并将任何其他需要的过滤器添加到 T4 模板并使用 &lt;#= op #&gt; 而不是运算符。 (在解决方案资源管理器中右键单击 T4 模板并选择运行自定义工具以强制更新) 您最终会得到两种方法:GetSampleSearchQueryOrGetSampleSearchQueryAnd 用于重复的 Or 和 And 过滤器,两者都只保留在一个地方。

          【讨论】:

          • 这是一个聪明的主意!理想情况下,我宁愿以编程方式进行,但这似乎是下一个最好的主意。也很好地说明了您可以通过 T4 模板做什么。
          【解决方案7】:

          您可以使用this answer 中介绍的技术。 Compose 方法可以这样调用:- first.Compose(second, Expression.And); 要么 first.Compose(second, Expression.Or);

          所以操作的类型就变成了一个参数。

          通过将它反复应用到所有子句中,您可以构建所需的表达式,这都是强类型和纯表达式代码。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-07-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多