我无法理解延迟加载的工作原理。例如,在下面的示例中,我可以在 Where()
子句中访问 Students
的 Courses
:
context.Students
.Where(st=>st.Courses
.Select(c=>c.CourseName).Contains('Math')
).ToList();
但是,尽管我没有禁用延迟加载,但如果我不使用 Include()
,下面的代码将不起作用并且会抛出 null
异常:
context.Students.Single(s => s.StudentId == 1)
.Courses.ToList()
谁能解释一下为什么会这样?
最佳答案
为了延迟加载工作,必须发生两件事:
- 必须在上下文中启用延迟加载
- 延迟加载的属性必须是虚拟的。
在你的例子中,你已经解释过你的属性不是虚拟的,所以它不能延迟加载。但是,仅当您希望在从数据库加载基础对象后访问子实体或集合时才需要延迟加载。这意味着您在示例中做了两件截然不同的事情。
在您的第一个示例中,您编写了一个 EF 查询,其中包括对 IQueryable.Where
的调用,然后是 IQueryable.ToList
。当此代码运行时,EF 将尝试将 Where
调用转换为对基础 SQL 数据存储的调用。 在 该调用中,您访问被查询对象的子实体引用,因此 EF 表达式解析器看到此引用并知道将其也转换为 SQL。本质上,您只要求 EF 进行一次数据库调用,因此它可以正常工作。
(一个警告:即使您的第一个查询有效,当对 ToList
的调用完成时,子集合也不会填充;SQL 查询仍然只返回所需的字段填充顶级对象。所发生的一切是 EF 在 WHERE
子句中包含子表以过滤结果集。如果您尝试访问 Courses
属性任何返回的 Student
对象,它仍然会失败。)
在您的第二个示例中,您正在调用 IQueryable.Single
以获取单个学生,然后调用 Courses
属性 getter ,然后调用调用 IQueryable.ToList
。同样,EF 表达式解析器会查看 Single
方法调用中的任何内容,并将其转换为 SQL 查询,但您访问子集合的尝试发生在该调用外部。在这里,您要求 EF 执行两个“查询”:一个是获取学生,另一个是获取类(class)。由于未启用延迟加载,因此第二个查询永远不会运行,并且 EF 会立即返回 null
。这会导致尝试在 null
对象上调用 ToList
,这会产生预期的错误。
如果您在第二个查询中使用了 Include
,EF 将被迫生成一个不同的 SQL 查询来满足您对 Single
的调用,该查询包含所有填充 Courses
子集合所需的信息。在这种情况下,当您在下一步中尝试访问 Courses
时,它不会是 null
,它已经被填充,并且 ToList
调用会起作用。
要真正理解差异,最简单的方法就是查看每种情况下生成的 SQL 查询;有多种方法可以做到这一点,这里描述了一种简单的方法:
关于c# - Entity Framework 中导航属性延迟加载的逻辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29337327/