TheSharperDev

Educating about C# and F#

EF Core’s AddAsync v. Add Method

Since learning about async programming with C#, if I’m presented with an option of async v. non-async method I’ll always choose the async version. Why be blocking? Am I right?

A co-worker and I recently had a conversation about EF Core’s AddAsync() method, and some googling led me to the conclusion that I’ve not been needing the async nature of AddAsync(). The regular Add() method fits most of the add use cases.

Let’s give a little background how EF Core tracks objects and then explain in detail why the AddAsync() exists and how to choose between the two.

Unit of Work Pattern

EF Core’s DbContext adheres to the Unit of Work pattern.

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that needs to be done to alter the database as a result of your work.

Martin Fowler

When adding, creating, updating, deleting objects through a DbContext, those operations aren’t actually performed against the database. They’re performed against the context’s in memory representation of your database. Only when saving, are all the database operations pushed to the database.

Take the following example, we’re adding three users to our users table. These objects are initially only added to DbContext’s in memory tracking.

Only on line 9 when saving, do those objects get sent to the database and persisted.

But for example, if the SaveChanges() method was never called, the three users would not get inserted into our database.

Since EF Core does a lot of things in memory, such as adding objects, why do I need an async version of a method that operates in memory?

AddAsync Method

An async method usually implies an network call. There is an overhead to async methods, so if I’m not needing any async-ness, am I just reducing the performance of my app?

So why does the Add() method need an async version? A quick google search led me to the following fact:

This method is async only to allow special value generators, such as the one used by Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo, to access the database asynchronously. For all other cases the non async method should be used.

Microsoft Doc

What are special value generators? What is SequenceHiLo? In brief, it’s a way to prevent unnecessary round trips to the database when creating primary keys. If a table’s primary key is an int and it’s auto-incrementing, you’ll always need to ask the database what the next primary key is. SequenceHiLo allows reservation of a block of primary keys so trips to the database can be reduced. Here’s a great article describing SequenceHiLo in more detail.

Since I’ve never even encountered those terms before, the Add() method fits the use case of every object I’ve ever created through EF Core.

EF Core 3.0 Changes

Unfortunately there’s a cost associated with asynchronous methods. For every Task<T>, the C# compiler actually builds a state machine that tracks and coordinates the resuming of that task.

If asynchronous things are occurring, the cost of the state machine is negligible compared to the cost same method implemented in a blocking fashion. But if no asynchronous is needed, such as my uses with AddAscyn(), that overhead can add up and reduce performance of your app.

With the EF Core’s 3.0 release, this method slightly changed. Instead of returning a Task<T>, this method now returns a ValueTask<T>.

This change reduces the number of heap allocations incurred when invoking these methods, improving general performance.

EF Core 3.0 Release Notes

ValueTask is a struct, which can offer better performance that regular objects. It is the preferred return type of a method that could complete synchronously or asynchronously. If it needs to be synchronous, there is no overhead, but if it needs to make an asynchronous call then the normal Task<T> overhead is invoked.

Which Add Method Should I Use?

I would default to using async version of methods if available. I believe it’s better to use async methods and potentially incur some overhead versus not using async methods and having blocking calls.

Pre 3.0, if performance is critical to your app, than I would recommend using the Add() if you don’t need special value generators.

With 3.0 and later, go ahead and use the AddAsync() method because the ValueTask<T> prevents that overhead if it’s unnecessary.

Final Thoughts

The tools that we all use have a lot of complications and subtleties to them. Blanket statements like always use async methods don’t hold up under scrutiny. There are always exceptions.

That’s why its important to spend time understanding the tools that you’re working with. You never know what you don’t know, and what you don’t might causes issues for you.