我一直在尝试熟悉实体框架。大部分看起来很简单,但我对使用 Include 方法的急切加载和默认的延迟加载之间的区别有点困惑。两者似乎都加载了相关的实体,所以从表面上看,它们似乎在做同样的事情。我错过了什么?
4 回答
假设您有两个具有一对多关系的实体:客户和订单,其中每个客户可以有多个订单。
加载客户实体时,实体框架允许您预先加载或延迟加载客户的订单集合。如果您选择预先加载 Orders 集合,当您从数据库中检索客户时,实体框架将生成 SQL,该 SQL 会在一个查询中检索客户的信息和客户的订单。但是,如果您选择延迟加载 Orders 集合,当您从数据库中检索客户时,实体框架将生成仅提取客户信息的 SQL(如果您稍后访问客户的 Orders 集合,实体框架将生成单独的 SQL 语句在您的代码中)。
确定何时使用急切加载以及何时使用延迟加载都归结为您希望对检索到的实体执行什么操作。如果您知道您只需要客户的信息,那么您应该延迟加载 Orders 集合(这样 SQL 查询可以通过仅检索客户的信息来高效)。相反,如果您知道需要遍历客户的订单,那么您应该预先加载订单(这样一旦您在代码中访问客户的订单,就可以节省额外的数据库命中)。
PS 使用延迟加载时要非常小心,因为它会导致 N+1 问题。例如,假设您有一个显示客户列表及其订单的页面。但是,您决定在获取订单时使用延迟加载。当您遍历 Customers 集合,然后遍历每个 Customer 的 Orders 时,您将为每个 Customer 执行数据库命中以延迟加载到他们的 Orders 集合中。这意味着对于 N 个客户,您将有 N+1 次数据库命中(1 次数据库命中以加载所有客户,然后 N 次数据库命中以加载他们的每个订单),而不是使用即时加载时只有 1 次数据库命中(这将在一个查询中检索所有客户及其订单)。
如果您来自 SQL 世界,请考虑 JOIN。
如果您必须在网格中显示 10 个订单和下订单的客户,您有 2 个选择:
1) 延迟加载(= 11 次查询 = 性能缓慢)
EF 将拍摄一个查询以检索订单,并为每个订单进行一次查询以检索客户数据。
Select * from order where order=1
+
10 x (Select * from customer where id = (order.customerId))
1)渴望负载(= 1个查询=高性能)
EF 将使用 JOIN 进行一次查询以检索订单和客户。
Select * from orders INNER JOIN customers on orders.customerId=customer.Id where order=1
PS: 当您从数据库中检索对象时,该对象在上下文处于活动状态时存储在缓存中。在我使用 LAZY LOAD 制作的示例中,如果所有 10 个订单都与同一客户相关,您将只看到 2 个查询,因为当您要求 EF 检索对象时,EF 将检查该对象是否在缓存中以及它是否在缓存中发现它不会触发另一个对数据库的 SQL 查询。
Eager loading 旨在解决ORM 特有的N+1 Selects问题。简短的版本是这样的:如果您要直接检索一些实体并且您知道您将通过检索到的实体访问某些相关实体,那么一次性检索所有相关实体会更有效,与通过延迟加载逐步检索它们相比。
一个重要的问题是序列化。如果您正在处理序列化对象,Microsoft 建议不要使用默认的延迟加载。序列化导致所有相关属性被调用,这可以启动被查询的相关实体的连锁反应。如果您从控制器返回 JSON 数据,这真的会发挥作用。JSON数据显然是序列化的。您要么希望通过 Eager 立即返回数据,要么在上下文中关闭延迟加载并使用显式延迟加载。