。在 EntityFramework 中包含() vs。 Load()性能

当查询一个较大的表时,您需要在稍后的代码中访问导航属性(我明确地不想使用延迟加载) ,什么将执行更好的 .Include().Load()?或者为什么要用这个而不是那个?

在本例中,所包含的表都只有大约10个条目,而雇员有大约200个条目,而且可能发生的情况是,其中大多数条目无论如何都将使用 include 加载,因为它们与 where 子句匹配。

Context.Measurements.Include(m => m.Product)
.Include(m => m.ProductVersion)
.Include(m => m.Line)
.Include(m => m.MeasureEmployee)
.Include(m => m.MeasurementType)
.Where(m => m.MeasurementTime >= DateTime.Now.AddDays(-1))
.ToList();

或者

Context.Products.Load();
Context.ProductVersions.Load();
Context.Lines.Load();
Context.Employees.Load();
Context.MeasurementType.Load();


Context.Measurements.Where(m => m.MeasurementTime >= DateTime.Now.AddDays(-1))
.ToList();
53971 次浏览

Include()将以 JOIN的形式写入 SQL: 一次数据库往返。

每个 Load()指令都“显式加载”所请求的实体,因此每次调用都有一次数据库往返。

因此,在这种情况下,Include()很可能是更明智的选择,但它取决于数据库布局、调用此代码的频率以及 DbContext的存活时间。为什么不试试这两种方法,分析查询并比较时间?

参见 加载相关实体

Include是急切加载的一个例子,当您不仅加载要查询的实体,而且加载所有相关实体时。

Load是对 EnableLazyLoading的手动重写。如果这个设置为 false。您仍然可以使用 .Load()延迟加载请求的实体

很难决定是用急切的,显性的,还是懒惰的载入。
无论如何,我建议总是执行一些侧写。这是确保您的请求能够执行的唯一方法。
有很多工具可以帮助你。看一下 这篇文章来自 Julie Lerman,她列出了几种不同的做分析的方法。一个简单的解决方案是启动 在 SQLServerManagementStudio 中进行分析
不要犹豫与 DBA 交谈(如果你身边有的话) ,这将有助于你理解执行计划。
您还可以查看 这个展示,我在其中编写了一个关于加载数据和性能的部分。

看情况,两个都试试

当使用 Include()时,您将获得将所有数据加载到底层数据存储的单个调用的 利益。例如,如果这是一个远程 SQLServer,那么性能将大大提高。

负面影响Include()查询倾向于获得 真的 很复杂,特别是如果您有任何过滤器(例如 Where()调用)或尝试执行任何分组。EF 将使用子 SELECTAPPLY语句生成非常多的嵌套查询,以获得所需的数据。它的效率也低得多——返回一行数据,其中包含每个可能的子对象列,因此顶级对象的数据将被重复很多次。(例如,一个有10个子对象的单个父对象将产生10行,每行的父对象列的数据相同。)我有一个单一的 EF 查询变得如此复杂,以至于造成了死锁当运行在同一时间作为 EF 更新逻辑。

Load()方法更多的是 更简单。每个查询都是针对单个表的单个、简单、直接的 SELECT语句。这些在每一个可能的方式更容易,除了你必须做很多(可能许多倍以上)。如果您有嵌套的集合集合,您甚至可能需要遍历顶级对象和 Load它们的子对象。会失控的。

简单的经验法则

尝试在单个查询中使 避免具有任何 超过三通 Include电话。我发现 EF 的查询过于丑陋以至于无法识别更多; 它也符合我对 SQL Server 查询的经验法则,即一个查询中多达四个 JOIN 语句工作得非常好,但之后就是 考虑重构了。

然而,所有这些只是一个起点。

它取决于您的模式、环境、数据 和许多其他因素。

最后,你只需要 每种方法都试一试

选择一个合理的“默认”模式来使用,看看它是否足够好,如果不够好,优化以满足口味。

我同意@MichaelEdenfield 在他的 回答中的观点,但是我确实想对嵌套收集场景进行评论。通过将查询反过来,您可以避免必须执行嵌套循环(以及对数据库的许多结果调用)。

例如,您可以使用下面这样的过滤器直接查询 OrderItems,而不是循环通过 Customer’s Orders 集合,然后通过 Order’s OrderItems 集合执行另一个嵌套循环。

context.OrderItems.Where(x => x.Order.CustomerId == customerId);

您将获得与嵌套循环中的 Loads 相同的结果数据,但只需对数据库进行一次调用。

此外,还有一个特殊的情况,应该考虑与包括。如果父数据和子数据之间的关系是一对一的,那么多次返回父数据的问题就不成问题了。

我不确定如果大多数情况下没有孩子存在的话会有什么影响——大量的空数?一对一关系中的稀疏子元素可能更适合于我上面概述的直接查询技术。

还有一件事要补充。这取决于您使用的服务器。如果您正在使用 sql 服务器,那么可以使用即时加载,但是对于 sqlite,则必须使用。Load ()以避免交叉加载异常,因为 sqlite 无法处理一些包含语句,这些语句的深度超过一个依赖级别

更新答案: 从 EF Core 5.0开始,你可以使用 AsSplitQuery()

这特别有用,我个人在有许多连接时一直在使用它 这可能会导致笛卡尔爆炸,或者只是需要更多的时间来完成。

顾名思义,EF 将对每个实体执行单独的查询,而不是使用连接。

因此,在使用显式加载的地方,现在可以使用带拆分查询的 Eager 加载来实现相同的结果,而且它肯定更具可读性。

参见 https://learn.microsoft.com/en-us/ef/core/querying/single-split-queries