DbContext.ChangeTracker.HasChanges is very slow

c#, entity-framework

Solution

In the first code snippet the call chain to HasChanges involves a call to DetectChanges. When using snapshot change tracking DetectChanges goes through all tracked entities to determine if any have changed so that HasChanges will return the correct result.

The second code snippet does not call DetectChanges but instead just asks the state manager about the states it already knows about. So if an entity has been modified but this has not yet been detected, then the second code snippet may return the wrong result.

There are a few ways of handling this, one of which is to use change tracking proxies instead of snapshot change tracking. I wrote a blog series on DetectChanges which describes the various options and tradeoffs in detail: http://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/. I would recommend reading through so that you can make a good choice about what kind of change tracking is best for your application.

Problem

My application uses Entity Framework 6.1.0 and `DbContext` API. It's a some kind of CAD system, and it is intended to edit some engineering documents. To detect the fact of changes in the document, I'm using `DbContext.ChangeTracker.HasChanges`. When document has large amount of data (approximately 20-25 thousands of entities), `DbContext.ChangeTracker.HasChanges` is running very slow. Since this code is used to enable/disable "Save" command, it executes rather frequently from UI thread. This, in turn, hits application performance. I've re-written this fragment: ``` private Lazy<DbContext> context; public bool HasChanges { get { if (!context.IsValueCreated) { return false; } return context.Value.ChangeTracker.HasChanges(); } } ``` to this one: ``` 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(); } } ``` and (it's a miracle!) everything works extremely fast. Looks like `DbChangeTracker.HasChanges` implementation isn't optimal. Am I missing something?

Original source