DbContext.ChangeTracker.HasChanges is very slow

本文关键字:very slow is HasChanges ChangeTracker DbContext | 更新日期: 2023-09-27 18:06:37

我的应用程序使用实体框架6.1.0和DbContext API。

这是一种CAD系统,用于编辑一些工程文档。为了检测文档中的更改,我使用DbContext.ChangeTracker.HasChanges

当文档有大量数据(大约20-25个实体)时,DbContext.ChangeTracker.HasChanges运行非常慢。由于此代码用于启用/禁用"保存"命令,因此它经常从UI线程执行。这反过来又会影响应用程序的性能。

我已经重写了这个片段:

    private Lazy<DbContext> context;
    public bool HasChanges
    {
        get
        {
            if (!context.IsValueCreated)
            {
                return false;
            }
            return context.Value.ChangeTracker.HasChanges();
        }
    }

:

    public bool HasChanges
    {
        get
        {
            if (!context.IsValueCreated)
            {
                return false;
            }
            var objectStateManager = ((IObjectContextAdapter)context.Value).ObjectContext.ObjectStateManager;
            return
                objectStateManager.GetObjectStateEntries(EntityState.Added).Any() ||
                objectStateManager.GetObjectStateEntries(EntityState.Deleted).Any() ||
                objectStateManager.GetObjectStateEntries(EntityState.Modified).Any();
        }
    }

和(这是一个奇迹!)一切都运行得非常快。

看起来DbChangeTracker.HasChanges的实现不是最优的。我错过什么了吗?

DbContext.ChangeTracker.HasChanges is very slow

在第一个代码片段中,对HasChanges的调用链涉及到对DetectChanges的调用。当使用快照更改跟踪时,DetectChanges将遍历所有被跟踪的实体,以确定是否有任何更改,以便HasChanges将返回正确的结果。

第二个代码片段没有调用DetectChanges,而只是询问状态管理器它已经知道的状态。因此,如果实体已被修改,但尚未检测到,则第二个代码片段可能返回错误的结果。

有几种方法可以处理这个问题,其中之一是使用变更跟踪代理而不是快照变更跟踪。我在DetectChanges上写了一个博客系列,详细描述了各种选项和权衡:http://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/。我建议你通读一遍,这样你就可以很好地选择哪种类型的更改跟踪最适合你的应用程序。

@Dennis,检测更改枚举所有附加项。这意味着如果我们添加1000个项目,我们添加的第一个项目不枚举任何项目,第二个项目枚举1个项目,以此类推。因此,如果我们对此进行计算,我们得到如下

 1 + 2 + 3 + ... + 999 + 1000
or:
N(N+1)/2

在你的例子中N大约是25000;想象一下总的输出和。这个函数属于O(N^2)类,也就是说,大O符号是N的平方的复杂度,这就解释了为什么添加大量项需要这么长时间。

如下代码将25,000个实体划分为3类,即添加、删除和;修改后的,它们的总和仍然是25,000,与之前返回N(N+1)/2的1个LOC相反。因此工作效率比较。

objectStateManager.GetObjectStateEntries(EntityState.Added).Any() ||
                objectStateManager.GetObjectStateEntries(EntityState.Deleted).Any() ||
                objectStateManager.GetObjectStateEntries(EntityState.Modified).Any()