实体框架包括性能
本文关键字:性能 包括 框架 实体 | 更新日期: 2023-09-27 18:13:54
我一直在关注实体框架的性能,特别是关于的使用,包括和生成和执行各种查询所花费的时间。
我将详细说明我所做的更改,但如果你认为这些假设中的任何一个是错误的,请纠正我。
首先,我们在数据库中有大约10,000个项目(不多),并且数据库显着规范化(这导致大量的导航属性)。目前,该方法是延迟加载所有内容,并且考虑到请求一个项可能会导致数十个DB请求,因此性能非常差,特别是对于较大的数据集。(这是一个继承的项目,第一步是试图在不进行重大重组的情况下提高性能)
因此,我的第一步是获取查询的结果,然后将导航属性的Includes仅应用于这些结果。我知道这在技术上执行2个查询,但是如果我们存储了10,000个项目,但只想返回10个项目,那么只包含这10个项目的导航属性更有意义。其次,如果在一个查询结果上使用多个包含,并且结果集的大小相当大,那么它仍然会受到性能差的影响。我一直是务实的何时急于加载,何时离开懒惰加载到位。我的下一个更改是批量加载查询包含,因此执行:
query.Include(q => q.MyInclude).Load();
这再次显著提高了性能,尽管需要更多的DB调用(每批包含一个),但它比一个大查询要快,或者至少减少了实体框架试图产生那个大查询的开销。
所以代码现在看起来像这样: 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()
,但不知道如何连接结果,我不知道它是否绝对可能或是否违背了良好的实践。
我的问题是;这是可能的吗,或者有其他方法可以进一步提高性能吗?我试图坚持使用实体框架提供的东西,而不是制作一些存储过程。由于
关于性能差异,我看到在一个语句中使用所有include和在小组中加载这些之间。当运行返回6000项的查询时。(使用SQL分析器和VS诊断来确定时间)
分组包含:执行包含总共需要~8秒。
包含在一条语句中:SQL查询需要~30秒来加载。(经常超时)
经过更多的调查,我认为EF将SQL结果转换为模型时没有太多的开销。然而,我们已经看到EF花费了近500毫秒来生成复杂的查询,这不是理想的,但我不确定这可以解决
更新2
与伊万的帮助和以下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
对于来到这里的每个人,我希望你们知道以下两件事:
-
。Select(x => x. navprop). load()实际上不会加载导航属性,如果你已经关闭了跟踪
-
从3.0.0版本开始,每个Include都会导致在关系提供者生成的SQL查询中添加额外的JOIN,而以前的版本会生成额外的SQL查询。这可能会显著改变查询的性能,或好或坏。特别是,包含大量Include操作符的LINQ查询可能需要分解为多个单独的LINQ查询,以避免笛卡尔爆炸问题。
两个语句的源:https://learn.microsoft.com/en-us/ef/core/querying/related-data
所以EF Core在后台做Select和SelectMany是不正确的。在我的例子中,我们有一个带有许多导航属性的单一实体,而在Include中,它实际上加载了超过15,000行(是的,这是正确的,我称之为笛卡尔爆炸问题)。在我重构代码以使用Select/SelectMany之后,行数减少到118行。查询时间从5秒减少到不到1秒,尽管我们有20个include)
希望这能帮助到一些人,并且非常感谢Ivan。
提高性能的方法有很多
我在这里放一些,你可以试试每一个,看看谁给你最好的效果。
您可以使用System.Diagnostics.StopWatch来获取运行时间。
1。 索引丢失(例如外键)
2。在数据库中的视图中编写查询,这要便宜得多。您还可以为此查询创建索引视图。
3。尝试在单独的查询中加载数据:
context.Configuration.LazyLoadingEnabled = false;
context.ContactTypes.Where(c => c.ContactID== contactId).Load();
context.ContactConnections.Where(c => c.ContactID== contactId).Load();
return context.Contacts.Find(contactId);
将所有需要的数据加载到上下文的缓存中。重要提示:关闭延迟加载,因为子集合在实体状态管理器中没有被标记为已加载,当你想要访问它们时,EF会尝试触发延迟加载。
4。用Select().Load():
替换Includevar query = ctx.Users.Where(u => u.UserID== userId)
.Join(ctx.Persons, p => p.PersonID, us => us.PersonID, (pr, ur) => ur);
query.Select(x => x.PersonIdentities).Load();
query.Select(x => x.PersonDetails).Load();
var result = query.ToList();
记住:打开跟踪来加载导航属性。
5。分隔包含,对于多个调用,限制为2,在每个调用中包含,然后循环连接对象属性。
下面是一个单对象获取的例子:
var contact= from c in db.Contacts
.Include(p=>p.ContactTypes)
.Include(p=>p.ContactConnections)
.FirstOrDefault();
var contact2= from c in db.Contacts
.Include(p=>p.ContactIdentities)
.Include(p=>p.Person)
.FirstOrDefault();
contact.ContactIdentities = contact2.ContactIdentities ;
contact.Person= contact2.Person;
return contact.
我知道这在技术上执行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(<filter criteria>)
.ToList();
如果只有10个项目符合筛选条件,这将只返回这些项目的数据。在幕后,. include大致类似于SQL JOIN。这里仍然有性能方面的考虑,但是(据我所知)没有任何理由避免使用这种标准语法。
如果连接导致性能问题,可能问题出在数据库上。你有合适的指标吗?它们是碎片化的吗?