为什么我没有收到空引用异常?
Why am I NOT getting a Null Reference Exception?
我正在使用 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
是我传入的用于过滤结果的整数列表。当我得到结果时,我看到其中一个 return 记录有一个空 EquipmentClass
。我执行了一些空值检查,但意识到我忘记了对其中一个属性进行空值检查。现在我希望在 EquipmentType = e.EquipmentType.Name
上得到空引用异常,但我没有。令我惊讶的是,它工作得很好,并且设置为 null。我的 EquipmentClass
有一个 属性 类型的 EquipmentType
,这是另一个 class。 EquipmentType
有一个 Name
属性 是一个字符串。
- 为什么这不抛出空引用异常?
作为测试,我从 InServiceStatus = e == null ? false : e.InServiceStatus
中删除了空检查,但在 运行 使用查询的 foreach 循环时它失败并出现无效操作异常。
- 这是否意味着我只需要对不可为 null 的值进行 null 检查?
更新:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
在查询后立即添加。在 p 的赋值上,我得到一个空引用异常。我不确定它是如何走到这一步的,因为它应该在 foreach 循环的第一行失败。如果没有声明变量 p 的行,我不会得到空引用异常。如果有人能解释发生了什么,我将不胜感激。仅供参考,item.EquipmentClass
和 item.EquipmentType
的值在 foreach 循环开始时都为 null。
更新2:
我发现 this link 似乎有人在使用 LINQ to SQL 时遇到了几乎相同的问题。我明白了答案的要点,但没有完全理解它对我上面两个问题的潜在影响。
你的更新帮助我理解了你真正关心的问题。关于 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跳过代码语句。此时您将在探查器会话中完全看不到任何查询执行,如下所示:
那是因为 LINQ 查询直到此时才被执行。
现在您到达下面的代码行:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
当您进入 foreach 循环时,LINQ 查询被触发,您将在 SQL 服务器分析器中看到相应的登录跟踪会话。因此,除非枚举 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
对象的 record/object 时中断,该对象具有 id
2 存在于 mylist1
中。 mylist2
与 id
没有任何对象 2. 希望这对您有所帮助!
如果您针对 IQueryable
编写 LINQ 查询,会发生的情况是您在幕后调用的方法(例如 Select
、Where
等)不除了记录您如何调用它们之外,还可以做任何其他事情,即它们记录谓词表达式并继承 LINQ 提供程序。一旦开始迭代查询,提供商就会被要求执行查询模型。所以基本上,提供者使用表达式模型为您提供预期类型的结果。
提供者绝对不需要实际编译甚至执行您作为表达式交付的代码(模型)。 事实上,LINQ 的全部意义在于 SQL 或 LINQ to Entities 是提供者不这样做,而是将代码表达式转换为 SQL。
因此,您的查询实际上呈现为 SQL 查询,并且该查询的结果被翻译回来。因此,您在查询中看到的变量 e
不一定是真正创建的,而只是用于 LINQ 提供程序编译 SQL 查询。但是,大多数数据库服务器都有空传播。
运行 针对 LINQ to Objects 的相同查询,您将得到丢失的 NullReferenceException.
你的 e.EquipmentType.Name
是 null
并且它被分配给 EquipmentType
并且将 null
分配给 nullable
类型是完全没问题的,你正在使用 DefaultIfEmpty()
如果无法匹配任何条件,它将使用默认值初始化元素,在这种情况下,您的 e.ElementType.Name
将设置为 null
我猜这很好。使用 ToList()
会抛出异常。
我希望我说得有道理,你们可能已经讨论过了。
我正在使用 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
是我传入的用于过滤结果的整数列表。当我得到结果时,我看到其中一个 return 记录有一个空 EquipmentClass
。我执行了一些空值检查,但意识到我忘记了对其中一个属性进行空值检查。现在我希望在 EquipmentType = e.EquipmentType.Name
上得到空引用异常,但我没有。令我惊讶的是,它工作得很好,并且设置为 null。我的 EquipmentClass
有一个 属性 类型的 EquipmentType
,这是另一个 class。 EquipmentType
有一个 Name
属性 是一个字符串。
- 为什么这不抛出空引用异常?
作为测试,我从 InServiceStatus = e == null ? false : e.InServiceStatus
中删除了空检查,但在 运行 使用查询的 foreach 循环时它失败并出现无效操作异常。
- 这是否意味着我只需要对不可为 null 的值进行 null 检查?
更新:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
在查询后立即添加。在 p 的赋值上,我得到一个空引用异常。我不确定它是如何走到这一步的,因为它应该在 foreach 循环的第一行失败。如果没有声明变量 p 的行,我不会得到空引用异常。如果有人能解释发生了什么,我将不胜感激。仅供参考,item.EquipmentClass
和 item.EquipmentType
的值在 foreach 循环开始时都为 null。
更新2: 我发现 this link 似乎有人在使用 LINQ to SQL 时遇到了几乎相同的问题。我明白了答案的要点,但没有完全理解它对我上面两个问题的潜在影响。
你的更新帮助我理解了你真正关心的问题。关于 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跳过代码语句。此时您将在探查器会话中完全看不到任何查询执行,如下所示:
那是因为 LINQ 查询直到此时才被执行。
现在您到达下面的代码行:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
当您进入 foreach 循环时,LINQ 查询被触发,您将在 SQL 服务器分析器中看到相应的登录跟踪会话。因此,除非枚举 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
对象的 record/object 时中断,该对象具有 id
2 存在于 mylist1
中。 mylist2
与 id
没有任何对象 2. 希望这对您有所帮助!
如果您针对 IQueryable
编写 LINQ 查询,会发生的情况是您在幕后调用的方法(例如 Select
、Where
等)不除了记录您如何调用它们之外,还可以做任何其他事情,即它们记录谓词表达式并继承 LINQ 提供程序。一旦开始迭代查询,提供商就会被要求执行查询模型。所以基本上,提供者使用表达式模型为您提供预期类型的结果。
提供者绝对不需要实际编译甚至执行您作为表达式交付的代码(模型)。 事实上,LINQ 的全部意义在于 SQL 或 LINQ to Entities 是提供者不这样做,而是将代码表达式转换为 SQL。
因此,您的查询实际上呈现为 SQL 查询,并且该查询的结果被翻译回来。因此,您在查询中看到的变量 e
不一定是真正创建的,而只是用于 LINQ 提供程序编译 SQL 查询。但是,大多数数据库服务器都有空传播。
运行 针对 LINQ to Objects 的相同查询,您将得到丢失的 NullReferenceException.
你的 e.EquipmentType.Name
是 null
并且它被分配给 EquipmentType
并且将 null
分配给 nullable
类型是完全没问题的,你正在使用 DefaultIfEmpty()
如果无法匹配任何条件,它将使用默认值初始化元素,在这种情况下,您的 e.ElementType.Name
将设置为 null
我猜这很好。使用 ToList()
会抛出异常。
我希望我说得有道理,你们可能已经讨论过了。