简体   繁体   English

实体框架上的懒惰与急切加载性能

[英]Lazy vs eager loading performance on Entity Framework

So I have the following model classes on my DbContext: 所以我的DbContext上有以下模型类:

贷款

Everytime I render a list of LoanApplication objects I do something like this: 每次我渲染一个LoanApplication对象列表时,我都会这样做:

var context = new MyContext();
var applications = context.LoanApplications.Where(d => d.PropertyThatIWantToFilter = localVariable);

This returns an IQueryable that then I convert to a ViewModel like this on my controller method call: 这将返回一个IQueryable,然后我在我的控制器方法调用上转换为这样的ViewModel:

var vm = applications.Select(d => new LoanApplicationViewModel(d));

Inside the LoanApplicationViewModel constructor I accept the entity object and do the corresponding mapping. LoanApplicationViewModel构造函数内部,我接受实体对象并执行相应的映射。 The thing is that, since the Solicitors collection is a navigational property, a call is made to the database each time a new view model is instanced. 问题在于,由于Solicitors集合是一个导航属性,因此每次实例化新的视图模型时都会对数据库进行调用。 The average number of solicitors per application is two, so that means that if I render a table listing the last 10 applications then the app is making about ~18-20 trips to the database. 每个应用程序的平均律师数量是两个,这意味着如果我呈现一个列出最后10个应用程序的表,那么该应用程序将大约18-20次访问数据库。

I thought there had to be a better way to get this collection, so I changed my original query to eagerly load the collection like so: 我认为必须有一个更好的方法来获得这个集合,所以我改变了我的原始查询,急切地加载集合,如下所示:

var applications = context.LoanApplications.Include("Solicitors").Where...

Although this reduced the number of calls to the database to only one, the query was much slower, about 50% more slow. 虽然这会将对数据库的调用次数减少到只有一次, 但查询速度要慢得多,大约慢50%。

The database is hosted on SQL Azure, and we've implemented Transient Fault Handling, but I want to reduce the quantity of calls made to the database without reducing response-time performance. 数据库托管在SQL Azure上,我们已经实现了瞬态错误处理,但我希望减少对数据库的调用数量,而不会降低响应时间性能。

What is the best practice here? 这里的最佳做法是什么?

"What is the best practice here?" “这里的最佳做法是什么?”

