Change tracking optimization in Entity Framework
Entity Framework change tracking optimization
Data changes tracking
Entity Framework gives us an opportunity to work with data without bothering to notify database about entity changes made during data processing. EF provides few methods of tracking this modifications.
Snapshot change tracking
By default in Entity Framework it is enabled snapshot change tracking mechanism. It works by saving entity states each time when it is loaded from the database. When for example SaveChanges
method is called, Entity Framework scans all entities in current context and compares them with saved state.
Entity Framework performs Detect Changes automatically when the following methods are called:
- DbSet.Find
- DbSet.Local
- DbSet.Remove
- DbSet.Add
- DbSet.Attach
- DbContext.SaveChanges
- DbContext.GetValidationErrors
- DbContext.Entry
- DbChangeTracker.Entries
Change tracking mechanism is wider described in following resources:
https://msdn.microsoft.com/en-us/data/jj556205.aspx
http://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/
Disabling AutoDetectChanges
If property AutoDetectChangesEnabled
in DbContext.Configuration
is set, EF runs change tracking according to rules described above. So you can disable it by setting this property to false. But you should remember to reset this value to previous one after making your special operations. In general case this mechanism is very useful, especially to modify multiple objects. However, it is not necessary for read-only queries or bulk insert operations.
Rules to safe disabling
Each time when you want to disable it, you should check if your code operating on data meets two following conditions:
- Method from EF cannot change the EF context that
DetectChanges
method needs to be called. - Each time when entities was changed outside of EF methods and this modifications should be saved,
DetectCanges
method should be executed manually.
For more information
Disable tracking for read-only
The first possibility to optimize time of database queries is to disable this tracking during read-only operations. In case on WebAPI it could means, that each action that doesn’t modify any data need not change tracking mechanism. It leads us to create custom attribute to disable this mechanism per HTTP request.
In the example below I use static IoC.Container
to get instance of DbContext
. You also have to remember to configure Dependency Injector container to create context in request scope, not in transient scope.
In my experiment on working system, method is executed around 20% faster than before adding this attribute to them.
public class ReadonlyAttribute : ActionFilterAttribute { private IDbContext _dbContext; public override void OnActionExecuting(HttpActionContext actionContext) { _dbContext = IoC.Container.GetInstance<IDbContext>(); _dbContext.Configuration.AutoDetectChangesEnabled = false; base.OnActionExecuting(actionContext); } public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { _dbContext = IoC.Container.GetInstance<IDbContext>(); _dbContext.Configuration.AutoDetectChangesEnabled = true; base.OnActionExecuted(actionExecutedContext); } }
You can use it just by adding this attribute to chosen methods:
[HttpGet] [Readonly] public IQueryable<PostDto> Get() { return _postRepository.All(); }
Bulk inserts
The other case when you could want to disable tracking is bulk inserts. In this case it is not necessary to disable this functionality for whole action, but you can do it for particular operation:
using (var context = new DbContext()) { try { context.Configuration.AutoDetectChangesEnabled = false; posts.ForEach(p => context.Posts.Add(p)); } finally { context.Configuration.AutoDetectChangesEnabled = true; } context.SaveChanges(); }
Additionally, I really like the solution described in post http://joshgallagher.info/2014/06/14/entity-framework-performance-tip-for-creating-entities/
It enables to encapsulate this operation in single and simple object:
public sealed class NoChangeTracking : IDisposable { private readonly DbContext _dbContext; private readonly bool _initialAutoDetectChangesValue; public NoChangeTracking(DbContext dbContext) { if (dbContext == null) throw new ArgumentNullException("dbContext"); _dbContext = dbContext; _initialAutoDetectChangesValue = dbContext.Configuration.AutoDetectChangesEnabled; SetChangeDetection(false); } public void Dispose() { SetChangeDetection(_initialAutoDetectChangesValue); } private void SetChangeDetection(bool setting) { _dbContext.Configuration.AutoDetectChangesEnabled = setting; } }
Then you can use this object simply
using(new NoChangeTracking(context)) { context.Posts.Add(new Post()); }
Disabling for single entity
In some cases you can disable tracking for single entity source. It could be useful for dictionaries types which are not changed in normal code.
var entities = context.PostCategory.AsNoTracking();
http://gasior.net.pl/quick-tip-12-stosuj-asnotracking-gdzie-sie-da/
http://blog.staticvoid.co.nz/2012/4/2/entity_framework_and_asnotracking