题 为什么DbSet.Add工作得如此之慢?


8个月前在这里讨论了同样的话题: 如何加速DbSet.Add()?。除了使用我们不接受的SqlBulkCopy之外,没有提出任何解决方案。我决定再次提出这个问题,希望围绕这个问题有新的想法和想法,并提出其他解决方法。至少我只是好奇为什么这个操作需要很长时间才能运行。

嗯,问题是:我必须将30K实体更新到数据库(EF 4.1,POCO)。实体类型非常简单,包含整数Id +其他4个整数属性,与其他类型无关。 2例:

  • 所有这些都是新记录。每个实体逐个运行context.Entities.Add(entity)需要90秒,Cntx.Configuration.AutoDetectChangesEnabled = false(true值使其永久运行)。然后SaveChanges只需要一秒钟。其他方法:将它附加到像这样的上下文需要相同的90秒:

    Cntx.Entities.Attach(entity);
    Cntx.Entry(entity).State = EntityState.Added;
    
  • 它们都是现有的记录,有一些变化。在这种情况下,将它附加到现有数据上下文只需几毫秒,如下所示:

    Cntx.Entities.Attach(entity);
    Cntx.Entry(entity).State = EntityState.Modified;
    

    看到不同?

Add方法的背后是什么让它的工作变得非常慢?


31
2017-08-13 00:21


起源


请将您的更新作为答案。 - Ladislav Mrnka
我尝试了,但我不能这样做,需要100个声望点(9个以上)才能做出自我回答。 - YMC
Upvoted你的问题,现在你有101代表:) - Slauma
谢谢:)我把它作为答案 - YMC
你试过吗? Cntx.Configuration.AutoDetectChangesEnabled = false; 鲁迪的方法? - Nenotlep


答案:


我有一些有趣的性能测试结果,我发现了一个罪魁祸首。在我读过的任何EF源代码中,我都没有看到任何类似的信息。

事实证明,在基类中重写了Equals。基类应该包含在所有类型的具体实体之间共享的Id属性。许多EF书籍推荐这种方法并且非常了解。你可以在这里找到它,例如: 如何最好地实现自定义类型的等于?

更确切地说,性能被取消装箱操作(对象到具体类型转换)所杀死,这使得它的工作速度很慢。当我评论这行代码时,它需要3秒才能反对90秒!

public override bool Equals ( object obj )
{
    // This line of code made the code so slow 
    var entityBase = obj as EntityBase;
    ...
}

当我发现它时,我开始考虑可能是这个Equals的替代方案。第一个想法是为EntityBase实现IEquatable,但它根本没有运行。所以我最终决定要为我的模型中的每个具体实体类实现IEquatable。我只有少数,所以这对我来说是一个小小的更新。您可以将整个Equal操作功能(通常是2个对象ID比较)放入扩展方法,以在具体实体类之间共享并运行它:Equal((EntityBase)ConcreteEntityClass)。最有趣的是,这个IEquatable加速了EntitySet.Add 6次!

所以我没有更多的性能问题,相同的代码运行不到一秒钟。 我获得了180倍的性能提升!惊人!

结论

  1. 运行EntitySet.Add的最快方法是为特定实体提供IEquatable(0.5秒)
  2. 缺少IEquatable使其运行3秒。
  3. 拥有大多数消息来源推荐的Equals(object obj)使其运行90秒

27
2017-08-13 18:36



@YMC:你能详细说明如何实施 IEquatable<T>?我很困惑是否为了一个可变类型覆盖object.Equals()(因为你当然也应该覆盖object.GetHashCode(),但是如果哈希代码在对象在字典中时发生变化,它就会变成孤立的)。在持久化对象后,我可以使用数据库中的主键,但在持久化之前,所有新对象的键都为0.我将此作为一个单独的问题提出,但尚未得到好的响应 stackoverflow.com/questions/9782235/... - Eric J.
@YMC我也希望你详细说明 IEquatable<T> 实现。我遇到了同样的问题。 - vlad
vlad和Eric,我现在还没有访问代码,但我认为你可以使用至少两种方法:1)Guid生成主键值和GetHashCode()实现可能就像 return Id.GetHashCode() 2)如果是整数Id,它可能看起来像这样 IsNew ? base.GetHashCode() : Id.GetHashCode(),如果Id == 0,则IsNew返回true。如果有任何错误,请纠正我 - YMC
哇...这里发生了奇怪的事情...我的代码从10秒变为0.1 ... cheesus - Mark Segal
我试过这个(实施了 IEquatable<T>)它对代码的速度没有影响。另外,你谈谈 拆箱 但是你引用的代码中没有取消装箱。在你的结论清单中,选项#2并不清楚:它只说明它是什么 失踪 但不是它 是。也许一些关于做什么的明确指示会很有用。 - Timwi