linqselect语句与直接访问
本文关键字:访问 语句 linqselect | 更新日期: 2023-09-27 18:02:04
我有两个类
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表的屏幕截图:https://i.stack.imgur.com/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
对象,以备以后使用。当对象有很多关系时,这样做太昂贵了。因为它根本没有被填充,所以它将是null
,即使数据库中实际上有Trunk
对象。
要告诉查询提供程序,"嘿,我需要这些主管的Trunks表中的信息。"可以使用Include
方法:
foreach (var supervisor in db.Supervisors.Include(s => s.Trunks))
当然,这仍然不如您的第一个解决方案,因为现在您要从两个表中撤回所有字段,而不仅仅是主干名称。这是大量浪费的网络流量。
还有一种选择是启用相关实体的惰性初始化。这意味着,当你试图访问一个查询中没有填充的相关实体时,它会再次往返数据库,在你需要的时候获取这些信息。在某些情况下,这可能很好,但在这里你知道你需要这些信息。为每一位主管执行额外的数据库往返是您真正希望避免的事情。因此,这将导致你的程序工作,而不是崩溃,代价是比任何其他选择都慢。
您的第一个表达式:
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]
如果有任何Supervisor实例没有任何Trunk,则会为该记录返回NULL值。
在第二个表达式中
foreach (var supervisor in db.Supervisors)
Console.WriteLine(supervisor.Trunks.FirstOrDefault().Name);
您正在遍历客户端上的实际数据结构。在这种情况下,如果有任何Supervisor没有任何Trunk,那么您正试图访问null的Name属性,也就是抛出ArgumentNullException的地方。
我的猜测是,你确实有没有Trunk的主管。尝试将第一个查询更改为以下查询,看看是否返回任何记录:
var s = from x in db.Supervisors
select x.Trunks.FirstOrDefault().Name;
if(s.Any(name => name == null))
Console.WriteLine("Trunkless supervisors detected.");
检查您是否有数据库。Configuration.LazyLoadingEnabled设置为false。。这将不会加载相关的实体,并将抛出错误