C# linq select语句与直接访问

C# linq select语句与直接访问,c#,linq,entity-framework,C#,Linq,Entity Framework,我有两节课 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;

我有两节课

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...//
}
但是这段代码没有并抛出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

Supervisors表的屏幕截图:

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

您的第一个表达式:

var s = from x in db.Supervisors
        select x.Trunks.FirstOrDefault().Name;
结果为IEnumerable,其值为此查询的结果:

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]
如果存在没有任何中继的任何监控器实例,则会为该记录返回空值

在第二个表达式中

foreach (var supervisor in db.Supervisors)
    Console.WriteLine(supervisor.Trunks.FirstOrDefault().Name);
您正在客户端的实际数据结构中循环。在本例中,如果有任何主管没有任何中继,那么您正试图访问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.");
以下代码:

var s = from x in db.Supervisors
    select x.Trunks.FirstOrDefault().Name;
实际上不是作为C代码执行的。这是编译一系列表达式对象,这些对象定义了它的源代码。它没有编译成可执行字节。然后将这些表达式对象传递给查询提供程序,查询提供程序将C源代码或至少等效代码转换为SQL代码,并针对数据库运行该代码。该查询提供程序可以看到您正在访问对象的Trunks属性,它知道如何将其转换为连接。它看到您正在访问该表的Name属性,因此它选择了该列,以此类推

当您编写以下内容时:

foreach (var supervisor in db.Supervisors)
除了回拉整个Supervisors表之外,不会向数据库发送任何内容。它没有建立任何表达式对象来定义查询可能是什么。Trunks表上没有连接。未在选择器中拉出中继名称

这个相关的表没有被急切地加载。它不会用所有中继信息填充supervisor对象,以防您以后可能需要使用它。当对象有很多关系时,这样做太昂贵了。因为它根本没有填充,所以它将为null,即使数据库中实际上有主干对象

要告诉查询提供者,嘿,我需要从Trunks表中获取这些主管的信息。您可以使用Include方法:

当然,这仍然不如第一个解决方案,因为现在要从两个表中提取所有字段,而不仅仅是主干名称。这浪费了大量的网络流量


另一种选择是对相关实体进行分类。这意味着,当您试图访问查询中未填充的相关实体时,它会再次往返数据库,以便在需要时获取该信息。在某些情况下,这很好,但在这里,你知道你需要这些信息。为每个主管执行额外的数据库往返是您真正希望避免的事情。因此,这将导致您的程序工作,而不是崩溃,代价是比任何其他选择慢得多。

检查db.Configuration.LazyLoadingEnabled是否设置为false。。
这将不会加载相关实体,并将抛出一个错误

您将收到该错误,因为FirstOrDefault可以返回null,您可以得到一个空值的属性。@ErikPhilips:问题不在于为什么会发生错误,而在于为什么会发生在第二个代码上,而不是第一个代码上。第一个代码也有FirstOrDefault。线索在FirstOrDefault中-如果Supervisor.Trunks为空,则返回空值。LINQ没有为该主管选择任何内容,因此s中没有空值。简单的foreach并没有这样做。@Chris知道错误存在的原因可以帮助未来的用户理解为什么会发生这种情况。我试图引导OP找到答案。@NeilHewitt依赖于提供程序-对象上的LINQ在这两种情况下都会产生异常。我运行了您的代码,但writeline从未运行过。没有空名称。我在原始问题中添加了Supervisors表的屏幕截图。您可以在这里找到接近答案的地方。这与第一个代码段被翻译成SQL代码而第二个代码段没有相关,但这并不意味着某些主管没有主干。正如我的回答所解释的,问题在于,因为第二个查询没有转换为SQL,所以即使数据在数据库中,也根本不会获取信息。同样值得注意的是,如果没有主管的中继,但该属性实际上是从数据库填充的,那么它将表示一个空集合,不是null集合,FirstOrDefault不会失败,它只会返回null,并且在尝试获取时会得到一个NRE
null的名称。正如我的回答详细解释的,这与正在发生的事情有关。未延迟加载相关实体。当然,在这种情况下,他真的不应该懒洋洋地加载实体。他不想为每个主管执行额外的DB往返。他应该在初始查询中提取相关信息,这非常有帮助。非常感谢你的帮助!
foreach (var supervisor in db.Supervisors)
foreach (var supervisor in db.Supervisors.Include(s => s.Trunks))