我一直在关注Entity Framework的性能问题,特别是在使用Includes和生成/执行各种查询所需的时间方面。
接下来我将详细介绍我所做的更改,但如果您认为这些假设中有任何错误,请纠正我。
首先,我们的数据库中有大约10,000个项目(不算多),并且数据库被显着规范化(导致有大量的导航属性)。当前的方法是懒加载所有内容,并且由于请求一个项目可能会产生数十个数据库请求,因此性能相当差,尤其是对于较大的数据集。
(这是一个继承而来的项目,第一步是试图在不进行重大重构的情况下提高性能)
因此,我的第一步是获取查询结果,然后仅对那些结果应用导航属性的Includes。
我知道这实际上执行了2个查询,但是如果我们在存储了10,000个项目,但只想返回10个项目时,在这10个项目上包含导航属性更有意义。
其次,在查询结果上使用多个includes并且结果集很大时,性能仍然很差。我很注重何时应该急切加载和何时应该保留懒加载。
我的下一个更改是批量加载查询includes,因此执行:
query.Include(q => q.MyInclude).Load();
这再次极大地提高了性能,尽管会有更多的DB调用(每个includes的批次会产生一个调用),但它比一个大查询或至少减少了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 提供的功能,而不是编写存储过程。谢谢
更新
关于使用所有 Includes 在一个语句中和将其分组加载之间的性能差异。运行返回 6000 个项目的查询时。(使用 SQL Profiler 和 VS 诊断来确定时间)
分组包含: 总共需要 ~8 秒钟才能执行包含操作。
在一个语句中包含: SQL 查询需要大约 30 秒钟才能加载。(经常超时)
经过更多的调查,我认为 EF 将 SQL 结果转换为模型时几乎没有太多开销。但是我们已经看到 EF 生成复杂查询需要将近 500ms 的时间,这不是理想的,但我不确定是否可以解决。
更新2
在 Ivan 的帮助下,并遵循这篇文章:https://msdn.microsoft.com/en-gb/data/hh949853.aspx,我们进一步改善了性能,特别是使用了 SelectMany
。我强烈建议任何试图提高他们的 EF 性能的人阅读 MSDN 文章。