Avoid AsNoTracking and Include when querying using Entity Framework in ASP.NET

Developers often use AsNoTracking in the belief that it will increase performance when performing read queries in Entity Framework. This post explains why this approach is flawed and its usage may actually be a sign of an underlying problem - a problem that is made even worse by the use of Include.

Background

HTTP is stateless, so in ASP.NET, when a page is displayed, we read data from the database and any subsequent saving of data is a completely separate request. Therefore, in most situations, when you query the initial data, you do not want Entity Framework to track it, as the DbContext will be destroyed at the end of the initial request.

If you submit a form, on postback, a new request will be issued, resulting in a new DbContext. At this stage (on update), we do want to track the data because this is how EF is able to work out what SQL needs to be executed in order to persist our changes.

GET

New Request -> New DbContext -> Query Data -> DbContext Destroyed

POST

New Request -> New DbContext -> Query Data (with tracking) -> Update Properties -> Save To Database -> DbContext Destroyed

It is quite clear to see that querying and updating are quite different situations. Querying doesn't require our data to be tracked and to do so is inefficient and may slow down the request. It seems as though AsNoTracking is exactly what we need!

The Problem

The problem of course is that we should not be querying entities in the first place.

Given the following query:

var blogPosts = _db.BlogPosts.Where(b => b.IsPublished).ToList();

If we add AddNoTracking, then depending on the number of records, we should see an immediate performance improvement:

var blogPosts = _db.BlogPosts.AsNoTracking().Where(b => b.IsPublished).ToList();

But we have to ask ourselves, why are we retrieving a list of BlogPost entities in the first place?

In any professional code base, we do not want to pass entity framework entities directly to our UI or client. Whether we are using ASP.NET MVC, Razor Pages, Web API or Blazor, we know this is bad practice. We should use an intermediate class - a DTO or view model. The benefits of such an approach are (almost) universally accepted:

It is unfortunate that there are so many online code samples that omit view models in the name of brevity. AsNoTracking is used to increase performance, but returning entities instead of view models can affect performance far more. When we are doing things the right way, AsNoTracking becomes redundant.

An Example

Let's look at an example, focusing on performance, because after all, this is what AsNoTracking is supposed to help with.

Imagine a blogging application where you want to display a list of links to blog posts:

With AsNoTracking, you might write:

var blogPosts = _db.BlogPosts.AsNoTracking().ToList();

Ignoring the lack of pagination, this looks fairly innocuous, so what's the problem?

The problem is that we do not place any restrictions on which columns to return. It is quite likely that the BlogPost database table includes the actual text of the blog post. This could be many thousands of words for each blog post and yet we query it unnecessarily.

Of course, in many cases, we need data from multiple tables. What if we wanted to display the author and number of comments too?

Is this a use case for EF's Include?

var blogPosts = _db.BlogPosts.AsNoTracking().Include(b => b.Author).Include(b => b.Comments).ToList();

This does give us the data that we need but at what cost?

We are querying all blog post data, all author data for those blog posts and all comments for all blog posts! This is extremely inefficient, but also extremely common. Yes, AsNoTracking may help performance in this case, but it is a band-aid. It is hiding a bigger problem. The way that we are querying our data is fundamentally flawed.

The Solution

The solution is to use a view model with a projection. In the first example, we only query the three fields we need:

var blogPosts = _db.BlogPosts.Select(b => new BlogPostModel(b.Title, b.Url, b.DatePublished)).ToList();

Note that because we use a projection, EF does not track any entity and the use of AsNoTracking is not necessary.

In the second example, we use navigation properties to query the author name field and count the number of comments. Our query is highly efficient and again AsNoTracking is not necessary:

var blogPosts = _db.BlogPosts.Select(b => new BlogPostModel(b.Title, b.Url, b.DatePublished, b.Author.Name, b.Comments.Count())).ToList();

Conclusion

The use of AsNoTracking and Include when simply returning data to the client is generally a sign that something is wrong. Either you are not using view models or you are querying entities and then mapping to view models in memory. In both cases, this is rarely a good idea and changing your approach will almost certainly result in performance gains in addition to the other benefits listed above.

When to use AsNoTracking

