.包括() vs .Load() 性能在 EntityFramework 中

本文关键字:EntityFramework Load vs 包括 性能 | 更新日期: 2023-09-27 18:35:02

当查询一个大表时,您需要稍后在代码中访问导航属性(我明确不想使用延迟加载(,什么会.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();

.包括() vs .Load() 性能在 EntityFramework 中

视情况而定,同时尝试两者

使用 Include() 时,您可以在对基础数据存储的单个调用中加载所有数据。例如,如果这是远程 SQL Server,则可以显著提高性能。

缺点Include()查询往往变得非常复杂,特别是如果您有任何过滤器(例如Where()调用(或尝试进行任何分组。EF 将使用子SELECTAPPLY语句生成非常密集的嵌套查询,以获取所需的数据。它的效率也低得多 - 你得到一行数据,其中包含每个可能的子对象列,所以你的顶级对象的数据将重复很多次。(例如,具有 10 个子对象的单个父对象将产生 10 行,每行具有父对象列的相同数据。我遇到过单个 EF 查询变得如此复杂,以至于在与 EF 更新逻辑同时运行时导致死锁。

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

快速经验法则

尽量避免在单个查询中具有三个以上的Include调用。我发现 EF 的查询变得太丑陋,无法识别超出此范围;它还符合我对 SQL Server 查询的经验法则,即单个查询中最多四个 JOIN 语句运行良好,但之后是时候考虑重构了。

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

这取决于您的架构、环境、数据和许多其他因素。

最后,您只需要尝试每种方式即可。

选择一个合理的"默认"模式来使用,看看它是否足够好,如果没有,则优化口味。

Include()将按JOIN写入 SQL:一个数据库往返。

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

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

请参阅加载相关实体。

我同意@MichaelEdenfield的回答,但我确实想评论嵌套集合方案。您可以通过将查询从内到外来避免必须执行嵌套循环(以及对数据库的许多结果调用(。

例如,您可以使用如下所示的筛选器直接查询 OrderItems,而不是向下循环遍历客户的订单集合,然后通过订单的 OrderItems 集合执行另一个嵌套循环。

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

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

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

我不确定如果大多数情况是没有孩子存在的地方会产生什么影响 - 很多空值?一对一关系中的稀疏子项可能更适合我上面概述的直接查询技术。

>Include是预先加载的一个示例,您不仅加载要查询的实体,还加载所有相关实体。

Load是手动覆盖EnableLazyLoading。如果此设置为 false .您仍然可以延迟加载您请求的实体.Load()

总是很难决定是否使用急切、显式甚至延迟加载。
无论如何,我建议始终执行一些分析。这是确保您的请求是否有效的唯一方法。
有很多工具可以帮助您。看看Julie Lerman的这篇文章,她列出了几种不同的分析方法。一个简单的解决方案是在SQL Server Management Studio中开始分析。
不要犹豫,与DBA(如果你在你附近(交谈,这将有助于你理解执行计划。
你也可以看看这个演示文稿,我写了一个关于加载数据和性能的部分。

还有

一件事要添加到这个线程中。这取决于您使用的服务器。如果你在sql服务器上工作,可以使用预先加载,但对于sqlite,你必须使用。Load(( 以避免交叉加载异常导致 sqlite 无法处理一些包含语句,这些语句比一个依赖级别更深

更新

的答案:从 EF Core 5.0 开始,您可以使用 AsSplitQuery() .

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

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

因此,在使用显式加载的地方,您现在可以将 Eager 加载与拆分查询一起使用以获得相同的结果,并且它绝对更具可读性 imo。

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