Secrets of DetectChanges Part 2: When is DetectChanges called automatically?

Part 1 of this series on DetectChanges described why DetectChanges is needed to discover the changes that have been made to POCO entities. This part will expand on that information and look at when it is important for the context to know about these changes. This will provide the basis for detailing when the context calls DetectChanges automatically.

When does the context need to know?

SaveChanges is the most important time at which the context needs to know what changes have been made to entities. This is somewhat obvious; if the changes are not known, then SaveChanges will not know which inserts/updates/deletes to send to the database. This is why DetectChanges is always called as part of SaveChanges (unless this has been explicitly disabled) even when using ObjectContext in EF4.

However, the context also needs to know about changes at other times. For example, if you ask the context for the state of an entity, then the context needs to know if any properties of an entity have changed in order to report that the entity is either Unchanged or Modified. Consider this code:

using (var context = new AnotherBlogContext())
{
    var post = context.Posts
        .Single(p => p.Title == "My First Post");
    post.Title = "My Best Post";

    Console.WriteLine(context.Entry(post).State);
}

The post entity has been changed, so it would be reasonable for the output to be “Modified”…and indeed it is. The reason it is Modified and not Unchanged is that the Entry method calls DetectChanges.

Fixup, which was covered in the context of DetectChanges in part 1, also needs to know about changes. Fixup can happen at various times—I’ll blog about this sometime. One time it can happen is when an entity is brought into the context with Find. For example:

using (var context = new AnotherBlogContext())
{
    var post = context.Posts.First(p => p.BlogId == 1);
    post.BlogId = 2;

    var blog2 = context.Blogs.Find(2);

    Assert.Same(blog2, post.Blog);
    Assert.Contains(post, blog2.Posts);
}

This code queries for a Post with an BlogId FK of 1. It then changes the FK to 2. Next Find is used to find the Blog entity with primary key 2. EF will attempt to do fixup when this Post entity is brought into the context. This will only be possible if EF knows that the FK of the post is now 2, and this can only happen if DetectChanges has been called. Thankfully, Find calls DetectChanges and so fixup happens correctly and the Asserts pass.

The methods that call DetectChanges

From the examples above it is clear that DetectChanges often needs to be called for the methods of DbContext and its associated classes to behave as expected. This is why DetectChanges is called automatically by each of these methods:

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

Special considerations when overriding SaveChanges or ValidateEntity

DetectChanges is called as part of the implementation of the SaveChanges. This means that if you override SaveChanges in your context, then DetectChanges will not have been called before your SaveChanges method is called. This can sometimes catch people out, especially when checking if an entity has been modified or not since its state may not be set to Modified until DetectChanges is called. Luckily this happens a lot less with the DbContext SaveChanges than it used to with ObjectContext SaveChanges because the Entry and Entries methods that are used to access entity state automatically call DetectChanges.

ValidateEntity is used for performing custom validation of an entity during GetValidationErrors or SaveChanges. Unlike SaveChanges, ValidateEntity is called after DetectChanges has been called. This is because validation needs to be done on what is going to be saved, and this is only known after DetectChanges has been called. Usually code in ValidateEntity will not modify the property values of the entity, just validate them. However, if it does change a property value then it may need to call DetectChanges again manually for that change to be saved correctly. An alternative to this is to modify the property in a way that DetectChanges is not needed—the way to do this is shown in Part 3 of this series.

So why don’t all context methods call DetectChanges?

Calling DetectChanges any time that something in a POCO entity might have changed turns out to be prohibitively expensive. For example, when executing a query, control returns to the application code after each entity is materialized—in other words, many, many times in a single query. If the application code changes something while processing the entity, then it could theoretically affect the behavior for fixing up the next entity, and so on.

I’m not going to write code that demonstrates this because it gets fairly convoluted. The bottom line is that if DetectChanges was called automatically any time that it could theoretically affect behavior, then running this code with 100 Posts in the database would result in EF calling DetectChanges at least 100 times:

using (var context = new AnotherBlogContext())
{
    foreach (var post in context.Posts)
    {
        Console.WriteLine(post.Title);
    }
}

So calling DetectChanges at every possible opportunity is prohibitively expensive, but never calling it automatically results in unexpected and unintuitive behavior in common scenarios. This is why DetectChanges is called automatically in the common places it is needed, but not everywhere it might possibly be needed.

Summary

The DbContext API calls DetectChanges automatically in the most common and useful places where it might be needed without calling it so frequently as to be a hindrance. In Part 3 we’ll look at how to switch off these automatic calls to DetectChanges and how you can then know whether or not you need to call it manually.

Advertisements

11 comments on “Secrets of DetectChanges Part 2: When is DetectChanges called automatically?

  1. James^Portelli says:

    I was having a performance issue with querying POCO classes from Local. Snapshot change tracking was in force. I was surprised DetectChanges was being called during a simple LINQ query on Local.

    This example returns a list of clientIDs that were added on a particular day:
    context.Clients.Load();
    …………………
    var newClientList = (from c in context.Clients.Local
    where (c.DateAdded.HasValue) && (c.DateAdded.Value == filterDate.Date)
    select c.ClientID).ToList();

    When I set context.Configuration.AutoDetectChangesEnabled = false; the performance issue was resolved. Why was DetectChanges being called during a query operation ????

  2. @James^Portelli The Local property returns objects that are currently tracked by the context, including those that have not yet been saved to the database–i.e. those in the Added state. This is important for data binding to work correctly, which is one of the fundamental scenarios in which Local is used. DetectChanges is called so that changes to the graph of entities (including adding new objects) are detected and Local will therefore return the most recent data–it won’t be missing some entities like might be the case if DetectChanges was not called.

    • James Portelli says:

      Is DetectChanges called once or several times during this LINQ Query ? I currently have 3000 fields in this particular table, but eventually it might go up to several thousands, so I guess the performance hit will be evem greater. It’s funny how books describe the use of Local as a means of “caching data”. Cached data in memory should perform faster, not slower.
      If I do this….
      1) try… context.Configuration.AutoDetectChangesEnabled = false;
      2) Call
      3) Finally context.Configuration.AutoDetectChangesEnabled = true;
      do I still have to call DetectChanges manually before the query to get the right results ? Thanks

      • @James DetectChanges is not really called as part of the LINQ query itself; it is called once when accessing the Local property.

        With regard to whether or not you will get the correct results if DetectChanges is not called, that depends on the type of entities you are using and/or the state of your object graph. if your graph contains only change tracking proxies, then DetectChanges should not be needed. But change tracking proxies have some other issues as described elsewhere in this series and on this blog.

        If you are using normal POCO entities then you will need to call DetectChanges only if the graph of entities has been modified (not using the EF APIs) since the last time DetectChanges was called. I could go into more details, but then I would be re-writing this series in a comment to this series, which doesn’t seem productive. :-)

        Thanks,
        Arthur

      • James Portelli says:

        Thanks Arthur. Now I can understand DetectChanges better thanks to you.

  3. codezyc says:

    Is The methods that call DetectChanges section same in the EF5 and EF6?

  4. Asaf says:

    Regarding the performance….if I’m not misunderstanding – it is better practice to accumulate the changes and call the SaveChanges once instead of calling it after every update (since the DetectChanges is called on every SaveChanges call).

    For instance this code shall run faster if I take the SaveChanges call out of the loop.:
    foreach (UploadedProduct p in model)
    {
    var query = db.productsV2.Where(x => x.itemID == p.itemID);
    if (query.Count() == 1)
    {
    query.First().lotNum = p.quantity;
    }
    db.SaveChanges();
    }

    • @Asaf You should treat DbContext as a unit-of-work and so accumulate all changes for the given logical unit of work and then call SaveChanges. Note that the code you wrote will run two database queries every time through the loop, which is also not optimal.

  5. Hey, if I call DbContext.Entry(entity) for the sole purpose of accessing the entries’ “Collection” and/or “Reference”-Methods using a navprop name or expression, with the goal of building a Query dynamically, like this: var foo = ctx.Entry(entity).Collection(navprop).Query(); var bar = foo.Where(…).Select(…). … .ToList(); – but this query is always read-only. Is it then safe to always disable the automatic DetectChanges call for .Entry(…)?

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s