The Truth About AddAsync: When to Use It in EF Core (and When Not To)
Mofajjal Rasul

Mofajjal Rasul @imzihad21

About: // Tech geek / Comfortable in CLI / Love to automate stuffs //

Location:
Dhaka, Bangladesh
Joined:
Dec 21, 2021

The Truth About AddAsync: When to Use It in EF Core (and When Not To)

Publish Date: May 28
0 0

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);  
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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(); 
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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?

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

  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.

Comments 0 total

    Add comment