实体框架中具有导航属性的延迟加载的逻辑

ren*_*kre 5 c# entity-framework lazy-loading eager-loading

我在理解延迟加载的工作方式时遇到了麻烦。例如,在下面的例子中,我可以访问CoursesStudentsWhere()子句:

context.Students
       .Where(st=>st.Courses
                    .Select(c=>c.CourseName).Contains('Math')
             ).ToList();
Run Code Online (Sandbox Code Playgroud)

但是,尽管我没有禁用延迟加载,但null如果不使用以下内容,则将引发异常Include()

context.Students.Single(s => s.StudentId == 1)
       .Courses.ToList()
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下为什么会这样吗?

Mic*_*eld 5

为了使延迟加载工作,必须发生两件事:

  1. 必须在上下文中启用延迟加载
  2. 要延迟加载的属性必须是虚拟的。

对于您的情况,您已经说明了您的属性不是虚拟的,因此不能延迟加载。但是,仅当您要在从数据库加载基础对象之后访问子实体或集合时才需要延迟加载。这意味着您在示例中所做的两件事非常不同。

在第一个示例中,您编写了一个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进行两项“查询”:一项是让学生获得,另一项是获得课程。由于未启用延迟加载,因此第二个查询将永远不会运行,而EF会null立即返回。这导致试图调用ToList一个上null对象,它给你预期的错误。

如果您Include在第二个查询中使用过,则EF将不得不生成另一个SQL查询来满足您对的调用Single,该查询包含了也填充子集合所需的所有信息Courses。在这种情况下,当您尝试Courses在下一步中访问时,它不会是null,它已经被填充,并且该ToList调用将起作用。

要真正理解它们之间的区别,最简单的方法就是查看每种情况下生成的SQL查询。有很多方法可以做到这一点,这里描述一个简单的方法:

如何查看由实体框架生成的SQL?