我一直在关注Entity Framework的性能,尤其是在使用 Includes 以及生成和执行各种查询所花费的时间方面。
我将详细说明我所做的更改,但是如果您认为这些假设中的任何一个错误,请更正我。
首先,数据库中大约有10,000个项目(数量不多),并且数据库已被规范化(这导致大量的导航属性)。当前,该方法是延迟加载所有内容,并且考虑到一项请求可以缓冲数十个数据库请求,因此性能相当差,尤其是对于较大的数据集。(这是一个继承的项目,第一步是尝试在不进行重大重组的情况下提高性能)
因此,我的第一步是获取查询结果,然后仅将导航属性的 Includes 应用于这些结果。我知道这在技术上执行了2个查询,但是如果我们存储了10,000个项目,但只想返回10个项目,则仅在这10个项目上包含导航属性就更有意义了。
其次,在查询结果上使用多个包含并且结果集大小很大的情况下,它仍然遭受性能不佳的困扰。对于什么时候渴望加载以及何时将延迟加载保留在原处,我一直很务实。我的下一个更改是要批量加载包含的查询,因此请执行以下操作:
query.Include(q => q.MyInclude).Load();
这再次显着提高了性能,尽管它进行了更多的数据库调用(每批包含一个),它比大型查询更快,或者至少减少了尝试生成大型查询的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(),在进行一些重构之后,它可能会更好地适合于架构的其余部分。
LoadAsync()
但是,一次只能在db上下文中执行一个查询。所以我想知道是否有可能创建一个新的数据库上下文,LoadAsync()对每个导航属性组(异步)执行,然后将所有结果连接在一起。
从技术上讲,我知道如何创建新的上下文,LoadAsync()为每个导航组触发一个,但是不知道如何将结果串联起来,我不知道这是否绝对可能或是否违背了良好做法。
所以我的问题是;这是可能的,还是我可以进一步提高性能的另一种方法?我试图坚持使用实体框架提供的功能,而不是精心设计一些存储过程。谢谢
更新
关于性能差异,我发现在一条语句中使用所有“包含”与将它们以小组形式加载之间存在差异。运行返回6000个项目的查询时。(使用SQL事件探查器和VS诊断来确定时间)
分组包含:总共需要约8秒钟才能执行包含。
包含在一条语句中:SQL查询加载大约需要30秒。(经常会超时)
经过更多调查后,我认为EF将sql结果转换为模型时没有太多开销。但是,我们已经看到EF将近500毫秒的时间来生成复杂的查询,这并不理想,但是我不确定这是否可以解决
更新2
在Ivan的帮助下,并按照https://msdn.microsoft.com/en- gb/data/hh949853.aspx进行操作,我们得以进一步改进,尤其是使用SelectMany。我强烈建议任何尝试提高其EF性能的人阅读msdn文章。
SelectMany
您的第二种方法依赖于EF导航属性修复过程。问题是尽管每个
query.Include(q => q.ItemNavN).Load();
语句还将包括所有主记录数据以及相关的实体数据。
使用相同的基本思想,一个潜在的改进可能是执行一个Load按每个导航属性,替换Include用任何Select(对于引用)或SelectMany(托收) -类似EF核心是如何处理的一些Include内部秒。
Load
Include
Select
以第二种方法为例,您可以尝试以下方法并比较性能:
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