Improve queries reading performances with AsNoTracking on C# EF Core
What is AsNoTracking
Entity Framework Core is a massive .NET tool to facilitate database queries.
If you delve into it, it offers many configuration possibilities to improve the speed depending on the use you make of it.
Here we will talk about the AsNoTracking
function, usable from IQueryable
.
If we take a look at the Microsoft :
Returns a new query where the entities returned will not be cached in the DbContext or ObjectContext
The question is: what is cached in the DbContext?
I'm not going to go into detail because it would take a whole article to have a proper answer.
The important part is the tracking graph. It is an internal property of the DbContext to record all the modified items that were obtained from the database.
Basically, it helps the DbContext to maintain all the updates made to the database objects in order to save the data.
This tracking has a cost, both on the performance of requests and on the use of memory.
As we are sure not to modify the retrieved data, the AsNoTracking
function is an excellent way to improve performance.
But beware.
AsNoTracking
can only be used for read requests.
Since DbContext no longer tracks updates, you will get errors if you try to SaveChanges
on the data you have retrieved.
Benchmark
To validate, we built a benchmark to compare the impact of AsNoTracking
on contextual queries.
public class Goat
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
The fixture generates 100.000 goats (yeah, that's huge!) in an InMemoryDatabase to make sure it has enough data to monitor differences.
public class DbContextBenchmark
{
[GlobalSetup]
public void Setup()
{
var goats = new Fixture().CreateMany<Goat>(100000);
var dbContext = new AppDbContext();
dbContext.AddRange(goats);
dbContext.SaveChanges();
}
[Benchmark]
public void GetInDatabase_WithAsNoTracking()
{
var dbContext = new AppDbContext();
dbContext.Goats.AsNoTracking().ToList();
}
[Benchmark]
public void GetInDatabase_WithoutAsNoTracking()
{
var dbContext = new AppDbContext();
dbContext.Goats.ToList();
}
}
We instantiate 2 functions:
GetInDatabase_WithAsNoTracking
to test with AsNoTrackingGetInDatabase_WithoutAsNoTracking
to test without AsNoTracking
Obviously, we need to create a new AppDbContext
for each method to make sure that no cache elements are contained in the DbContext after the first resolution.
Results
The benchmark shows us that with AsNoTracking
, the read request is 4x faster on average.
| Method | Mean | Error | StdDev | Median |
|---------------------------------- |---------:|---------:|---------:|---------:|
| GetInDatabase_WithAsNoTracking | 119.5 ms | 7.38 ms | 21.75 ms | 113.2 ms |
| GetInDatabase_WithoutAsNoTracking | 483.5 ms | 23.88 ms | 69.65 ms | 467.9 ms |
It's pretty massive, and it's a good trick to improve database access speed.
But remember, it's READ ONLY. I have to say that out loud!
To go further:
Have fun 🐐