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.

comments powered by Disqus

Related