.包括() 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();
视情况而定,同时尝试两者
使用 Include()
时,您可以在对基础数据存储的单个调用中加载所有数据。例如,如果这是远程 SQL Server,则可以显著提高性能。
缺点是Include()
查询往往变得非常复杂,特别是如果您有任何过滤器(例如Where()
调用(或尝试进行任何分组。EF 将使用子SELECT
和APPLY
语句生成非常密集的嵌套查询,以获取所需的数据。它的效率也低得多 - 你得到一行数据,其中包含每个可能的子对象列,所以你的顶级对象的数据将重复很多次。(例如,具有 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