对 Linq 查询进行空检查是惯用的吗?
Is null-checking on Linq queries idiomatic?
使用 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 中被优化掉了。如果查询对象而不是数据库,则空检查将是强制性的,但由于可以创建可能在对象上失败但在数据库上不会失败的通用 IQueriable<Foo>
查询,因此第一个选项始终有效,尽管 Linq SQL 代码比第二个选项长一点。 提供的第三个选项似乎是两全其美,但在 foo.Bar
的类型为 bool? 的情况下不起作用:foos.Where(foo => foo.Bar)
(由于此处未进行隐式转换,导致类型错误)。
是否应该努力编写通用查询,使其在最初设计的上下文之外使用时不会失败?
基础数据存储技术多年来一直在发生变化。代码的寿命似乎比任何人想象的都要长。我不会建立关于底层存储提供商的假设。
如果 Linq 是您的抽象层,假设任何 Linq 提供程序都可能在应用程序生命周期的某个时刻插入。
The former generates an extra Bar IS NOT NULL predicate that is probably being optimized away in most DBMS'es.
我不是数据库性能专家,但我认为与许多查询的总体成本相比,额外的 IS NOT NULL 的性能成本会很小。
我会编写特定于您正在使用的提供商的查询。 Linq-to-objects
可能看起来与 linq-to-sql/entities
相似,但它们的行为却截然不同。
- 它们不支持相同的功能
- 他们return不是同一类型
- 它们的执行方式不同
如果您的 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
查询。尝试找到同时满足两者的查询是否有益?
第三种选择是使用 "lifted" <
运算符,如果任一操作数为空,则 returns 为 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
为真的记录(即,既不是 false 也不是 null),那么你可以写
foos.Where(foo => foo.Bar == true)
或
foos.Where(foo => foo.Bar.GetValueOrDefault())
Linq-to-SQL 翻译器将负责输出正确的 SQL。在这种情况下,您的伪 ORM 代码反映了可为 null 的 Bar 列。这在您的应用程序代码中很有用,在处理返回的结果时,但对于 MS SQL 查询的 where
子句,根据您的示例,这不是很有用。我会把它留给你的第二个选择。
使用 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 中被优化掉了。如果查询对象而不是数据库,则空检查将是强制性的,但由于可以创建可能在对象上失败但在数据库上不会失败的通用 IQueriable<Foo>
查询,因此第一个选项始终有效,尽管 Linq SQL 代码比第二个选项长一点。 foo.Bar
的类型为 bool? 的情况下不起作用:foos.Where(foo => foo.Bar)
(由于此处未进行隐式转换,导致类型错误)。
是否应该努力编写通用查询,使其在最初设计的上下文之外使用时不会失败?
基础数据存储技术多年来一直在发生变化。代码的寿命似乎比任何人想象的都要长。我不会建立关于底层存储提供商的假设。
如果 Linq 是您的抽象层,假设任何 Linq 提供程序都可能在应用程序生命周期的某个时刻插入。
The former generates an extra Bar IS NOT NULL predicate that is probably being optimized away in most DBMS'es.
我不是数据库性能专家,但我认为与许多查询的总体成本相比,额外的 IS NOT NULL 的性能成本会很小。
我会编写特定于您正在使用的提供商的查询。 Linq-to-objects
可能看起来与 linq-to-sql/entities
相似,但它们的行为却截然不同。
- 它们不支持相同的功能
- 他们return不是同一类型
- 它们的执行方式不同
如果您的 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
查询。尝试找到同时满足两者的查询是否有益?
第三种选择是使用 "lifted" <
运算符,如果任一操作数为空,则 returns 为 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
为真的记录(即,既不是 false 也不是 null),那么你可以写
foos.Where(foo => foo.Bar == true)
或
foos.Where(foo => foo.Bar.GetValueOrDefault())
Linq-to-SQL 翻译器将负责输出正确的 SQL。在这种情况下,您的伪 ORM 代码反映了可为 null 的 Bar 列。这在您的应用程序代码中很有用,在处理返回的结果时,但对于 MS SQL 查询的 where
子句,根据您的示例,这不是很有用。我会把它留给你的第二个选择。