entity framework 中导航属性延迟加载的逻辑
the logic of lazy loading with navigation properties in entity framework
我无法理解延迟加载的工作原理。例如,在下面的示例中,我可以在 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 查询仍然只有 returns 填充顶级对象所需的字段。所发生的只是 EF 在 WHERE
子句中包含子 table 以过滤结果集。如果您尝试访问Courses
属性 在任何返回的 Student
对象上,它仍然会失败。)
在您的第二个示例中,您正在调用 IQueryable.Single
以获取单个学生,然后调用 Courses
属性 getter,随后调用 IQueryable.ToList
。同样,EF 表达式解析器会查看 Single
方法调用中的任何内容,并将其转换为 SQL 查询,但您尝试访问子集合发生在 外部 称呼。在这里,您要求 EF 执行两项 "queries":一项是获取学生,一项是获取课程。由于未启用延迟加载,因此第二个查询永远不会运行,并且 EF 会立即 returns null
。这会导致尝试在 null
对象上调用 ToList
,这会产生预期的错误。
如果您在第二个查询中使用了 Include
,EF 将被迫生成一个不同的 SQL 查询来满足您对 Single
的调用,该查询包含所有还需要填充 Courses
子集合的信息。在这种情况下,当您在下一步中尝试访问 Courses
时,它不会是 null
,它已经被填充,并且 ToList
调用将起作用。
要真正理解差异,最简单的方法就是查看每种情况下生成的 SQL 查询;有多种方法可以做到这一点,这里描述了一种简单的方法:
How do I view the SQL generated by the Entity Framework?
我无法理解延迟加载的工作原理。例如,在下面的示例中,我可以在 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 查询仍然只有 returns 填充顶级对象所需的字段。所发生的只是 EF 在 WHERE
子句中包含子 table 以过滤结果集。如果您尝试访问Courses
属性 在任何返回的 Student
对象上,它仍然会失败。)
在您的第二个示例中,您正在调用 IQueryable.Single
以获取单个学生,然后调用 Courses
属性 getter,随后调用 IQueryable.ToList
。同样,EF 表达式解析器会查看 Single
方法调用中的任何内容,并将其转换为 SQL 查询,但您尝试访问子集合发生在 外部 称呼。在这里,您要求 EF 执行两项 "queries":一项是获取学生,一项是获取课程。由于未启用延迟加载,因此第二个查询永远不会运行,并且 EF 会立即 returns null
。这会导致尝试在 null
对象上调用 ToList
,这会产生预期的错误。
如果您在第二个查询中使用了 Include
,EF 将被迫生成一个不同的 SQL 查询来满足您对 Single
的调用,该查询包含所有还需要填充 Courses
子集合的信息。在这种情况下,当您在下一步中尝试访问 Courses
时,它不会是 null
,它已经被填充,并且 ToList
调用将起作用。
要真正理解差异,最简单的方法就是查看每种情况下生成的 SQL 查询;有多种方法可以做到这一点,这里描述了一种简单的方法:
How do I view the SQL generated by the Entity Framework?