我有两个类
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.Trunks时,我才会在第二个代码块中得到null。
主管表的屏幕截图:/image/H8Ha6.jpg
这两个代码块做的事情不完全相同吗?
最佳答案
以下代码:
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
对象来定义查询可能是什么。没有针对 Trunks
表的 Join
。中继名称不会在选择器中提取。
此相关表未立即加载。如果您稍后可能想使用它,它不会使用所有 Trunks
信息填充 supervisor
对象。当对象有很多关系时,这样做的成本太高了。由于它根本没有填充,因此即使数据库中实际上存在 Trunk
对象,它也将为 null
。
告诉查询提供者,“嘿,我需要这些主管的 Trunks 表中的信息。”您使用 Inlude
方法:
foreach (var supervisor in db.Supervisors.Include(s => s.Trunks))
当然,这仍然不如您的第一个解决方案,因为现在您要从两个表中提取所有字段,而不仅仅是中继名称。这会浪费大量的网络流量。
还有一个选择是enable lazy initialization的相关实体。这意味着,当您尝试访问查询中未填充的相关实体时,它会再次往返数据库以在您需要时获取该信息。在某些情况下这可能没问题,但在这里您知道您将需要该信息。您真正想要避免的是为每个主管执行额外的数据库往返。因此,这将导致您的程序正常运行,而不是崩溃,但代价是比任何其他替代方案慢得多。
关于c# - linq select 语句与直接访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23500121/