linq select语句与直接访问

时间:2014-05-06 16:29:50

标签: 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; 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

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

3 个答案:

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