Browsed by
Month: January 2016

Change tracking optimization in Entity Framework

Change tracking optimization in Entity Framework

Entity Framework change tracking optimization

EF_readonly_optimization change tracking metaphor

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/

http://blog.oneunicorn.com/2012/03/11/secrets-of-detectchanges-part-2-when-is-detectchanges-called-automatically/

http://blog.oneunicorn.com/2012/03/12/secrets-of-detectchanges-part-3-switching-off-automatic-detectchanges/

http://blog.oneunicorn.com/2012/03/13/secrets-of-detectchanges-part-4-binary-properties-and-complex-types/

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:

  1. Method from EF cannot change the EF context that DetectChanges method needs to be called.
  2. 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

https://ilmatte.wordpress.com/2013/01/01/entity-framework-code-first-always-disable-autodetectchanges-when-importing-data/

http://rpajak.com/2012/04/

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