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

时间:2013-04-03 03:29:47

标签: c# asp.net-mvc entity-framework code-first azure-sql-database

所以我的DbContext上有以下模型类:

Loans

每次我渲染一个LoanApplication对象列表时,我都会这样做:

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

这将返回一个IQueryable,然后我在我的控制器方法调用中转换为这样的ViewModel:

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

LoanApplicationViewModel构造函数内部,我接受实体对象并执行相应的映射。问题在于,由于Solicitors集合是一个导航属性,因此每次实例化新的视图模型时都会对数据库进行调用。每个应用程序的平均律师数量是两个,这意味着如果我呈现一个列出最后10个应用程序的表,那么该应用程序将大约18-20次访问数据库。

我认为必须有更好的方法来获取此集合,因此我更改了原始查询以急切加载集合,如下所示:

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

虽然这会将对数据库的调用次数减少到只有一次,但查询速度要慢得多,大约慢50%。

数据库托管在SQL Azure上,我们已经实现了瞬态错误处理,但我希望减少对数据库的调用数量,而不会降低响应时间性能。

这里的最佳做法是什么?

5 个答案:

答案 0 :(得分:14)

“这里的最佳做法是什么?”

最佳做法是

  1. 设置!应用范围广!绩效目标
  2. 个人资料,基准测试和定位瓶颈
  3. 检查并微调瓶颈,为您提供最佳性能,最少的工作。 (根据我的经验,90%的时间不是tsql)
  4. 现在看起来似乎有点无关紧要了,但从这个角度来看,哪种加载模式在你的应用领域中是最佳的,这是正确的方法。

    没有渴望/懒惰的“最佳实践”。这就是为什么两种选择都可用的原因。此外,如果tsql是你的瓶颈,并且渴望/懒惰之间的切换仍未达到你的性能目标,那么你需要在SSMS中查看大量其他工具,例如查询分析器和查询计划分析器。


    对于某些背景:

    我正在谷歌搜索“急切加载慢”并来到这里。这是我的结果:

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

    渴望加载:106毫秒

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

    延迟加载获胜,故事结束。

答案 1 :(得分:4)

除了在使用eager和lazy时产生巨大结果或大量调用的SQL语句之外,通过从结果中放入和映射到ObjectContext / DbContext中会发生巨大的工作。这会导致巨大的性能损失,在检索大量数据时我无法真正推荐任何这些。

最佳解决方案是指定显式选择呼叫。但是,在不知道如何构建viewmodel对象的情况下,给出一个如何执行此操作的示例有点困难。所以,我在这里做的是给你一个使用匿名对象作为查询结果的例子。

此示例为您提供有关联系人所属客户的信息的联系人。

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

此查询将生成一个SQL SELECT,它使用内部联接连接与客户联系,然后仅选择Contact.Id,Contact.Name和Customer.Name列。

如果您不打算使用数据并将更改保存回相同的上下文,则此解决方案是从服务器检索数据的最有效方法。它不会使用急切或延迟加载。

答案 2 :(得分:0)

如果你能以某种方式查询你的律师表并使用你已经获取的应用程序列表过滤查询,那么获取的实体将被缓存在你的上下文中,我相信这将被用于导航属性而不是命中数据库。

我不确定如何编写律师提取查询,但我在想这样的事情

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

答案 3 :(得分:0)

您是否考虑过使用sql视图?

我不太确定Sql Azure。但是在sql server中,在没有正确索引的情况下连接2个表时可能会有性能损失。也许这会发生在您的查询中。

需要注意的是,您之前的查询是使用where子句,2个调用访问1个表。在after查询中,它使用where子句,1个调用访问2个表。在查询后加入,可能需要不同的索引。

您可以创建一个sql视图以确保使用正确的索引。然后让您的应用程序调用视图。存储过程也可以用于此目的,但不太适合这种情况。

答案 4 :(得分:0)

急切加载提取冗余主数据。尽管上下文中的对象图仅存储每个实体的单个主数据,但它会占用大量内存,但SQL会将大量数据转储到其中。 我从here

获取了以下图片

enter image description here

如果你看到,User of Data表也在SQL查询的结果集中重复了UserDetails表的数量。这似乎可以区分性能因素(在您的情况下,主列有更多记录,然后是详细信息表)。

如果性能是您主要关注的问题,我建议您使用 LINQ join 和相同的where子句同时获取详细信息表的数据 所以在你的情况下: -

<强>步骤1

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

然后 的步骤2

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>>

谢谢,你的问题让我回顾了我自己的代码: - )