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

这两个代码块不是在做完全相同的事情吗?

linqselect语句与直接访问

以下代码:

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。。这将不会加载相关的实体,并将抛出错误