Categories
Computers and Internet

Entity Framework Performance Tip for Creating Entities

This tip is applicable if you’re using Entity Framework Code First with dynamic proxies and you have a lot of objects attached to your context, for whatever reason (e.g. within a batch job).

The first thing to note is that if you have a lot of objects attached to your context you want to avoid DetectChanges being called on the context unless absolutely necessary. DetectChanges compares the original to the current state of each object and uses this information for a couple of purposes: Marking entities as added/changed/deleted and fixing up relationships such as bi-directional navigation properties and foreign key columns.

Arthur Vickers has an excellent blog series explaining this all very well: http://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/

DetectChanges is obviously necessary when SaveChanges is called, but it’s also called whenever one of these operations is called:

  • DbSet.Find
  • DbSet.Local
  • DbSet.Remove
  • DbSet.Add
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries

DetectChanges calls can be avoided, though, by turning AutoDetectChanges off. Check out this gist:

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);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
public void Dispose()
{
SetChangeDetection(_initialAutoDetectChangesValue);
}
private void SetChangeDetection(bool setting)
{
_dbContext.Configuration.AutoDetectChangesEnabled = setting;
}
}

With this class you can write code such as:


using(new NoChangeTracking(context))
{
  context.MyEntities.Add(new MyEntity());
}

… and DetectChanges will not be called. (You could even just turn off automatic detect changes globally, but you would at least need to remember to call DetectChanges manually before SaveChanges was called).

This technique works okay, but it can result in problems if you are relying on two way navigation properties. For example:


var parent = new Parent();
var child = new Child();
parent.Children.Add(child);
using(new NoChangeTracking(context))
{
  context.Parents.Add(parent);
}
Debug.Write(child.Parent.Id); // Null reference exception

The child.Parent navigation property will not have been set as we set AutoDetectChangesEnabled to false before we performed the DbSet.Add. We could choose not to turn it off, but that would lead again to the performance issues. We could also explicitly alter both the parent and child navigation properties each time we change one end, but that’s extra code and it’s easy to forget to do.

With dynamic proxies enabled, there’s an easier way. Instead of creating the entities by using the new operator, you create a dynamic proxy by using the DbSet.Create method. This dynamic proxy contains code to intercept alterations to each navigation property and ensure that any reciprocal navigation property on the target object is updated. E.g. when parent.Children.Add(child) is called, the child.Parent property is automatically populated.

Here’s that code again but with the correct proxy initialization:


var parent = context.Parents.Create();
var child = context.Children.Create();
parent.Children.Add(child);
using(new NoChangeTracking(context))
{
  context.Parents.Add(parent);
}
Debug.Write(child.Parent.Id); // No null reference!

That’s it. There are many other performance considerations, but combining switching off AutoDetectChangedEnabled with properly using dynamic proxies can get us a long way.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s