当对象为空时,Linq到对象VS Linq到SQL

gdo*_*ica 6 .net c# linq linq-to-entities linq-to-sql

我有这个Linq对象查询:

var result = Users.Where(u => u.Address.Country.Code == 12)
Run Code Online (Sandbox Code Playgroud)

如果Address或Country为null,我会收到异常.
为什么这个查询不会检查地址是否为空并且只是在该地址之后?这样我就不需要写这个可怕的查询了:

var result = Users.Where(u => u.Address != null &&
                              u.Address.Country != null &&  
                              u.Address.Country.Code == 12)
Run Code Online (Sandbox Code Playgroud)

在Linq to SQL中,第一个查询将完成工作(当然是出于其他原因).

是一种避免linq中的"空检查"对象的方法吗?

Eri*_*ert 7

不幸的是,在C#(以及许多其他编程语言)中,"null"被不一致地处理.可空算术被解除了.也就是说,如果你对可空整数进行算术运算,并且没有操作数为空,那么你得到正常的答案,但是如果它们中的任何一个为空,你就得到null.但是"成员访问"操作员没有解除; 如果你给成员访问"."一个空操作数.运算符,它抛出异常而不是返回空值.

如果我们从头开始设计类型系统,我们可能会说所有类型都是可空的,并且包含空操作数的任何表达式都会产生null结果,而不管操作数的类型如何.因此,使用null接收器或null参数调用方法将产生null结果.这个系统很有意义,但显然我们现在实施它已经太晚了; 已经编写了数百万行代码来预期当前的行为.

我们已经考虑过添加一个"提升"的成员访问操作符,可能是标记的.?.所以你可以说where user.?Address.?Country.?Code == 12,那会产生一个可以为空的int,然后可以将它作为可空的int通常比较为12.然而,这一点从来没有超过"是的,在未来的版本中可能很好"的设计过程阶段,所以我不希望它很快.


更新:上面提到的"Elvis"运算符是在C#6.0中实现的.


Jam*_*are 5

不,这是一个空引用异常就像访问var x = u.Address.Country.Code;将是一个NullReferenceException.

您必须始终确保您要取消引用的内容不在nullLINQ to对象中,就像使用任何其他代码语句一样.

您可以使用&&您拥有的逻辑执行此操作,也可以链接Where子句(尽管这将包含更多迭代器并且执行速度可能更慢):

var result = Users.Where(u => u.Address != null)
                  .Where(u.Address.Country != null)
                  .Where(u.Address.Country.Code == 12);
Run Code Online (Sandbox Code Playgroud)

注意:Maybe()下面的方法只是作为"你也可以"的方法提供,我不是说它的好坏,只是展示一些人做的事情.如果你不喜欢Maybe()我只是重复我见过的各种解决方案,请不要投票.

我已经看到了一些Maybe()编写的扩展方法,可以让你做你想做的事情.有些人不喜欢这些,因为它们是对null引用进行操作的扩展方法.我不是说这是好还是坏,只是有些人觉得违反了良好的OO行为.

例如,您可以创建一个扩展方法,如:

public static class ObjectExtensions
{
    // returns default if LHS is null
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator)
        where TInput : class
    {
        return (value != null) ? evaluator(value) : default(TResult);
    }

    // returns specified value if LHS is null
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator, TResult failureValue)
        where TInput : class
    {
        return (value != null) ? evaluator(value) : failureValue;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后做:

var result = Users.Where(u => u.Maybe(x => x.Address)
                  .Maybe(x => x.Country)
                  .Maybe(x => x.Code) == 12);
Run Code Online (Sandbox Code Playgroud)

从本质上讲,这只是null将链条向下级联(或者在非引用类型的情况下为默认值).

更新:

如果您想提供非默认的失败值(如果任何部分为空,则代码为-1),您只需将新的失败值传递给Maybe():

// if you wanted to compare to zero, for example, but didn't want null
// to translate to zero, change the default in the final maybe to -1
var result = Users.Where(u => u.Maybe(x => x.Address)
                  .Maybe(x => x.Country)
                  .Maybe(x => x.Code, -1) == 0);
Run Code Online (Sandbox Code Playgroud)

就像我说的,这只是众多解决方案中的一种.有些人不喜欢能够从null引用类型调用扩展方法,但是有些人倾向于使用它来解决这些null级联问题.

但是,目前C#中没有内置的null-de-reference运算符,所以要么像以前一样使用条件空值检查,要么链接你的Where()语句以便它们过滤掉null,或者构建一些东西让你nullMaybe()上面的方法一样级联.