【问题标题】:EntityFramework : Calling ToList() on IQueryable with ~11.000 records takes 10 secondsEntityFramework:在 IQueryable 上调用 ToList() 约 11.000 条记录需要 10 秒
【发布时间】:2012-04-04 10:52:46
【问题描述】:

我想通过 EntityFramework 4 通过 WCF 服务从 SQL Express 2008 R2 服务器返回相对大量的记录到 WCF 客户端。我的测试表目前包含大约 11.000 条记录。 LINQ 查询就这么简单:

Database DB = new Database(); // create object context
var retValue = DB.Entities.Persons
        .Include("District")
        .Include("District.City")
        .Include("District.City.State")
        .Include("Nationality")

return retValue.ToList();

这需要大约 10 秒才能完成。

在 SQL Server Managament Studio 中执行相同的 SELECT 查询不到 1 秒。

EF 一定要那么慢吗?

【问题讨论】:

  • 这段代码运行时,除了 SQL Server 正在执行的查询之外,还会发生什么?
  • @AakashM 你是什么意思?这只是在那一刻发生的一个陈述。目前启用了 WCF 跟踪,但我怀疑它是否会增加这 9 秒。
  • 对,我要求您考虑一下这段代码运行时实际发生的情况,除了 SQL Server 正在执行的查询。
  • @AakashM 刚试过不带WCF跟踪,时间上没有区别。
  • 您是否使用 SQL Profiler 查看实际运行了多少 SQL 查询?我敢打赌,您会看到很多查询在您的数据库中触发。

标签: entity-framework linq sql-server-2008-r2 entity-framework-4


【解决方案1】:

您的查询并不简单,它包含很多连接(由于Includes),更重要的是它可能返回大量重复数据,特别是如果包含的导航属性是集合:https://stackoverflow.com/a/5522195/270591

当数据库的结果返回到实体框架上下文时,时间消耗部分是对象实现并将实体附加到上下文。

您的测量结果(在您问题的 cmets 中)证实了这一点,即同一上下文中的第二个查询非常快。在这种情况下,EF 将对数据库执行查询,但不需要再次具体化对象,因为它们仍附加到上下文。

如果您在第二个上下文中运行第二个查询,则生成的实体必须附加到新上下文 - 这一步又很慢(您的测量也证实了这一点)。

这可能是使用 EF 的查询 实际上很慢,并且与原始 SQL 查询相比增加了很多开销。 EF 需要创建许多数据结构,为更改跟踪和管理上下文中的对象身份做准备,这会消耗额外的时间。

我认为提高性能的唯一方法是禁用更改跟踪(假设您的操作不需要它)。在 EF 4.0 / ObjectContext 中它将是:

Database DB = new Database();
DB.Entities.Persons.MergeOption = MergeOption.NoTracking;
// MergeOption is in System.Data.Objects namespace

使用这种方法时,必须注意,即使相关对象具有相同的键,它们也会被创建为单独的对象 - 启用更改跟踪的情况并非如此,因为附加到上下文将避免这种重复。

因此,可能会有更多的对象被加载到内存中。如果这会适得其反并且实际上会进一步降低性能,或者它是否仍然表现更好,则需要进行测试。

【讨论】:

  • 这也没有那么复杂,因为所有Includes 引用的导航属性都是多对一的关系。所以行中不会有重复数据,列中只有 NULL 值。虽然您的答案看起来会成功,但不幸的是,在应用 NoTracking 后,查询持续了 1 分钟以上。(!)服务超时,因为超时设置为 00:01:00
  • @DejanCG:我几乎认为(因为他的名字)导航属性只是单个引用。我担心 NoTracking 可能会更慢,因为我猜,国籍、城市等通常是相同的,所以你有很多重复的对象。目前我不知道了。
  • @DejanCG:只是为了确保它按我的预期工作:你可以在查询(使用 MergeOption NoTracking)执行后(.ToList 之后)调用以下内容:DB.Entities.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged).Count() 并检查如果结果是0?另一个问题:您使用的是 POCO 还是 EntityObject 派生实体?
  • 感谢您帮助我。返回的计数为0。最初我使用EntityObjects,它必须转换为我自己的 POCO,然后才能发送回客户端,但是当我遇到大量记录时,它已被证明是一个低效的解决方案。我不得不从IQueryable 调用ToList,然后使用foreach 循环将其一一转换为我的POCO,最后将其全部发送给客户端,但已序列化。我不知道哪个部分更慢。现在我使用 EF 4.x POCO Generator 生成的 POCO。
  • 顺便说一句,您是对的,我将答案标记为已接受。在我们添加 NoTracking 选项后,我没有在 WCF 端进行测量,而是在客户端进行测量。 NoTracking 正如预期的那样创建了更多的对象,因此序列化必须慢很多。查询本身在 2 秒内完成。现在我要提出一个关于那个让我发疯的该死的序列化的新问题。谢谢!
【解决方案2】:

这很可能是因为与查询执行相比,EF 中的查询编译(包含大量包含的 LINQ 查询 -> 要使用的 SQL)非常慢。您可以通过 CPU 分析您的代码来验证这是否是问题所在。考虑使用更少的包含 + 多个更小的查询,使用已编译的查询或升级到最新的 EF5 测试版。

【讨论】:

  • 谢谢,我选择的解决方案是创建自定义 ViewModel,而不是返回整个结果集。它的工作速度要快得多,这 11.000 条记录的整个过程(查询、ToList、序列化、反序列化、转换为 DataTable)只需不到 1 秒的时间。关于您的 EF5 建议,我倾向于不使用任何“测试版”,此外,唯一的性能改进是缓存已编译的查询。
猜你喜欢
  • 2013-05-25
  • 2023-04-05
  • 2017-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多