对Linq查询惯用的空值检查?

时间:2015-07-02 15:40:23

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

使用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检查将是必需的,但由于可以创建可能在对象上失败而在数据库上失败的通用IQueriable<Foo>查询,因此第一个选项将始终有效,尽管Linq和SQL代码比第二个选项稍长。由Michael Liu提供的第三个选项似乎是两个方面中最好的,但在foo.Bar类型为 bool?的情况下无效:<{em}:foos.Where(foo => foo.Bar) (导致类型错误,因为此处未进行隐式转换)。

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

4 个答案:

答案 0 :(得分:4)

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

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

  

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

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

答案 1 :(得分:2)

我会编写特定于您正在使用的提供程序的查询。 Linq-to-objects可能与linq-to-sql/entities类似,但行为却截然不同。

  1. 他们不支持相同的功能
  2. 他们不会返回相同的类型
  3. 执行方式不同
  4. 如果您的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查询差异很大。尝试找到满足两者的查询是否有益?

答案 2 :(得分:2)

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

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

如果foos的编译时类型为IEnumerable<Foo>,那么foo.Bar < 42相当于

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

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

如果foos的编译时类型为IQueryable<Foo>,那么foo.Bar < 42相当于

foo.Bar < (int?)42

并且由LINQ提供程序决定是否以及如何检查空值。

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

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

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

答案 3 :(得分:0)

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