The Truth About AddAsync: When to Use It in EF Core (and When Not To)
Let’s crack open that EF Core mystery: Why does AddAsync()
exist when there’s no UpdateAsync()
or RemoveAsync()
? Feels like unfair treatment, right? Spoiler: It’s not favoritism—it’s about async value generation, not database calls. Let’s break it down.
The Big Misconception
AddAsync()
does NOT talk to your database. Nope, not even a little. When you write:
await db.Users.AddAsync(user);
Zero bytes hit the database. All it does is tell EF: “Track this new entity; insert it later when I save.” The actual INSERT
happens at SaveChangesAsync()
.
So why Async
? 🤔
The Real Reason: Async Value Generation
Imagine your entity needs a unique ID before saving, like a distributed Snowflake ID from an external service:
public class Order
{
public long Id { get; set; } // Generated by a fancy async service!
public string Customer { get; set; }
}
You’d write a custom generator:
public class SnowflakeIdGenerator : ValueGenerator<long>
{
public override bool GeneratesTemporaryValues => false;
public override ValueTask<long> NextAsync(EntityEntry entry, CancellationToken ct)
=> new ValueTask<long>(_GetIdAsync(ct));
private async Task<long> _GetIdAsync(CancellationToken ct)
{
await Task.Delay(50, ct); // Simulate network call
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
}
The Problem: Blocking Threads
If you use synchronous Add()
:
var order = new Order { Customer = "ZèD" };
db.Orders.Add(order); // ← SYNC CALL (DANGER!)
await db.SaveChangesAsync();
EF tries to run NextAsync()
synchronously. That Task.Delay(50)
? It blocks your thread while waiting. In a busy ASP.NET Core app, this murders throughput.
How AddAsync()
Saves the Day
var order = new Order { Customer = "ZèD" };
await db.Orders.AddAsync(order); // ← AWAITS async ID generation!
await db.SaveChangesAsync();
Now your async generator runs without blocking. No thread starvation, no performance bombs.
Why No UpdateAsync
/RemoveAsync
?
Simple: They don’t generate values.
Update()
: Just marks entity asModified
. No async work needed.Remove()
: Just marks asDeleted
. Nothing to await.
Unless you’re doing something wild (like async blockchain lookups during deletion—please don’t), there’s no async step here.
When Should YOU Use AddAsync
?
Scenario | Use AddAsync ? | Why |
---|---|---|
Custom async value generators (IDs, audit stamps) | Yes | Avoids blocking threads |
Async-first apps (e.g., ASP.NET Core) | Yes | Consistency & future-proofing |
High-scale services | Yes | Prevents thread pool starvation |
Simple apps with no async generators | Optional | Add() works fine too |
Key Takeaways
AddAsync
enables async value generation (like distributed IDs).- Updates/deletes don’t need async—they’re just state changes.
- No hidden database calls in
AddAsync
—it’s all about pre-save logic. - Default to
AddAsync
in async code. It’s safer and more flexible.
So next time you see AddAsync
, remember: it’s not EF playing favorites. It’s your secret weapon for non-blocking async workflows. And hey, if you really want RemoveAsync()
… maybe go pet a dog instead.
TL;DR:
AddAsync()
= Async value generation (e.g., IDs).
Update()
/Remove()
= No value generation → noAsync
needed.