为啥 DbSet.Add 工作这么慢?

Posted

技术标签:

【中文标题】为啥 DbSet.Add 工作这么慢?【英文标题】:Why does DbSet.Add work so slow?为什么 DbSet.Add 工作这么慢? 【发布时间】:2011-10-26 05:38:04 【问题描述】:

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

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

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

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

所有这些都是现有的记录,但有一些变化。在这种情况下,只需几毫秒即可将其附加到现有数据上下文,如下所示:

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

看到区别了吗?

Add 方法的幕后原因是什么让它运行得如此缓慢?

【问题讨论】:

请将您的更新作为答案。 我试过了,但我做不到,需要 100 个声望点(另外 9 个)才能做出自我回答。 赞成你的问题,现在你有 101 个代表 :) 谢谢 :) 我已经把它作为答案 你试过rudi的Cntx.Configuration.AutoDetectChangesEnabled = false;方法了吗? 【参考方案1】:

我得到了有趣的性能测试结果,我找到了罪魁祸首。在我读过的任何 EF 资源中,我都没有看到任何类似的信息。

原来是在基类中重写了 Equals。应该包含在所有类型的具体实体之间共享的 Id 属性的基类。这种方法被许多 EF 书籍推荐并且非常熟悉。您可以在这里找到它,例如:How to best implement Equals for custom types?

更准确地说,拆箱操作(对象到具体类型的转换)会导致性能下降,这使得它工作得如此缓慢。当我评论这行代码时,它需要 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 倍的性能提升!太棒了!

结论

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

【讨论】:

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

以上是关于为啥 DbSet.Add 工作这么慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 psycopg2 对我这么慢?

为啥这个 F# 代码这么慢?

为啥我的 MongoDB 聚合查询这么慢

Serializable 是如何工作的,为啥使用起来比 Parcelable 慢? [复制]

为啥 byte 的工作速度比 int 慢?类实例化

为啥火花中的一些音符工作得非常慢?为啥在同一情况下多次执行有不同的执行时间?