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 as Modified. No async work needed.
  • Remove(): Just marks as Deleted. 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?

ScenarioUse AddAsync?Why
Custom async value generators (IDs, audit stamps)YesAvoids blocking threads
Async-first apps (e.g., ASP.NET Core)YesConsistency & future-proofing
High-scale servicesYesPrevents thread pool starvation
Simple apps with no async generatorsOptionalAdd() works fine too

Key Takeaways

  1. AddAsync enables async value generation (like distributed IDs).
  2. Updates/deletes don’t need async—they’re just state changes.
  3. No hidden database calls in AddAsync—it’s all about pre-save logic.
  4. 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 → no Async needed.