Entity Framework Core (EF Core) is a popular Object-Relational Mapping (ORM) framework that simplifies database interactions for .NET developers. One of its methods that often sparks discussions about performance is .AsNoTracking().

What is .AsNoTracking()?

When you query entities using EF Core, by default, these entities are “tracked” by the DbContext. This means that any changes to these entities will be detected and persisted back to the database when you call SaveChanges(). This tracking mechanism ensures data integrity but comes with an overhead.

Enter .AsNoTracking(). When applied, the context will not track the returned entities. In simple terms, it tells EF Core, “just fetch the data for me and don’t worry about any changes I might make to it.”

Why Use .AsNoTracking()?

  1. Performance: Since EF doesn’t have to maintain information about the entity’s state, there’s typically less overhead, leading to faster query performance in many scenarios.
  2. Memory Efficiency: Without tracking, the context doesn’t hold onto the entity, leading to reduced memory usage.
  3. Read-only Scenarios: When you know you’re just reading data and won’t modify it, .AsNoTracking() is a good fit.

Let’s see an example benchmark on 10_000 rows:

[MemoryDiagnoser]
public class AsNoTrackingVsNormal
{
    [Benchmark]
    public List<Blog> Blogs()
    {
        using var context = new BlogContext();
        var blogs = context.Blogs.ToList();

        return blogs;
    }

    [Benchmark]
    public List<Blog> BlogsAsNoTracking()
    {
        using var context = new BlogContext();
        var blogs = context.Blogs.AsNoTracking().ToList();

        return blogs;
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Here are the results on my computer

MethodMeanErrorStdDevGen0Gen1Gen2Allocated
Blogs31.111 ms0.3717 ms0.3295 ms1875.0000812.5000437.500013.18 MB
BlogsAsNoTracking6.151 ms0.1197 ms0.1061 ms375.0000203.125078.12502.91 MB
Benchmarked on EF Core 7 and .NET 7

As we can see, AsNoTracking helped performance a lot in this particular case!

When Might .AsNoTracking() Seem Slower?

While .AsNoTracking() often results in performance gains, there are scenarios where this might not be the case:

  1. Cache Utilization: EF Core’s first-level cache can quickly return entities without hitting the database again. For repeated queries, tracked entities might be faster as they’re retrieved from the cache.
  2. Relation Loading: Tracked entities can sometimes handle relationships more efficiently. Without tracking, additional database queries might be required when navigating through relations.
  3. Re-materialization: In cases where entities are re-queried, having them tracked might avoid the overhead of recreating object instances.

Best Practices

  1. Understand Your Use Case: Before blindly using .AsNoTracking(), understand the specific needs of your application. If you’re performing read-heavy operations without the need for subsequent updates, .AsNoTracking() is beneficial.
  2. Inspect Generated SQL: Occasionally, inspect the SQL generated by your queries. This will give you an idea of the actual database operations taking place.
  3. Benchmark: Always benchmark performance changes in a controlled environment. Use tools like BenchmarkDotNet to compare the performance of tracked vs. non-tracked queries.
  4. Remember Updates: If you’re fetching data to update it later in the same context, tracking is beneficial. Otherwise, you’ll need to re-attach and set the state of entities manually.

Note: If you want your application to have a default behavior of no tracking you can do this:

services.AddDbContext<DatabaseContext>(options =>
{
  options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    options.UseSqlServer(databaseSettings.DefaultConnection);
});

Conclusion

.AsNoTracking() is a powerful tool in the EF Core toolkit. While it often results in performance improvements, it’s essential to understand the underlying mechanisms and trade-offs. As with many tools, its effectiveness depends on the context in which it’s used. Always remember to measure, analyze, and then optimize.

Leave a comment

Trending