entity framework包括性能

我一直在关注entity framework的性能,特别是关于使用Includes以及生成和执行各种查询所花费的时间。

我将详细介绍我所做的更改,但如果您认为任何这些假设是错误的,请纠正我。

首先,我们在数据库中有大约10,000个项目(不是很多),并且数据库显着标准化(这导致了大量的导航属性)。 目前的方法是延迟加载所有内容,并且假设请求一个项目可以假脱机数十个db请求,性能非常差,特别是对于较大的数据集。 (这是一个inheritance的项目,第一步是尝试在没有重大重组的情况下提高性能)

因此,我的第一步是获取查询结果,然后仅将导航属性的包含应用于这些结果。 我知道这在技术上执行了2个查询,但是如果我们存储了10,000个项目,但只想返回10个项目,那么仅在这10个项目中包含导航属性更有意义。

其次,在查询结果上使用多个包含且结果集大小非常大的情况下,仍然会遇到性能不佳的问题。 关于什么时候需要加载以及何时将延迟加载到位,我一直很务实。 我的下一个更改是批量加载查询包含,因此执行:

query.Include(q => q.MyInclude).Load();

这再一次显着提高了性能,虽然还有一些db调用(每批包含一个),它比大型查询更快,或者至少减少了entity framework尝试生成大型查询的开销。

所以代码现在看起来像这样:

  var query = ctx.Filters.Where(x => x.SessionId == id) .Join(ctx.Items, i => i.ItemId, fs => fs.Id, (f, fs) => fs); query .Include(x => x.ItemNav1) .Include(x => x.ItemNav2).Load(); query .Include(x => x.ItemNav3) .Include(x => x.ItemNav4).Load(); query .Include(x => x.ItemNav5) .Include(x => x.ItemNav6).Load(); 

现在这是相当高效的,但是,进一步改进这一点会很好。

我曾考虑过使用LoadAsync() ,经过一点点重构后,它可以更好地适应架构的其余部分。

但是,您只能在db上下文中一次执行一个查询。 所以我想知道是否有可能创建一个新的db上下文,在每组导航属性上执行LoadAsync() (异步),然后连接所有结果。

我从技术上知道你如何创建一个新的上下文,为每个导航组启动一个LoadAsync() ,但不知道如何连接结果,我不知道它是否有可能或者是否违反了良好的做法。

所以我的问题是; 这是可能的,或者,还有另一种方法可以进一步提高性能吗? 我试图坚持entity framework提供的东西,而不是制作一些存储过程。 谢谢

UPDATE

关于在一个语句中使用所有包含和在小组中加载这些包含之间的性能差异。 运行返回6000个项目的查询时。 (使用SQL事件探查器和VS诊断程序确定时间)

分组包括:执行包含总共需要约8秒。

包含在一个语句中:SQL查询需要大约30秒才能加载。 (通常会超时)

经过一番调查后,我认为当EF将sql结果转换为模型时,开销并不大。 但是我们已经看到EF用于生成复杂查询的近500毫秒,这不是理想的,但我不确定这是否可以解决

更新2

在Ivan的帮助下,通过这个https://msdn.microsoft.com/en-gb/data/hh949853.aspx,我们能够进一步改进,特别是使用SelectMany 。 我强烈推荐msdn文章给任何试图改善其EF性能的人。

您的第二种方法依赖于EF导航属性修复过程。 问题是尽管如此

 query.Include(q => q.ItemNavN).Load(); 

声明还将包括所有主记录数据以及相关实体数据。

使用相同的基本思想,一个潜在的改进可能是每个导航属性执行一个Load ,用Select (用于引用)或SelectMany (用于集合)替换Include – 类似于EF Core在内部处理Include的方式。

采用第二种方法示例,您可以尝试以下方法并比较性能:

 var query = ctx.Filters.Where(x => x.SessionId == id) .Join(ctx.Items, i => i.ItemId, fs => fs.Id, (f, fs) => fs); query.Select(x => x.ItemNav1).Load(); query.Select(x => x.ItemNav2).Load(); query.Select(x => x.ItemNav3).Load(); query.Select(x => x.ItemNav4).Load(); query.Select(x => x.ItemNav5).Load(); query.Select(x => x.ItemNav6).Load(); var result = query.ToList(); // here all the navigation properties should be populated 

我知道这在技术上执行了2个查询,但是如果我们存储了10,000个项目,但只想返回10个项目,那么仅在这10个项目中包含导航属性更有意义。

我想你误解了.Include运算符是如何工作的。 在下面的代码中,DB只返回我们想要的项目,不会有“额外数据”。

 ctx.Items.Include(e => e.ItemNav1) .Include(e => e.ItemNav2) .Include(e => e.ItemNav3) .Include(e => e.ItemNav4) .Include(e => e.ItemNav5) .Include(e => e.ItemNav6) .Where() .ToList(); 

如果只有10个项符合筛选条件,则只返回这些项的数据。 在幕后,.Include大致类似于SQL JOIN。 仍有性能方面的考虑因素,但实际上没有任何理由(我知道)可以避免使用这种标准语法。


如果连接导致性能问题,可能问题是您的数据库。 你有适当的索引吗? 它们是否支离破碎?