多次使用Include()时,entity framework代码很慢

我一直在调试一些慢速代码,似乎罪魁祸首是下面发布的EF代码。 在稍后阶段评估查询时需要4-5秒。 我试图让它在1秒内运行。

我使用SQL Server Profiler对此进行了测试,似乎执行了一堆SQL脚本。 它还确认SQL Server完成执行需要3-4秒。

我已经阅读了有关使用Include()的其他类似问题,并且在使用它时似乎确实存在性能损失。 我试图将下面的代码分成几个不同的查询,但它并没有太大的区别。

知道我怎么能让下面更快地执行?

目前我正在处理的网络应用程序只是在等待下面的内容时显示一个空的iframe。 如果我无法获得更快的执行时间,我必须将其拆分并部分加载iframe数据或使用其他异步解决方案。 这里的任何想法也将不胜感激!

using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted })) { formInstance = context.FormInstanceSet .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormSectionDefinitions).Include(fs => fs.FormStateDefinitionEditableSections)) .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormStateDefinitions)) .Includes(x => x.Include(fi => fi.FormSectionInstances).Include(fs => fs.FormFieldInstances).Include(ff => ff.FormFieldDefinition).Include(ffd => ffd.FormFieldMetaDataDefinition).Include(ffmdd => ffmdd.ComplexTypePropertyNames)) .Include(x => x.CurrentFormStateInstance) .Include(x => x.Files) .FirstOrDefault(x => x.FormInstanceIdentifier == formInstanceIdentifier); scope.Complete(); } 

使用Include时似乎确实存在性能损失

这是轻描淡写! 多个Include快速扩展SQL查询结果的宽度和长度。 这是为什么?

tl; dr Multiple Include s炸毁SQL结果集。 很快,通过多个数据库调用加载数据而不是运行一个mega语句变得更便宜。 尝试找到IncludeLoad语句的最佳混合。

生长因子Include s

假设我们有

  • 根实体Root
  • 父实体Root.Parent
  • 子实体Root.Children1Root.Children2
  • LINQ语句Root.Include("Parent").Include("Children1").Include("Children2")

这将构建一个具有以下结构的SQL语句:

 SELECT *,  FROM Root JOIN Parent JOIN Children1 UNION SELECT *,  FROM Root JOIN Parent JOIN Children2 

这些由类似CAST(NULL AS int) AS [C2],的表达式组成CAST(NULL AS int) AS [C2],它们在所有UNION -ed查询中具有相同数量的列。 第一部分为Child2添加伪列, Child2添加伪列。

这就是SQL结果集大小的含义:

  • SELECT子句中的列数是四个表中所有列的总和
  • 数是包含的子集合中的记录总和

由于数据点的总数是columns * rows ,因此每个附加的Include指数方式增加结果集中的数据点总数。 让我再次尝试使用Root ,现在再添加一个Children3集合。 如果所有表都有5列和100行,我们得到:

一个IncludeRoot + 1子集合):10列* 100行= 1000个数据点。
两个Include s( Root + 2子集合):15列* 200行= 3000个数据点。
三个Include s( Root + 3子集合):20列* 300行= 6000个数据点。

有12个Includes这将达到78000个数据点!

相反,如果您分别获得每个表的所有记录而不是12个Includes ,则您有13 * 5 * 100数据点:6500,低于10%!

现在这些数字有些夸大,因为许多这些数据点都是null ,因此它们对发送给客户端的结果集的实际大小没有太大贡献。 但查询大小和查询优化器的任务肯定会受到Include s数量增加的负面影响。

平衡

因此,使用Includes是数据库调用和数据量之间的微妙平衡。 很难给出一个经验法则,但是现在你可以想象,如果有超过3个子集合的Includes ,那么数据量通常会快速超过额外调用的成本(但对于父Includes ,相当多一些, Includes只扩大结果集)。

替代

Include的替代方法是在单独的查询中加载数据:

 context.Configuration.LazyLoadingEnabled = false; var rootId = 1; context.Children1.Where(c => c.RootId == rootId).Load(); context.Children2.Where(c => c.RootId == rootId).Load(); return context.Roots.Find(rootId); 

这会将所有必需的数据加载到上下文的缓存中。 在此过程中,EF执行关系修正,通过它可以通过加载的实体自动填充导航属性( Root.Children等)。 最终结果与Include s的语句相同,除了一个重要区别:子集合未在实体状态管理器中标记为已加载,因此如果您访问它们,EF将尝试触发延迟加载。 这就是为什么关闭延迟加载很重要的原因。

实际上,您必须弄清楚IncludeLoad语句的哪种组合最适合您。

您是否在尝试“包含”的所有实体之间正确配置了关系? 如果至少有一个实体与某些其他实体没有关系,那么EF将无法使用SQL连接语法构造一个复杂查询 – 相反,它将执行与您拥有的许多“包含”一样多的查询。 当然,这将导致性能问题。 您能否发布EF生成的确切查询(-es)以获取数据?