Secrets of DetectChanges Part 3: Switching off automatic DetectChanges

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:

  • Don’t turn off automatic DetectChanges unless you really need to; it will just cause you pain.
  • If you need to turn off DetectChanges, then do it locally using a try/finally.
  • Use the DbContext property APIs to make changes to your entities without needing a call to DetectChanges

Next time we’ll look at some corner cases around DetectChanges with complex types and binary properties.

About Arthur Vickers

Developer on the Entity Framework team at Microsoft.
This entry was posted in Change Tracking, DbContext API, Entity Framework, Foreign Keys, Proxies and tagged , , , , , , , , , , . Bookmark the permalink.

13 Responses to Secrets of DetectChanges Part 3: Switching off automatic DetectChanges

  1. Anders Abel says:

    Thanks for a great in depth post series.

    In your code sample you switch automatic change detection back right before SaveChanges, and then the context is disposed of. Is it really needed in that case? Or is it just a best practice to always do, in case there are other changes done that requires snapshot detection? Or is the configuration object somehow shared so that the disabled change detection affects future context to be instantiated?

  2. As I said in the post, “In fact, this means that in this case the SaveChanges call could also safely been performed before automatic DetectChanges is switched back on.” So, yes, in this case SaveChages does not need DetectChanges. If you modify the entities, then often it will be needed.

    The flag does not transfer to new contexts, so if you know the context will be disposed, then it’s safe to not switch automatic DetectChanges back on.

  3. Per Clausen says:

    Great article Arthur!

  4. alviankristi says:

    Hi Arthur,
    Thanks for explanation about DetectChanges.
    Could you share about Tracking EF and best practice to use EF?

  5. Very helpful article!

  6. Peter says:

    Is there a possibility to avoid DetectChanges by using the property API for a relation, but accessing the entity (its DbEntityEntry) at the other side of the relation, the side holding the ICollection?
    Example:
    context.Entry(efBlog)
    .Reference(p2 => efBlog.Posts)
    .CurrentValue.Add(p);
    I want to add the post to the Blog’s posts collection instead of adding the blog to the post without having to use DetectChanges().
    Greetings

    • @Peter It currently isn’t possible to do this with the DbContext API. We have an item in CodePlex to enable this but I’m not sure which release of EF it will get into–it won’t be in EF6. The CodePlex item is: https://entityframework.codeplex.com/workitem/271 Note that it is possible to do this by dropping down to ObjectContext–the ObjectContext APIs to use are included in the CodePlex item.

      Thanks,
      Arthur

  7. GAURAV UMALKAR says:

    Hi Arthur Vickers,

    @Thanks for the great article above

    “context.Configuration.AutoDetectChangesEnabled”, this property is not working in entity framework 4.0,is there any other way that i can turnoff the AutoDetectChanges in my entity farm work 4.0.While using these property i am getting an error,that the core entities doesn’t contain a definition for configuration,do i need to add any reference or is there any other way for the same….Plz help….

    Thanks in advance…..

    Regards,
    Gaurav

    • @Gaurav This property only applies to EF 4.1 or above when using DbContext. ObjectContext does not automatically call DetectChanges expect as part of SaveChanges, and this can be switched off using one of the overloads of SaveChanges.

      Thanks,
      Arthur

  8. Alberto says:

    Great! Very usefull, thanks so much!

  9. Casey says:

    In this post, you allude to there being issues with turning off automatic change detection and just calling DetectChanges() before SaveChanges(). Could you shed some light on what those are? I’d like to know under what circumstances it would cause bugs.

    • @Casey Sorry for being slow to respond. In a nutshell the reason is that EF functionality is often dependent on the states of the entities. If EF does not have current information on these states then it will behave in ways that can be unexpected.

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