.net EntityFramework中的.Include()与Load()性能比较

s6fujrry  于 2022-12-20  发布在  .NET
关注(0)|答案(7)|浏览(217)

当查询一个大的表时,你需要在代码中访问导航属性(我明确地不想使用延迟加载),.Include().Load()哪一个执行得更好?或者为什么使用一个而不是另一个?
在这个例子中,被包含的表都只有大约10个条目,而employees有大约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();
vuktfyat

vuktfyat1#

更新答案:从EF Core 5.0开始,您可以使用AsSplitQuery()
这是特别有用的,我个人使用它所有的时间,当我有许多连接,这将导致可能的笛卡尔爆炸,或将只是需要更多的时间来完成。
顾名思义,EF将为每个实体执行单独的查询,而不是使用连接。
因此,在您使用显式加载的地方,您现在可以使用带拆分查询的Eager加载来实现相同的结果,而且它无疑更具可读性。
参见https://learn.microsoft.com/en-us/ef/core/querying/single-split-queries

h5qlskok

h5qlskok2#

看情况,两个都试试
当使用Include()时,您可以获得好处,只需一次调用底层数据存储就可以加载所有数据,例如,如果这是一个远程SQL Server,这将是一个重大的性能提升。

缺点Include()查询往往会变得*非常复杂**,特别是如果您有任何过滤器的话(Where()呼叫,EF将使用sub-SELECTAPPLY语句生成非常严重的嵌套查询来获取您想要的数据。它的效率也要低得多--您将得到单行数据,其中包含每个可能的子对象列,因此顶层对象的数据将重复很多次。(例如,具有10个子对象的单个父对象将生成10行,每个都有相同的父对象列数据)。我遇到过单个EF查询,它们变得非常复杂,在与EF更新逻辑同时运行时会导致死锁。

Load()方法简单得多。每个查询都是针对单个表的单个、简单、直接的SELECT语句。这些方法在所有可能的方面都要简单得多,* 除了 * 您必须执行其中的许多操作(可能多很多倍)。如果您有集合的嵌套集合,你甚至需要遍历你的顶层对象和Load它们的子对象。这可能会失控。

快速经验法则

尝试避免在单个查询中有任何超过三个Include调用。我发现EF的查询变得太难看了,以至于无法识别;这也符合我对SQL Server查询的经验法则,即在一个查询中最多可以使用四个JOIN语句,但在此之后,就该 * 考虑重构 * 了。
然而,所有这一切仅仅是一个起点。

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

最后,您只需要尝试每种方法

  • 选择一个合理的“默认”模式来使用,看看它是否足够好,如果不够好,就进行优化。*
3bygqnnd

3bygqnnd3#

Include()将作为JOIN写入SQL:一次数据库往返。
每个Load()-指令都"显式地加载"所请求的实体,因此每个调用一次数据库往返。
因此Include()在这种情况下很可能是更明智的选择,但这取决于数据库布局、调用此代码的频率以及DbContext的寿命。为什么不尝试两种方法,分析查询并比较时间呢?
参见Loading Related Entities

l5tcr1uw

l5tcr1uw4#

我同意@MichaelEdenfield在他的answer中的观点,但我确实想对嵌套集合场景进行评论。您可以通过将查询从里到外翻转来避免必须执行嵌套循环(以及许多由此产生的对数据库的调用)。
您可以使用如下筛选器直接查询OrderItems,而不是向下循环通过Customer的Orders集合,然后再执行另一个嵌套循环通过Order的OrderItems集合。

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

您将获得与嵌套循环中的Loads相同的结果数据,但只需对数据库进行一次调用。
此外,Includes还有一个特殊情况需要考虑。如果父代和子代之间的关系是一对一,那么父代数据被多次返回的问题就不是问题。
我不确定如果大多数情况下没有子节点存在会有什么效果--大量的空值?一对一关系中的稀疏子节点可能更适合我上面概述的直接查询技术。

ogsagwnx

ogsagwnx5#

Include是一个快速加载的示例,其中您不仅加载要查询的实体,而且加载所有相关实体。
LoadEnableLazyLoading的手动覆盖。如果将此设置为false,您仍然可以使用.Load()延迟加载所需的实体

hmmo2u0o

hmmo2u0o6#

总是很难决定是使用“渴望”、“显式”还是“懒惰加载”。
无论如何,我推荐的是总是执行一些分析,这是确保您的请求是否可执行的唯一方法。
有很多工具可以帮助你,看看this article from Julie Lerman,她列出了几种不同的分析方法,一个简单的解决方案是启动profiling in your SQL Server Management Studio
不要犹豫,与DBA(如果您身边有)交谈,这将帮助您理解执行计划。
您还可以查看this presentation,我在其中写了一节关于加载数据和性能的内容。

vtwuwzda

vtwuwzda7#

还有一件事要添加到这个线程中。这取决于你使用的服务器。如果你正在使用sql server,可以使用快速加载,但是对于sqlite,你必须使用.Load()来避免交叉加载异常,因为sqlite不能处理一些深度超过一个依赖级别的include语句

相关问题