Secrets of DetectChanges
Part 1: What does DetectChanges do?
Relevance
These posts were written in 2012 for Entity Framework 4.3. However, the information is fundamentally correct for all versions up to and including EF6.
The general concepts are also relevant for EF Core.
As always, use your noggin.
Entity Framework change tracking is often something that doesn't require too much thought from the app developer. However, change tracking and the DetectChanges method are a part of the stack where there is a tension between ease-of-use and performance. For this reason it can be useful to have an idea of what is actually going on such you can make an informed decision on what to do if the default behavior is not right for your app.
Overview
I already blogged about change tracking proxies which touched on some areas of change tracking. In this four part series I'll focus on the DetectChanges method.- Part 1 (this post) provides an introduction and describes briefly what DetectChanges does.
- Part 2 details when DetectChanges is called automatically and what the consequences of these calls are.
- Part 3 shows how to switch off automatic calls to DetectChanges and covers when you must then call it manually.
- Part 4 looks at special considerations for DetectChanges when your entities contain complex types or byte arrays.
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
public class AnotherBlogContext : DbContext
{
public DbSet Blogs { get; set; }
public DbSet Posts { get; set; }
}
The change tracking problem
Most EF applications make use of persistent ignorant POCO entities and snapshot change tracking. This means that there is no code in the entities themselves to keep track of changes or notify the context of changes.Let's look at the following code to illustrate the point:
using (var context = new AnotherBlogContext())
{
var post = context.Posts
.Single(p => p.Title == "My First Post");
post.Title = "My Best Post";
context.SaveChanges();
}
In this code, a single post is queried from the database, its title is changed, and then this change is saved back to the database.
But when Title property is changed nothing special happens because it is just a simple automatic property of the C# class. There is nothing in the entity that keeps track of the fact that it has changed (such as an isDirty flag) or of the fact that the original value of this property was "My First Post". There is also nothing in the entity that notifies the context that this change has happened.
So how does SaveChanges know that it must send an update to the database for the changed title? The answer is that is uses snapshot change tracking and DetectChanges.
Snapshot change tracking and DetectChanges
The EF context makes a snapshot of the properties of each entity when it is queried from the database. So in the example above, the context recorded in a snapshot that the Post entity had a Title property with the value "My First Post".When SaveChanges is called it will in turn automatically call the DetectChanges method. DetectChanges scans all entities in the context and compares the current value of each property to the original value stored in the snapshot. If the property is found to have changed, then EF knows that it must send an update for that property to the database. In the example above, the current Title value of “My Best Post” is detected as different from the original Title value of “My First Post” and an appropriate update is generated.
What else does DetectChanges do?
In reality, DetectChanges does quite a bit more than what is described above. Most of this falls under the category of "fixup" which I hope to describe in more detail in a future post. Briefly, fixup is the process of updating the references between entities and adjusting internal state and indexes appropriately.For example, imagine that in addition to changing the Title property, the foreign key of the Post entity is also changed:
using (var context = new AnotherBlogContext())
{
context.Blogs.Load();
var post = context.Posts
.Single(p => p.Title == "My First Post");
post.Title = "My Best Post";
post.BlogId = 7;
context.SaveChanges();
}
When DetectChanges is called (as part of SaveChanges) it will notice this change to the FK and do a few things:
- It will make sure that the new FK is saved to the database, just like for any property change.
- It will check whether or not a Blog entity with a primary key that matches the new value of the BlogId FK is being tracked by the context and, if it is, it will change the Blog navigation property to point to this entity.
- Since the Blog type contains a Posts inverse navigation property, DetectChanges will also make sure that these collections are updated. That is, the Post will be removed from the Posts collection on the original Blog entity and added to the Posts collection of the Blog with which it is now associated.
- It will update the state of the Blog entity, the state of its properties, and the state of any other affected entities and their properties.
- It will update internal indexes that keep track of dangling foreign keys and conceptually (but not actually) null foreign keys as appropriate.
Isn't all this work very expensive?
If your context is tracking thousands of entities and doing a lot of work with them, then calling DetectChanges frequently can be expensive. If this is a problem for your app then read the upcoming posts to find out what to do about it.That being said, for many applications, even when dealing with lots of entities, DetectChanges does not result in a performance bottleneck. Trying to optimize for DetectChanges when you don't need to can be an excellent way of introducing subtle bugs into your code.
Up next: Secrets* of DetectChanges Part 2: When is DetectChanges called automatically?
*Okay, so these aren't really secrets as such. But “secrets” sounded better than "things that EF geeks might be interested in knowing about". And it's shorter to tweet.