【问题标题】:Is null-checking on Linq queries idiomatic?对 Linq 查询进行空值检查是惯用的吗?
【发布时间】:2015-07-02 15:40:23
【问题描述】:

使用Linq-to-SQL,不知下面三个哪个更惯用,

  • foos.Where(foo => foo.Bar.HasValue && foo.Bar.Value < 42)
  • foos.Where(foo => foo.Bar.Value < 42)
  • foos.Where(foo => foo.Bar < 42)

第一个选项生成一个额外的Bar IS NOT NULL 谓词,该谓词可能在大多数 DBMS 中已被优化掉。如果查询的是对象而不是数据库,则 null-check 将是强制性的,但由于可以创建通用的 IQueriable<Foo> 查询,这些查询可能在对象上失败但在数据库上失败,第一个选项将始终有效,尽管 Linq 和 SQL代码比第二个选项长一点。 Michael Liu 提供的第三个选项似乎是两全其美,但在 foo.Bar 的类型为 bool? 的情况下不起作用:foos.Where(foo => foo.Bar)(导致类型错误因为这里没有进行隐式转换)。

是否应该努力编写在最初设计的上下文之外使用时不会失败的通用查询?

【问题讨论】:

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


    【解决方案1】:

    基础数据存储技术多年来一直在发生变化。代码的寿命似乎比任何人想象的要长得多。我不会建立关于底层存储提供程序的假设。

    如果 Linq 是您的抽象层,则假设任何 Linq 提供程序都可能在应用程序生命周期的某个时间点插入。

    前者会生成一个额外的 Bar IS NOT NULL 谓词,该谓词可能在大多数 DBMS 中已被优化掉。

    我不是数据库性能专家,但我认为与许多查询的总体成本相比,额外的 IS NOT NULL 的性能成本会很小。

    【讨论】:

    • 同意 - 从数据服务层返回结果时,我不使用 Linq 枚举,我使用 X 列表
    【解决方案2】:

    第三种选择是使用“提升”< 运算符,如果任一操作数为空,则返回 false。这使您可以省略对 null 的显式检查并适用于所有 LINQ 提供程序:

    foos.Where(foo => foo.Bar < 42)
    

    如果foos的编译时类型是IEnumerable&lt;Foo&gt;,那么foo.Bar &lt; 42等价于

    foo.Bar.GetValueOrDefault() < 42 && foo.Bar.HasValue
    

    除了foo.Bar 只被访问一次。 (我是通过在 Reflector 中反编译程序得到的。有趣的是,编译器选择在 检查 null 之前执行比较,但它作为微优化是有意义的。)

    如果foos的编译时类型是IQueryable&lt;Foo&gt;,那么foo.Bar &lt; 42等价于

    foo.Bar < (int?)42
    

    这取决于 LINQ 提供程序是否以及如何检查空值。


    更新:如果foo.Bar的类型是bool?,并且你只想包含foo.Bar为真的记录(即,既不是假也不是空),那么你可以写

    foos.Where(foo => foo.Bar == true)
    

    foos.Where(foo => foo.Bar.GetValueOrDefault())
    

    【讨论】:

    • 你能展示一个lifted &lt;的示例实现吗?我很难理解它是如何知道它是IQueryable 还是IEnumerable
    • @Aducci:编译器根据 foos 的编译时类型将 lambda 表达式视为一个或另一个。
    • @Aducci 这是一个 lambda,而 lambdas 是 C# 中为数不多的上下文表达式之一(也就是说,该表达式的编译方式及其含义取决于该表达式的使用方式)。如果foosIEnumerable&lt;T&gt;,那么Enumerable.Where 将期望Func&lt;T, bool&gt; 作为第一个参数,这意味着lambda 将被编译为该委托类型。如果 foosIQueryable&lt;T&gt;,它将调用 Queryable.Where 并期待 Expression&lt;Func&lt;T, bool&gt;&gt;,因此 lambda 将被编译为封装该委托类型的表达式。
    • @Aducci 因此,这反过来又确定编译器是否正在尝试将该代码编译成一个委托,该委托指向一个方法,该方法包含对&lt; 运算符的调用,在int? 上调用和一个int,或者如果它需要创建一个Expression,表示该运算符对这两个表达式的调用(一个是封闭变量的属性访问,一个是常量整数)。
    • 如果foo.Bar的类型是bool?,你想把null当作false,那么你可以写foos.Where(foo =&gt; foo.Bar.GetValueOrDefault())。要将 null 视为 true,请将 true 作为参数传递给 GetValueOrDefault。
    【解决方案3】:

    我会编写特定于您正在使用的提供程序的查询。 Linq-to-objects 可能看起来与linq-to-sql/entities 相似,但它们的行为非常不同。

    1. 它们不支持相同的功能
    2. 它们返回的类型不同
    3. 它们的执行方式不同

    如果您的 linq 提供程序在未来发生变化,您很可能无论如何都必须重写您的查询。例如,有一些特定于提供者的类,如SqlFunctions,它们只适用于给定的提供者。您无法预测未来的提供商将支持什么或它将如何工作。

    linq-to-entities 中的简单分组查询

    var query = from f in foos
                group f by new { f.Name, f.Bar.Value } into g
                select ...
    

    会产生一个非常不同的linq-to-objects 查询。尝试找到同时满足两者的查询是否有益?

    【讨论】:

      【解决方案4】:

      Linq-to-SQL 转换器将负责输出正确的 SQL。在这种情况下,您的伪 ORM 代码反映了可为空的 Bar 列。这在您的应用程序代码中很有用,在处理返回的结果时,但对于您的示例中的 MS SQL 查询的 where 子句而言,这并不重要。我会把它留给你的第二个选择。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-31
        • 2017-11-02
        • 2016-08-05
        • 2021-10-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多