If you are using projections to map to view models (and you should be) then there is no need to ever use AsNoTracking.

When to use Include

As with AsNoTracking, if you are using projections to map to view models then Include should never be used when querying.

Includes does have an important use when updating however.

If you are updating a 'parent' entity (an aggregate root) such as an Order then you may well want to update child collections or other related data. In this scenario, Include can be used to query and track all required data in order to update it.

var order = _db.Orders.Include(o => o.OrderItems).First(o => o.Id = id);
// update order entity and child collection of order items
_db.SaveChanges();

Some of you might argue that view models are unnecessary in the most basic of CRUD applications but I do not agree with this and would recommend everyone use them in all cases. The benefits outweigh the very small overhead required, and with C# records, it is easier than ever.

Useful or Interesting?

If you liked the article, I would really appreciate it if you could share it with your Twitter followers.

Share on Twitter

Comments

Avatar for Vahid Vahid wrote on 04 Feb 2022

By usin `AsNoTracking()` method, change tracking proxies will never be created. It will increase the performance of querying data and reporting scenarios (it has a lower memory usage and more speed).

Avatar for Paul Hiles Paul Hiles wrote on 04 Feb 2022

@vahid - AsNoTracking() is only beneficial if you are returning the whole EF entity. When using projections (using .Select to map to a view model within the EF query) then EF will not track anything by default and AsNoTracking() is not necessary. This view model approach will typically increase performance even more, as well as providing many other benefits, as mentioned in the article.

Avatar for Miron Alighieri Miron Alighieri wrote on 09 Jun 2022

Thanks for sharing your point of view and experience.
I agree with all you presented!
But I hava a doubt about separation of responsabilities and application architeture layers.
I use view models, but they are defined in the scope of my web project. I have project to deal with business logic and another one to deal with data retrival and persistence.

Doing so, I have the following layer hierarchy:

UI (Web) => Business => Data

Where Web, and only web, "knows" view model, because, its used to show my views.

So, Business and Data doesn't know view model. In this case, view model could have some data that does matter only for the view.

Should I include an intermediate layer with classes that business and data will know and I could use for projection, like a DTO layer?

And when use it in CRUD, when im going to update an entity, I will come from de view model, to DTO class, to business logic, and then to data pesistence. At this point will I have to retrive the hole entity record, and manually update it from my DTO, and finally update it in EF?

Avatar for Olli Olli wrote on 15 Dec 2022

Thank you for a useful article.

Spotted one item that I would like to query (pun intended) about: "If you are using projections to map to view models (and you should be) then there is no need to ever use AsNoTracking."

In Microsoft EF Core documentation (https://learn.microsoft.com/en-us/ef/core/querying/tracking) they note that: "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."

These seem to contradict each other. What is your take on this?

Avatar for GJH GJH wrote on 09 Jan 2023

@OLLI, the thing is that if you use an entity type in you result, that item will be tracked. If you use only projection, it will not track. Just read the page you are referring to a bit more carefully.

Avatar for Paul Hiles Paul Hiles wrote on 12 Feb 2023

@OLLI As GJH pointed out, if you return the whole entity as part of the projection then the entity is tracked. This is almost certainly not something you want to do when querying but it can be very handy when updating.

The following contrived example updates the user status field based on the number of records in another table. You can take advantage of this EF tracking behaviour and get all the data you need in one database call:

var query = _db.Users.Select(user => new { User = user, OrderCount = user.Orders.Count() }).Where(user => user.Id == 1).First();

query.User.Status = query.OrderCount > 5 ? "Gold" : "Silver"; // user is being tracked so we can update it

_db.SaveChanges();

Avatar for Tohid Azizi Tohid Azizi wrote on 28 Apr 2023

@OLLI - Hi. Keep reading the same document: "If the result set doesn't contain any entity types, then no tracking is done."

https://learn.microsoft.com/en-us/ef/core/querying/tracking

Avatar for panchito panchito wrote on 29 Aug 2023

I think thas is very confusing and complicated and your are complicated a lot yourself with AsNoTracking(). if the performance is very important for your application so you must use dapper, sqlconnection with ADO.NET directly with you projections.
Always is faster use ADO.NET than using Entity Framework.