我有两个班级
class Supervisor
{
public int SupervisorID { get; set; }
public string Name { get; set; }
public virtual List<Trunk> Trunks { get; set; }
//constructor + other code etc...//
}
class Trunk
{
public int TrunkID { get; set; }
public string Name { get; set; }
public int SupervisorID { get; set; }
//constructor + other code etc...//
}
这些类所连接的数据库中充满了数据,每个Supervisor记录至少有一个Trunk记录。
我的问题是: 为什么此代码正常工作并打印出每个主管的中继名称:
//Works
var s = from x in db.Supervisors
select x.Trunks.FirstOrDefault().Name;
foreach (var name in s)
Console.WriteLine(name);
但是这段代码不会抛出ArgumentNullException?
//Doesn't Work: ArgumentNullException
foreach (var supervisor in db.Supervisors)
Console.WriteLine(supervisor.Trunks.FirstOrDefault().Name);
此外,此代码运行良好:
foreach (var supervisor in db.Supervisors)
Console.WriteLine(supervisor.Name);
所以只有在访问supervisor时才会这样。我在第二个代码块中得到null。
Supervisors表的屏幕截图:http://imgur.com/90fKxB8
这两个代码块是不是做了完全相同的事情?
答案 0 :(得分:3)
以下代码:
var s = from x in db.Supervisors
select x.Trunks.FirstOrDefault().Name;
实际上并不是作为C#代码执行的。这是编译一系列Expression
对象,用于定义源代码的外观。它不会编译为可执行字节。然后将这些Expression
对象传递给查询提供程序,该查询提供程序将C#源代码(或至少等效的)转换为SQL代码,并针对数据库运行该代码。该查询提供程序可以看到您正在访问该对象的Trunks
属性,它知道将其转换为Join
。它看到您正在访问该表的Name
属性,因此它是选择的列等等。
撰写以下内容时:
foreach (var supervisor in db.Supervisors)
除了撤回整个Supervisors
表外,不会向数据库发送任何内容。它没有构建任何Expression
个对象来定义查询的内容。对Join
表没有Trunks
。主干名称未在选择器中拉出。
此相关表格未加载。如果您以后可能想要使用它,则不会使用所有supervisor
信息填充Trunks
对象。当对象有很多关系时,这太贵了。由于它根本没有填充,它将是null
,即使数据库中实际存在Trunk
个对象。
告诉查询提供者,“嘿,我将需要这些主管的Trunks表中的信息。”您使用Include
方法:
foreach (var supervisor in db.Supervisors.Include(s => s.Trunks))
当然,这仍然不如你的第一个解决方案,因为现在你从两个表中撤回所有字段,而不仅仅是中继名称。这是浪费了很多网络流量。
另一个选择是enable lazy initialization相关实体。这意味着当您尝试访问未在查询中填充的相关实体时,它会再次往返数据库,以便在您需要时获取该信息。在某些情况下可以没问题,但在这里你知道你将需要这些信息。为每个单独的主管执行额外的数据库往返是您真正想要避免的。因此,这将导致您的程序工作,而不是崩溃,代价是 比任何其他替代方案慢。
答案 1 :(得分:0)
你的第一个表达:
var s = from x in db.Supervisors
select x.Trunks.FirstOrDefault().Name;
会产生IEnumerable<string>
,其值为此查询的结果:
SELECT (
SELECT [t2].[Name]
FROM (
SELECT TOP (1) [t1].[Name]
FROM [Trunk] AS [t1]
WHERE [t1].[SupervisorId] = [t0].[SupervisorId]
) AS [t2]
) AS [value]
FROM [Supervisor] AS [t0]
如果有任何主管没有任何中继的实例,则会为该记录返回NULL值。
在第二个表达式中
foreach (var supervisor in db.Supervisors)
Console.WriteLine(supervisor.Trunks.FirstOrDefault().Name);
您正在循环访问客户端的实际数据结构。在这种情况下,如果有任何Supervisor没有任何Trunk,那么您正在尝试访问null的Name属性 - 这是抛出ArgumentNullException的地方。
我的猜测是你有没有任何中继线的主管。尝试将第一个查询更改为以下内容,看看是否返回任何记录:
var s = from x in db.Supervisors
select x.Trunks.FirstOrDefault().Name;
if(s.Any(name => name == null))
Console.WriteLine("Trunkless supervisors detected.");
答案 2 :(得分:0)
检查db.Configuration.LazyLoadingEnabled是否设置为false .. 这不会加载相关的实体,并会抛出错误