Tracking vs. No-Tracking Queries

Tracking behavior controls if Entity Framework Core will keep information about an entity instance in its change tracker. If an entity is tracked, any changes detected in the entity will be persisted to the database during SaveChanges(). EF Core will also fix up navigation properties between the entities in a tracking query result and the entities that are in the change tracker.

Note

Keyless entity types are never tracked. Wherever this article mentions entity types, it refers to entity types which have a key defined.

Tip

You can view this article's sample on GitHub.

Tracking queries

By default, queries that return entity types are tracking. Which means you can make changes to those entity instances and have those changes persisted by SaveChanges(). In the following example, the change to the blogs rating will be detected and persisted to the database during SaveChanges().

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

No-tracking queries

No tracking queries are useful when the results are used in a read-only scenario. They're quicker to execute because there's no need to set up the change tracking information. If you don't need to update the entities retrieved from the database, then a no-tracking query should be used. You can swap an individual query to be no-tracking.

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

You can also change the default tracking behavior at the context instance level:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

Identity resolution

Since a tracking query uses the change tracker, EF Core will do identity resolution in a tracking query. When materializing an entity, EF Core will return the same entity instance from the change tracker if it's already being tracked. If the result contains same entity multiple times, you get back same instance for each occurrence. No-tracking queries don't use the change tracker and don't do identity resolution. So you get back new instance of entity even when the same entity is contained in the result multiple times. This behavior was different in versions before EF Core 3.0, see previous versions.

Tracking and custom projections

Even if the result type of the query isn't an entity type, EF Core will still track entity types contained in the result by default. In the following query, which returns an anonymous type, the instances of Blog in the result set will be tracked.

var blog = context.Blogs
    .Select(b =>
        new
        {
            Blog = b,
            PostCount = b.Posts.Count()
        });

If the result set contains entity types coming out from LINQ composition, EF Core will track them.

var blog = context.Blogs
    .Select(b =>
        new
        {
            Blog = b,
            Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault()
        });

If the result set doesn't contain any entity types, then no tracking is done. In the following query, we return an anonymous type with some of the values from the entity (but no instances of the actual entity type). There are no tracked entities coming out of the query.

var blog = context.Blogs
    .Select(b =>
        new
        {
            Id = b.BlogId,
            Url = b.Url
        });

EF Core supports doing client evaluation in the top-level projection. If EF Core materializes an entity instance for client evaluation, it will be tracked. Here, since we're passing blog entities to the client method StandardizeURL, EF Core will track the blog instances too.

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(blog => new
    {
        Id = blog.BlogId,
        Url = StandardizeUrl(blog)
    })
    .ToList();
public static string StandardizeUrl(Blog blog)
{
    var url = blog.Url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

EF Core doesn't track the keyless entity instances contained in the result. But EF Core tracks all the other instances of entity types with key according to rules above.

Some of the above rules worked differently before EF Core 3.0. For more information, see previous versions.

Previous versions

Before version 3.0, EF Core had some differences in how tracking was done. Notable differences are as follows:

  • As explained in Client vs Server Evaluation page, EF Core supported client evaluation in any part of the query before version 3.0. Client evaluation caused materialization of entities, which weren't part of the result. So EF Core analyzed the result to detect what to track. This design had certain differences as follows:

    • Client evaluation in the projection, which caused materialization but didn't return the materialized entity instance wasn't tracked. The following example didn't track blog entities.

      var blogs = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(blog => new
          {
              Id = blog.BlogId,
              Url = StandardizeUrl(blog)
          })
          .ToList();
      
    • EF Core didn't track the objects coming out of LINQ composition in certain cases. The following example didn't track Post.

      var blog = context.Blogs
          .Select(b =>
              new
              {
                  Blog = b,
                  Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault()
              });
      
  • Whenever query results contained keyless entity types, the whole query was made non-tracking. That means that entity types with keys, which are in result weren't being tracked either.

  • EF Core did identity resolution in no-tracking query. It used weak references to keep track of entities that had already been returned. So if a result set contained the same entity multiples times, you would get the same instance for each occurrence. Though if a previous result with the same identity went out of scope and got garbage collected, EF Core returned a new instance.