The best practice is to 最好的做法是

  1. set !application wide! 设置!应用范围广! performance target 绩效目标
  2. profile, benchmark, and locate bottle neck 轮廓,基准和定位瓶颈
  3. review and fine tune the bottle neck that gives you the greatest performance win for least work. 检查并微调瓶颈,为您提供最佳性能,赢得最少的工作。 (and from my experience 90% of the time it's not tsql) (根据我的经验,90%的时间不是tsql)

Now that may seem a bit irrelevant, but from that point of view, which ever loading pattern you PROFILED to be optimal within your application domain is the correct way to go. 现在看起来似乎有点无关紧要,但从这个角度来看,哪种加载模式你在应用程序域中最佳化是正确的方法。

There's no "best practice" of eager/lazy. 渴望/懒惰没有“最佳实践”。 That's why both options are both available. 这就是为什么两种选择都可用的原因。 Also if the tsql is your bottle neck and switching between eager/lazy still isn't hitting your performance target, you will need to go down a whole plethora of other tools such as query analyzer and query plan analyser in SSMS. 此外,如果tsql是你的瓶颈并且在渴望/懒惰之间切换仍然没有达到你的性能目标,那么你需要在SSMS中查看大量其他工具,例如查询分析器和查询计划分析器。


For some background: 对于某些背景:

I was googling "eager loading slow" and came here. 我正在谷歌搜索“急切加载慢”来到这里。 Here's my result: 这是我的结果:

var foo = _context.Foos
    //.Include("Answers")
    //.Include("Attachments")
    .FirstOrDefault(q => q.Id == key);

Eager loading: 106ms 渴望加载:106ms

Lazy loading: 11ms + 5ms + 5ms 延迟加载:11ms + 5ms + 5ms

Lazy loading wins, end of story. 懒人装载获胜,故事结束。

In addition to SQL statements that gives a huge results or lots of calls when using both eager and lazy there is huge job that takes place by putting and mapping into the ObjectContext/DbContext from the result. 除了在使用eager和lazy时产生巨大结果或大量调用的SQL语句之外,通过从结果中放入和映射到ObjectContext / DbContext中会发生巨大的工作。 This causes a huge performance hit and I can't really recommend any of these when retrieving large amount of data. 这会导致巨大的性能损失,在检索大量数据时我无法真正推荐任何这些。

The best solution is to specify an explicit Select call. 最佳解决方案是指定显式Select调用。 However, it's a bit difficult to give you an example on how to do this without knowing how your viewmodel object is built up. 但是,在不知道如何构建viewmodel对象的情况下,给出一个如何执行此操作的示例有点困难。 So, what I do here is giving you an example that uses anonymous object's as result from the query. 所以,我在这里做的是给你一个使用匿名对象作为查询结果的例子。

This example gives you contacts with information about the customer the contact belongs to. 此示例为您提供有关联系人所属客户的信息的联系人。

var contacts = context.Contacts.Where(row => row.CategoryId == 1)
                      .Select(row => new {
                                             ContactId = row.Id,
                                             Name = row.Name,
                                             CustomerName = row.Customer.Name
                                         }).ToList();

This query will generate an SQL SELECT that joins Contacts with Customer using an inner join, and then only select the Contact.Id, Contact.Name and Customer.Name columns. 此查询将生成一个SQL SELECT,它使用内部联接将Customer与Customer连接,然后仅选择Contact.Id,Contact.Name和Customer.Name列。

This solution is far most the most effective way to retrieve data from server if you don't intend to work with the data and save the changes right back to the same context. 如果您不打算使用数据并将更改保存回相同的上下文,则此解决方案是从服务器检索数据的最有效方法。 It doesn't use either eager nor lazy loading. 它既不使用急切也不延迟加载。

If you could somehow query your solicitors table and filter the query using your already fetched list of applications then the fetched Entities would be cached in your context, which I believe will then be used for the navigation property instead of hitting the database. 如果你能以某种方式查询你的律师表并使用你已经获取的应用程序列表过滤查询,那么获取的实体将被缓存在你的上下文中,我相信这将被用于导航属性而不是命中数据库。

I'm not sure exactly how to write the solicitors fetching query, but I was thinking something like this 我不确定如何编写律师提取查询,但我在想这样的事情

int[] applicationIDs = applications.Select(x => x.ID).ToArray();
var solicitors = context.Solicitors.Where(x => x.Applications.Any(y => applicationIDs.Contains(y.ID))).ToArray(); // added toarray to cause execution cause im never sure when the LINQ actually runs

Have you considered to use sql view? 你考虑过使用sql视图吗?

I don't quite sure about Sql Azure. 我不太确定Sql Azure。 However in sql server, you can have performance penalty when joining 2 tables without having proper indexes. 但是在sql server中,在没有正确索引的情况下连接2个表时可能会有性能损失。 Maybe this happen in your query. 也许这会发生在您的查询中。

To be noted, your before query is accessing 1 table with where clause, 2 calls. 需要注意的是,您之前的查询是使用where子句,2个调用访问1个表。 And in the after query, it is accessing 2 tables with where clause, 1 call. 在after查询中,它使用where子句,1个调用访问2个表。 There is join in your after query and is likely to need different index. 在您的查询后加入,可能需要不同的索引。

You can create a sql view to make sure that a proper index is used. 您可以创建一个sql视图以确保使用正确的索引。 Then make your application call the view. 然后让您的应用程序调用视图。 Stored procedure can be used for this purpose too but it is less suitable for this. 存储过程也可用于此目的,但不太适合这种情况。

Eager loading fetches redundant master data. 急切加载提取冗余主数据。 It will take lots of memory, though object graph in context stores only single master data per entity, but SQL will dump lots of data in it's plate. 尽管上下文中的对象图仅存储每个实体的单个主数据,但它会占用大量内存,但SQL会将大量数据转储到其中。 I took following image from here 我从这里拍了下面的图片

在此输入图像描述

If you see, Data of User table also repeated as many as UserDetails table in result set of SQL query. 如果你看到,Data of User表也在SQL查询的结果集中重复了UserDetails表的数量。 That seem to differentiating factor in performance (In your case master columns has more records then detail table). 这似乎可以区分性能因素(在您的情况下,主列有更多记录,然后是详细信息表)。

If performance is your major concern, I would recommend You to use LINQ join with same where clause while fetching data for detail table separately So in your case :- 如果性能是您主要考虑的问题,我建议您在使用相同的where子句时使用LINQ join ,同时单独获取详细信息表的数据。所以在您的情况下: -

step1 步骤1

 var context = new MyContext();
    var applications = context.LoanApplications.Where(d => d.PropertyThatIWantToFilter = localVariable);

and then step2 然后是step2

var solicitors = from s in context.Solicitors
join loanApp in context.LoanApplications
select s.columns
where loanApp. <<Same condition as in step 1 where clause>>

Thanks, your question made me to review my own code :-) 谢谢,你的问题让我回顾了我自己的代码:-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM