Secrets of DetectChanges

Part 3: Switching off automatic DetectChanges

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.


In parts 1 and 2 of this series we looked at what DetectChanges does and why the context calls DetectChanges automatically. In this part we'll look at how automatic calls to DetectChanges can be switched off and what you then need to do differently in your code.

Turning off automatic DetectChanges

If your context is not tracking a large number of entities you pretty much never need to switch off automatic DetectChanges. This holds true for a great many apps, especially when the app makes use of the best practice of having a short-lived context—for example, a context-per-request in a web app.

If your context is tracking thousands of entities you often still don't need to turn off automatic DetectChanges as long as the app doesn't call methods that use DetectChanges many, many times. The time you might want to switch off automatic DetectChanges is if your app is tracking many entities and repeatedly calling one of the methods that calls DetectChanges. The usual example of this is looping through a collection of entities and calling Add or Attach for each. For example:

public void AddPosts(List posts)
{
    using (var context = new AnotherBlogContext())
    {
        posts.ForEach(p => context.Posts.Add(p));
        context.SaveChanges();
    }
}

In this example every call to Add results in a call to DetectChanges. This makes the operation O(n2) where n is the number of posts in the list. If n is large then this operation obviously gets very expensive. To avoid this you can turn off automatic DetectChanges just for the duration of the potentially expensive operation:

public void AddPosts(List posts)
{
    using (var context = new AnotherBlogContext())
    {
        try
        {
            context.Configuration.AutoDetectChangesEnabled = false;
            posts.ForEach(p => context.Posts.Add(p));
        }
        finally
        {
            context.Configuration.AutoDetectChangesEnabled = true;
        }
        context.SaveChanges();
    }
}

Using try/finally ensures that automatic DetectChanges is always switched back on even if an exception is thrown when looping through the entities and calling Add.

Rules for when DetectChanges is needed

So how do you know that it's okay for DetectChanges not to be called in the loop above? Taking this further, if you turn DetectChanges off for a longer period, then how do you know if this is okay or not?

The answer is that there are two rules that EF adheres to:

  1. No call to EF code will leave the context in a state where DetectChanges needs to be called if it didn't need to be called before.
  2. Any time that non-EF code changes any property value of an entity or complex object then DetectChanges may need to be called.
So the calls to Add above will not result in DetectChanges needing to be called (rule 1) and since the code also doesn't make any changes to the post entities (rule 2) the end result is that DetectChanges is not needed. In fact, this means that in this case the SaveChanges call could also safely been performed before automatic DetectChanges is switched back on.

Making effective use of Rule 1

If the code makes change changes to the properties of the entities instead of just calling Add or Attach, then, by Rule 2, DetectChanges will need to be called, at least as part of SaveChanges and possibly also before then.

However, this can be avoided by taking notice of Rule 1. Rule 1 can be very powerful when used in conjunction with the DbContext property APIs. For example, if you want to set the value of a property but need to do it without requiring a call to DetectChanges you can use the property API to do this. For example:

public void AttachAndMovePosts(Blog efBlog, List posts)
{
    using (var context = new AnotherBlogContext())
    {
        try
        {
            context.Configuration.AutoDetectChangesEnabled = false;

            context.Blogs.Attach(efBlog);

            posts.ForEach(
                p =>
                {
                    context.Posts.Attach(p);
                    if (p.Title.StartsWith("Entity Framework:"))
                    {
                        context.Entry(p)
                            .Property(p2 => p2.Title)
                            .CurrentValue = p.Title.Replace("Entity Framework:", "EF:");

                        context.Entry(p)
                            .Reference(p2 => p2.Blog)
                            .CurrentValue = efBlog;
                    }
                });

            context.SaveChanges();
        }
        finally
        {
            context.Configuration.AutoDetectChangesEnabled = true;
        }
    }
}

This method attaches all posts in the given list. In addition, it replaces any title that starts with “Entity Framework:” with a title that starts with “EF:” and moves that post to a different blog.

If the code had done this by changing the properties of the Post entity directly, then a call to DetectChanges would have been required for EF to know about these changes and perform fixup, etc., and to ensure the changes are saved to the database correctly.

Instead, the code uses .Property and .Reference methods and then sets both the Title scalar property and the Blog navigation property through use of CurrentValue. Since this is a call into EF code it means that, in accordance with Rule 1, EF will ensure that everything is taken care of without needing a call to DetectChanges. This means that the code can call SaveChanges before switching on automatic DetectChanges with the confidence that everything will be saved correctly.

What about change-tracking proxies?

Another way to ensure that DetectChanges is not needed is to make use of change-tracking proxies. This is certainly a valid approach, but it also has its limitations and drawbacks, as described in my previous post on the subject.

Summary

Summarizing the advice in this post on DetectChanges: Next time we'll look at some corner cases around DetectChanges with complex types and binary properties.
This page is up-to-date as of March 12th, 2012. Some things change. Some things stay the same. Use your noggin.