为什么在EF4.1中插入实体比ObjectContext慢?

83

基本上,我在一个事务中插入了35000个对象:

using(var uow = new MyContext()){
  for(int i = 1; i < 35000; i++) {
     var o = new MyObject()...;
     uow.MySet.Add(o);
  }
  uow.SaveChanges();
}

这太慢了!如果我使用底层的ObjectContex(通过使用IObjectAdapter),它仍然很慢,但大约需要20秒。看起来DbSet<>正在执行一些线性搜索,这需要平方级别的时间...

还有其他人遇到这个问题吗?


3
我相信答案会类似于这个链接中的内容:https://dev59.com/TG025IYBdhLWcg3wl3Em#5921259 - Ladislav Mrnka
4个回答

132

正如Ladislav在评论中所指出的,为了提高性能,您需要禁用自动变更检测:

context.Configuration.AutoDetectChangesEnabled = false;

DbContext API 中,默认启用了此更改检测。

DbContextObjectContext API 的区别在于,当自动更改检测启用时,DbContext API 的许多函数会比 ObjectContext API 的函数更频繁地内部调用 DetectChanges 函数。

这里 可以找到默认调用 DetectChanges 的那些函数列表。它们是:

  • DbSet 上的 AddAttachFindLocalRemove 成员
  • DbContext 上的 GetValidationErrorsEntrySaveChanges 成员
  • DbChangeTracker 上的 Entries 方法

特别是 Add 调用 DetectChanges,这就是您遇到性能问题的原因。

相比之下,ObjectContext API 仅在 SaveChanges 中才自动调用 DetectChanges,而在 AddObject 和上述其他对应方法中则不会调用。这就是为什么 ObjectContext 的默认性能更快的原因。

他们为什么要在 DbContext 中引入此默认自动更改检测呢?我不确定,但似乎禁用它并在正确的时机手动调用 DetectChanges 被认为是高级技巧,可能会在您的应用程序中引入微妙的错误,因此请谨慎使用


@Ladislav:你说得对,我没有找到这个,因为我只搜索了插入问题 :-( - Hartmut
3
@Hartmut: 你可以在你派生的DbContext的构造函数内禁用更改检测,这样它就一直被禁用。但是个人认为,当它被禁用时,“可能会引入微妙的错误”这句话有点让人紧张。我默认开启更改检测,只在像你的代码块中明显需要提高性能且我感到不会出问题的情况下禁用它。 - Slauma
我同意,我只是在测试我的应用程序的一些性能关键部分。在生产代码中,最好将其限制在像批量插入等情况下使用。 - Hartmut
这里是链接的新副本:http://msdn.microsoft.com/zh-cn/data/jj556205.aspx - Casey
@emodendroket:太好了,谢谢!我已经将链接添加到答案中。 - Slauma
显示剩余4条评论

12

使用 EF 4.3 CodeFirst 进行小型经验测试:

使用 AutoDetectChanges = true 删除 1000 个对象:23 秒

使用 AutoDetectChanges = false 删除 1000 个对象:11 秒

使用 AutoDetectChanges = true 插入 1000 个对象:21 秒

使用 AutoDetectChanges = false 插入 1000 个对象:13 秒


1
谢谢Zax。根据问题,您使用35,000的结果如何?您会发现在原始问题中它指出性能呈二次下降。 - Daniel Dyson

12

在 .netcore 2.0 中,此功能被移动到:

context.ChangeTracker.AutoDetectChangesEnabled = false;


1
除了你在这里找到的答案之外,了解到在数据库层面上插入比添加更需要工作是很重要的。数据库必须扩展/分配新空间。然后至少更新主键索引。尽管在更新时可能还会更新索引,但这种情况较少见。如果有任何外键,它还必须读取这些索引以确保维护引用完整性。触发器也可能起到作用,尽管它们可能会对更新产生同样的影响。
所有这些数据库工作在日常用户输入产生的插入活动中都是有意义的。但是,如果您只是上传现有数据库或有一个生成大量插入的过程,则可能希望考虑推迟到最后加速该过程。通常在插入时禁用索引是一种常见的方法。根据情况可以进行非常复杂的优化,这可能有点令人不知所措。
只需知道,通常插入操作所需时间比更新操作长。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接