Cor*_*lis 13 c# sql sql-server performance entity-framework
我一直在关注Entity Framework的性能,特别是关于使用Includes以及生成和执行各种查询所花费的时间.
我将详细介绍我所做的更改,但如果您认为任何这些假设是错误的,请纠正我.
首先,我们在数据库中有大约10,000个项目(不是很多),并且数据库显着标准化(这导致了大量的导航属性).目前的方法是延迟加载所有内容,并且假设请求一个项目可以假脱机数十个db请求,性能非常差,特别是对于较大的数据集.(这是一个继承的项目,第一步是尝试在没有重大重组的情况下提高性能)
因此,我的第一步是获取查询结果,然后仅将导航属性的包含应用于这些结果.我知道这在技术上执行了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();
Run Code Online (Sandbox Code Playgroud)
现在这是相当高效的,但是,进一步改进这一点会很好.
我曾经考虑过使用LoadAsync()
,经过一些重构后,它可以更好地适应架构的其他部分.
但是,您只能在db上下文中一次执行一个查询.所以我想知道是否有可能创建一个新的db上下文,LoadAsync()
对每组导航属性执行(异步)然后连接所有结果.
我从技术上知道你如何创建一个新的上下文,LoadAsync()
为每个导航组启动一个,但不是如何连接结果,我不知道它是否有可能或者是否违反了良好的做法.
所以我的问题是; 这是可能的,还是有另一种方法可以进一步提高性能吗?我试图坚持实体框架提供的东西,而不是制作一些存储过程.谢谢
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();
Run Code Online (Sandbox Code Playgroud)
声明还将包括所有主记录数据以及相关实体数据.
使用相同的基本思想,一个潜在的改进可能是Load
每个导航属性执行一个,替换Include
为Select
(对于引用)或SelectMany
(对于集合) - 类似于EF Core Include
内部处理s的方式.
采用第二种方法示例,您可以尝试以下方法并比较性能:
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
Run Code Online (Sandbox Code Playgroud)
对于来到这里的每个人,我希望您知道以下两件事:
如果您关闭了跟踪,.Select(x => x.NavProp).Load() 实际上不会加载导航属性。
从 3.0.0 版本开始,每个 Include 都会导致将额外的 JOIN 添加到关系提供程序生成的 SQL 查询中,而以前的版本会生成额外的 SQL 查询。这会显着改变查询的性能,无论好坏。特别是,具有大量 Include 运算符的 LINQ 查询可能需要分解为多个单独的 LINQ 查询,以避免笛卡尔爆炸问题。
两个声明的来源:https : //docs.microsoft.com/en-us/ef/core/querying/related-data
因此,EF Core 在后台执行 Select 和 SelectMany 是不正确的。在我的例子中,我们有一个带有大量导航属性的实体,并且使用 Include 它实际上加载了超过 15,000 行(是的,这是正确的,我称之为笛卡尔爆炸问题)。在我重构代码以使用 Select / SelectMany 后,行数减少到 118。查询时间从 4 秒减少到不到一秒,即使我们正好有 20 个包含)
希望这对某人有所帮助,非常感谢伊万。
小智 5
有很多方法可以提高性能。
我会在这里放一些,你可以尝试每一个,看看谁给你最好的结果。
您可以使用 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);
Run Code Online (Sandbox Code Playgroud)
这会将所有需要的数据加载到上下文的缓存中。重要提示:关闭延迟加载,因为子集合在实体状态管理器中未标记为已加载,EF 将在您想要访问它们时尝试触发延迟加载。
4.用Select().Load()替换 Include :
var 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();
Run Code Online (Sandbox Code Playgroud)
请记住:打开跟踪以加载导航属性。
5.分离includes,多次调用,每次调用限制为2个includes,然后循环连接对象属性。
这是单个对象获取的示例:
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.
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
8052 次 |
最近记录: |