【问题标题】:How to reuse where clauses in Linq To Sql queries如何在 Linq To Sql 查询中重用 where 子句
【发布时间】:2011-06-24 13:37:58
【问题描述】:

我有用户搜索记录类型的记录。他们在文本框中输入搜索词,然后我通过将多个字段与搜索词匹配来搜索记录。

我的查询如下:

var results = from record in DataContext.Records
              where
                   record.Field1.ToLower().Contains(term) ||
                   record.Field2.ToLower().Contains(term) ||
                   record.Field3.ToLower().Contains(term)
              select record;

我有许多查询都使用相同的过滤器,因此我想提取过滤器以便重复使用。比如:

var filter = new Func<Record, string, bool>(
                (record, term) =>
                    record.Field1.ToLower().Contains(term) ||
                    record.Field2.ToLower().Contains(term) ||
                    record.Field3.ToLower().Contains(term)
             );

var results = from record in DataContext.Records
              where filter(record, term)
              select record;

但是,它不起作用,因为:

方法 'System.Object DynamicInvoke(System.Object[])' 不支持 SQL 转换。

如何在查询中重复使用我的 where 条件?

【问题讨论】:

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


    【解决方案1】:

    你需要构建一个表达式而不是一个函数:

    Expression<Func<Record, bool>> filter = 
      record => record.Field1.ToLower().Contains(term);  // rest omitted
    

    lambda 表达式保持不变,但您需要将其返回到 Expression&lt;Func&lt;Record, bool&gt;&gt; 类型的变量中——这将使 C# 编译器将其编译为表达式而不是委托,从而允许将其传递给 LINQ SQL。

    但是,您将无法使用带有 C# 语法 where 子句的表达式变量:您需要使用 Where 扩展方法:

    var results = DataContext.Records.Where(filter);
    

    编辑添加:如果您希望能够根据不同的术语创建过滤器,您只需要一种方法来从术语生成表达式:

    private static Expression<Func<Record, bool>> Filter(string term)
    {
      return r => r.Field1.ToLower().Contains(term);
    }
    
    var results = DataContext.Records.Where(Filter(term));
    

    如果您希望像现在一样将 filter 保留为 lambda,您可以这样做,但泛型会有点嵌套:

    Func<string, Expression<Func<Record, bool>>> filter =
      term => (r => r.Field1.ToLower().Contains(term));
    
    var results = DataContext.Records.Where(filter(term));
    

    无论如何,重要的是 Where 子句中的内容必须是 Expression&lt;Func&lt;Record, bool&gt;&gt; - 但如上所示,您可以通过动态构建合适的表达式来使表达式依赖于 term。如果您在 Where 子句中拼写出过滤器,这正是 LINQ to SQL 会做的事情。

    【讨论】:

    • where 子句的 C# 语法只是编译器的糖;它生成相同的代码。
    • 是的,但是如果过滤器的类型是 Expression 而不是 Func,那么它不支持 filter(record) 语法。你得到错误“过滤器是一个变量,但像方法一样使用”。如果你只写where filter,那么你会得到'不能隐式地将类型 Expression(...) 转换为 bool'。
    • 谢谢 - 这确实有道理。但是我没能成功,因为在我给出的(错误的)示例中,term 是我的 lambda 表达式中的一个闭包,并且要重用过滤器,我需要将其设为参数。现在我的过滤器是一个Expression&lt;Lambda&lt;Record, string, bool&gt;&gt;,其中应该是一个Expression&lt;Lambda&lt;Record, bool&gt;&gt;。这仍然可行吗?
    • 是和不是。 Where 需要Expression&lt;Func&lt;T, bool&gt;&gt;,因此您的表达式必须属于该类型。但是很容易做到这一点。我会更新答案。
    • 虽然我选择了CompiledQuery,但这种方法的优点是没有将表达式绑定到DataContext。无论如何,我从中学到了很多!
    【解决方案2】:

    使用CompiledQuery

    var filter = CompiledQuery.Compile(
        (DatabaseDataContext dc, Record record, string term) =>
            record.Field1.ToLower().Contains(term) ||
            record.Field2.ToLower().Contains(term) ||
            record.Field3.ToLower().Contains(term)
    );
    
    var results = from record in DataContext.Records
                  where filter(DataContext, record, term)
                  select record;
    

    如需了解更多信息,请参阅How to: Store and Reuse Queries

    【讨论】:

    • 我认为这不会像所写的那样工作——CQ.Compile 产生一个 Func,因此它不能在 LINQ to SQL 查询的 Where 子句中使用。你不需要像var query = CompiledQuery.Compile((string term) =&gt; from r in DataContext.Records where r.Field1.ToLower().Contains(term) select r); 然后var results = query("someTerm"); 这样的东西吗?没有使用 CompiledQuery,所以我可能错了!
    • @itowlson:确实有效。这就是您创建可在 LINQ to SQL 中使用的可重用函数的方式。查询提供程序将识别已编译的函数并根据需要生成查询。它不仅生成完整的查询,还可以生成部分查询(例如这个条件)。
    • 这按预期工作。唯一奇怪的是,我还在 Linq To Object 查询中重用了该过滤器,然后我仍然必须将 Linq To Sql DataContext 作为参数传递。有什么方法可以独立于 DataContext 定义查询?
    • @Xavier:生成的委托可以像普通的 C# 委托一样使用。它只是在涉及其编译的数据上下文的 LINQ to SQL 查询中工作有额外的好处。据我了解它是如何工作的,委托的 Target 属性设置为关联的编译查询,该查询负责查询生成。我认为您可能能够编译一个适用于任何数据上下文的程序,但您将无法使用任何 LINQ to SQL 类作为参数。仅常见类型(intstring 等)。我将对此进行探索并报告。
    • @Xavier:再想一想,这看起来不太合理。它的用途非常有限,实际上毫无价值。它仅在与为其编译的数据上下文一起使用时才有用。
    【解决方案3】:

    除了其他人指出的Expression&lt;Func&lt;Record, bool&gt;&gt; 问题,我建议查看PredicateBuilder。非常适合动态组合 lambda 表达式。

    【讨论】:

      【解决方案4】:

      我认为您需要将其设为Expression&lt;Func&lt;Record, bool&gt;&gt;。否则,它会尝试将实际的 C# 方法调用转换为 SQL,而不是它的描述。这并不能保证 版本可以正常工作;我不确定哪些字符串函数可以转换为 SQL。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        • 1970-01-01
        • 1970-01-01
        • 2015-08-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多