EF: 延迟加载、急切加载和“枚举可枚举项”

19

我发现我对懒加载等概念感到困惑。

首先,这两个语句是否等价:

(1) Lazy loading:
_flaggedDates = context.FlaggedDates.Include("scheduledSchools")
.Include  ("interviews").Include("partialDayAvailableBlocks")
.Include("visit").Include("events");

(2) Eager loading:
_flaggedDates = context.FlaggedDates;
换句话说,在 (1) 中,“Includes”会导致导航集合/属性与请求的特定集合一起加载,而不管您是否使用懒加载...是吗?
而在 (2) 中,该语句将加载所有导航实体,即使您没有明确请求它们,因为您正在使用急切加载...是吗?
第二点:即使您使用急切加载,数据直到“枚举可枚举项”(例如以下代码)才会实际从数据库中下载:
var dates = from d in _flaggedDates
            where d.dateID = 2
            select d;
foreach (FlaggedDate date in dates)
{
... etc.
}

仅仅定义了查询(如 "var dates" )并不会真正地下载("枚举")数据,只有在 foreach 循环中才会执行。换句话说,查询的执行要等到 foreach 循环开始。

既然是这样(如果我的假设正确),那么急切加载和延迟加载之间的真正区别是什么呢?在任何一种情况下,数据都要等到枚举时才会出现,我有什么遗漏吗?

(顺便说一句,我的具体经验是使用代码优先、POCO开发,尽管这些问题可能更通用。)


EntityFramework使用急切/延迟加载(一种通用的软件术语,用于加载数据)来获取相关对象和集合。延迟查询执行是LINQ的一个产物,它允许查询更加灵活(在某些情况下可能根本不执行)。 - jwize
2个回答

17

你对(1)的描述是正确的,但这是一种急切加载的例子,而不是延迟加载。

你对(2)的描述是不正确的。 (2)在技术上根本没有使用加载,但如果你尝试访问FlaggedDates中的任何非标量值,它将使用延迟加载。

无论哪种情况,你都正确地指出,在尝试“做某事”之前,不会从数据存储中加载任何数据。 但是,每种情况下发生的情况都不同。

(1):急切加载:当你开始你的for 循环时,你指定的每一个对象都将从数据库中提取并构建成一个巨大的内存数据结构。 这将是一个非常昂贵的操作,从你的数据库中提取大量数据。然而,它将在一个数据库往返中完成,使用单个SQL查询执行。

(2):延迟加载:当你的for 循环开始时,它只会加载FlaggedDates对象。 但是,如果你在for 循环内部访问相关的对象,它还没有把那些对象加载到内存中。第一次尝试检索给定FlaggedDate的scheduledSchools将导致要么进行新的数据库往返以检索学校,要么抛出异常,因为已经关闭了你的上下文。由于你会在for 循环内部访问scheduledSchools集合,所以你将为最初在for 循环开始时加载的每个FlaggedDate进行新的数据库往返。

对评论的回应

禁用延迟加载不等于启用急切加载。在这个例子中:

context.ContextOptions.LazyLoadingEnabled = false;
var schools = context.FlaggedDates.First().scheduledSchools;

schools变量将包含一个空的EntityCollection,因为在原始查询(FlaggedDates.First())中我没有Include它们,并且我禁用了延迟加载,以便在执行初始查询后它们不能被加载。

您说得对,where d.dateID == 2意味着只会拉取与该特定FlaggedDate对象相关的对象。但是,根据与该FlaggedDate相关联的对象数量,您仍然可能会获得大量数据。这是由于EntityFramework构建其SQL查询的方式所致。SQL查询结果总是以表格式呈现,这意味着每行必须具有相同的列数。对于每个scheduledSchool对象,结果集中都至少需要有一行,在每行都必须至少包含每列的某些值的情况下,您最终会得到FlaggedDate对象上每个标量值都重复的每一行。因此,如果您有10个与FlaggedDate关联的scheduledSchools和10个interviews,您最终会得到20行,每行都包含FlaggedDate上的每个标量值。其中一半的行将对所有ScheduledSchool列具有null值,另一半的行将对所有Interviews列具有null值。

然而,当您在所包含的数据中进行“深入”时,情况就变得非常糟糕了。例如,如果每个ScheduledSchool都有一个students属性,并且您也将其包含在内,则突然间,每个ScheduledSchool中的每个学生都会有一行,每行都会包含其ScheduledSchool的每个标量值(尽管只有第一行的值最终被使用),以及原始FlaggedDate对象上的每个标量值。它会迅速地增加。

很难用文字解释,但如果您查看具有多个Include的查询返回的实际数据,您将看到有大量重复数据。您可以使用LinqPad查看由EF代码生成的SQL查询。


对于 (2) (MY (2)) ... 如果禁用了延迟加载(也就是启用了急切加载),你的意思是导航属性不会随着 FlaggedDates 一起加载?那么什么是急切加载呢? - Cynthia
对您的编辑做出回应:好的,我明白您的意思。但是对于您的第1点:所有对象都将被拉取,但只有查询中指定的对象,对吗?例如,在我的示例中,我说where d.dateID == 2,因此只有dateID = 2 的FlaggedDate对象的数据会被拉取。对吗?因此,只要在查询中限制了范围,就不会那么昂贵。 - Cynthia
好的,我觉得我明白了。这是对的吗:懒加载 = true:在枚举时加载导航对象(通过生成额外的SQL语句);懒加载 = false:你必须在原始查询上使用INCLUDES或在枚举时使用LOAD来加载导航对象。 - Cynthia
我理解的是,“eager loading”只是一个词,用于描述在枚举时使用INCLUDES加载导航对象的过程。 - Cynthia
你的最后一条评论是正确的。因此,你可以使用“eager loading”和启用“lazy loading”,通过使用“Include”。如果一个属性没有包含在原始查询中,而你尝试使用它,那么懒加载将会启动来加载它,除非你已经禁用了懒加载,在这种情况下,它将愉快地告诉你该属性为空,而不做任何加载值的尝试。 - StriplingWarrior
显示剩余2条评论

0

没有区别。这在EF 1.0中是不正确的,因为它不支持急切加载(至少不是自动的)。在1.0中,您必须修改属性以自动加载,或者调用属性引用上的Load()方法。

需要记住的一件事是,如果您跨多个对象进行查询,则这些包含可能会失效,例如:

from d in ctx.ObjectDates.Include("MyObjectProperty")
from da in d.Days

ObjectDate.MyObjectProperty 不会被自动加载。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接