Turning off Tracking for Find and FindAsync in Entity Framework
There is a lot of debate around the “best” way to retrieve a single entity using its primary key with Entity Framework.
You will see discussions about using FindAsync
vs FirstAsync
vs FirstOrDefaultAsync
vs SingleAsync
vs SingleOrDefaultAsync
and their non-async counterparts.
From what I’ve seen Find/FindAsync
seems to be the most favored. It is also the one that Visual Studio will scaffold for you if you create a new controller with a Get
method that takes a primary key as a parameter.
So let’s assume FindAsync
is the “best”. The SQL it produces is the same as that of FirstAsync
and FirstOrDefaultAsync
-
SELECT TOP(1) [p].[ProductId], [p].[Code], [p].[Created], [p].[Description], [p].[LastModified], [p].[Name], [p].[Price], [p].[ProductCategory], [p].[SKU]
FROM [Products] AS [p]
WHERE [p].[ProductId] = @__id_0
If you use SingleAsync
the SQL will attempt to retrieve the TOP 2 rows, and throw an exception if there are more than one row is returned.
FindAsync
also searches the context before generating the SQL and calling the database. If the entity is found locally, no query is made to the database. This is a distinct advantage over the others.
But the discussions around FindAsync
do not cover its lack of an AsNoTracking
option.
With all the others you can use the AsNoTracking
option to tell Entity Framework not to track the entity for changes. This is very useful if you are retrieving the entity to read it, and not to update it.
If you had a Products
table you could do the following and generate the SQL shown above -
var product = _salesContext.Products.AsNoTracking().FirstAsync(p => p.ProductId == productId);
Very efficient, especially if have a GET
method on a controller that takes a primary key as a parameter. The context will be empty, and you won’t be updating the entity thus negating the advantages of FindAsync
.
Turning off tracking for Find/FindAsync
But what if you want to use Find/FindAsync
and turn off tracking? There is no Find/FindAsync
extension on EntityFrameworkQueryableExtensions
, so you can’t use AsNoTracking().FindAsync(..)
.
For example, this will give you a compilation error -
_salesContext.Products.AsNoTracking().FindAsync(productId);
But you can do this -
_salesContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
await _salesContext.Products.FindAsync(productId);
This will turn off tracking and then retrieve the entity.
Keep in mind, all calls to Find/FindAsync
will go to the database, even if you loaded the entity previously.
Conclusion
Remember that FindAsync
and FirstAsync
produce precisely the same SQL, and with tracking turned off for both it is likely that their performance will be the same, but I have not tested this. As with things performance related, you should do it yourself.