为什么我没有收到Null引用异常?

时间:2019-03-07 16:14:02

标签: c# linq linq-to-entities

我正在使用LINQ to Entities从数据库中获取一些数据。以下是我的查询。

var location = from l in dbContext.Locations
join e in dbContext.Equipment on l.ID equals e.LocationID into rs1
from e in rs1.DefaultIfEmpty()
where ids.Contains(l.ID)
select new
{
    EquipmentClass = e,
    LocationID = l.ID,
    LocationName = l.Name,
    EquipmentName = e == null ? null : e.Name,
    Description = e == null ? null : e.Description,
    InServiceStatus = e == null ? false : e.InServiceStatus,
    EquipmentType = e.EquipmentType.Name
};

foreach (var item in location)
{
    // some logic
}

在上面的代码中,ids是我传递用来过滤结果的整数的列表。得到结果后,我发现返回记录之一的值为EquipmentClass。我执行了一些空检查,但意识到我忘记对其中一个属性进行空检查。现在,我期望在EquipmentType = e.EquipmentType.Name上得到一个空引用异常,但我没有。令我惊讶的是,它工作得很好,并且设置为null。我的EquipmentClass的属性类型为EquipmentType,这是另一个类。 EquipmentType有一个Name属性,它是一个字符串。

  1. 为什么这不会引发空引用异常?

作为测试,我从InServiceStatus = e == null ? false : e.InServiceStatus中删除了空检查,并且在使用查询运行foreach循环时,它以无效的操作异常失败。

  1. 这是否意味着我只需要对非空值进行空检查?

更新

foreach (var item in location)
{
    var p = item.EquipmentClass.EquipmentType.Name;
}

在查询之后立即添加了此内容。关于p的赋值,我得到一个空引用异常。我不确定它到底能达到什么程度,因为它应该在foreach循环的第一行失败。如果没有在行中声明变量p,我不会收到空引用异常。如果有人能解释发生了什么,我将不胜感激。仅供参考,在foreach循环开始时,item.EquipmentClassitem.EquipmentType的值都为空。

Update2: 我发现this link在使用LINQ to SQL的情况下似乎存在几乎相同的问题。我明白了答案的要点,但并不完全理解它对上面两个问题的潜在影响。

2 个答案:

答案 0 :(得分:1)

您的更新帮助我了解了您的真正担忧。关于LINQ查询,您需要了解的概念是deferred execution in LINQ

请通过以下链接获取更多详细信息:

What are the benefits of a Deferred Execution in LINQ?

Linq - What is the quickest way to find out deferred execution or not?

现在您的情况如何?您已将查询存储在location变量中。该特定步骤只是初始化部分。它实际上并不会通过ORM层在数据库上执行查询。这就是您可以测试的方式。

在使用LINQ查询初始化location变量的代码行上放置一个断点。当调试器在Visual Studio中停止时,请转到SQL Server Management Studio(SSMS)并启动SQL Server Profiler会话。

现在在Visual Studio中按 F10 可以跳过代码语句。此时,您将在探查器会话中看到绝对没有查询执行,如下所示:

enter image description here

这是因为LINQ查询直到这个时候才被执行。

现在您到达下面的代码行:

foreach (var item in location)
{
    var p = item.EquipmentClass.EquipmentType.Name;
}

一旦您进入foreach循环,将触发LINQ查询,您将在SQL Server事件探查器中看到相应的登录跟踪会话。因此,除非被枚举,否则不会触发LINQ查询。这称为延迟执行,即运行时将执行推迟到枚举为止。如果您从不枚举代码中的location变量,那么查询执行将永远不会发生。

因此,要回答您的查询,只有在查询被触发时才会出现异常。在那之前!

更新1 :您是在说-我没有收到空引用异常。是!您将不会获得空引用异常,直到找到缺少相应RHS合并记录的记录。请看下面的代码以更好地理解:

class Program
{
    static void Main(string[] args)
    {
        var mylist1 = new List<MyClass1>();
        mylist1.Add(new MyClass1 { id = 1, Name1 = "1" });
        mylist1.Add(new MyClass1 { id = 2, Name1 = "2" });

        var mylist2 = new List<MyClass2>();
        mylist2.Add(new MyClass2 { id = 1, Name2 = "1" });

        var location = from l in mylist1
                       join e in mylist2 on l.id equals e.id into rs1
                       from e in rs1.DefaultIfEmpty()
                       //where ids.Contains(l.ID)
                       select new
                       {
                           EquipmentClass = e,
                           InServiceStatus = e == null ? 1 : e.id,
                           EquipmentType = e.id
                       };

        foreach (var item in location)
        {

        }
    }
}

class MyClass1
{
    public int id { get; set; }
    public string Name1 { get; set; }
}

class MyClass2
{
    public int id { get; set; }

    public string Name2 { get; set; }
}

因此,现在,当我开始迭代location变量时,它在第一次迭代中不会中断。它在第二次迭代中中断。当无法获取与MyClass1中具有id 2的mylist1对象相对应的记录/对象时,它将中断。 mylist2id 2没有任何对象。希望这对您有所帮助!

答案 1 :(得分:-1)

您的e.EquipmentType.Namenull,并将其分配给EquipmentType,将null分配给nullable类型是非常好的,您正在使用DefaultIfEmpty()会在不符合任何条件的情况下使用默认值初始化元素,在这种情况下,您的e.ElementType.Name会被设置为null,这很好。使用将引发异常的ToList()

我希望我有道理,你们中的人们可能已经讨论过了。