Explain the difference between `SaveChanges()` and `SaveChangesAsync()`.

4 minbeginnerEF-CoreSaveChangesasync

Quick Answer

`SaveChanges()` persists tracked changes synchronously, blocking the calling thread until the database completes. `SaveChangesAsync()` does the same work asynchronously, freeing the thread during the I/O wait, which improves scalability in web apps. Prefer the async version for I/O-bound database work; both run inside an implicit transaction so all changes commit or roll back together.

Detailed Answer

SaveChanges() - Synchronous Blocks the current thread until database operations complete.

public void AddProduct(Product product)
{
    context.Products.Add(product);
    int affectedRows = context.SaveChanges(); // Blocks here
    Console.WriteLine($"Saved {affectedRows} rows");
}

SaveChangesAsync() - Asynchronous Returns a Task, allowing the thread to do other work while waiting.

public async Task AddProductAsync(Product product)
{
    context.Products.Add(product);
    int affectedRows = await context.SaveChangesAsync(); // Doesn't block
    Console.WriteLine($"Saved {affectedRows} rows");
}

Key Differences:

AspectSaveChanges()SaveChangesAsync()
Thread BlockingBlocks threadNon-blocking
Return TypeintTask<int>
PerformanceCan bottleneckBetter scalability
Use CaseConsole apps, batch jobsWeb APIs, UI apps
Thread PoolOccupies threadReleases thread

Performance Impact:

// Synchronous - Thread blocked during I/O
public void ProcessOrders()
{
    for (int i = 0; i < 1000; i++)
    {
        var order = new Order { /* ... */ };
        context.Orders.Add(order);
        context.SaveChanges(); // Thread waits for DB
    }
}

// Asynchronous - Thread available for other work
public async Task ProcessOrdersAsync()
{
    for (int i = 0; i < 1000; i++)
    {
        var order = new Order { /* ... */ };
        context.Orders.Add(order);
        await context.SaveChangesAsync(); // Thread released during DB operation
    }
}

Web API Example:

// BAD: Synchronous in async controller
[HttpPost]
public async Task CreateProduct(Product product)
{
    context.Products.Add(product);
    context.SaveChanges(); // Don't do this!
    return Ok();
}

// GOOD: Async all the way
[HttpPost]
public async Task CreateProduct(Product product)
{
    context.Products.Add(product);
    await context.SaveChangesAsync();
    return Ok();
}

CancellationToken Support:

public async Task CreateProductAsync(
    Product product, 
    CancellationToken cancellationToken)
{
    context.Products.Add(product);
    await context.SaveChangesAsync(cancellationToken);
    return product;
}

When to Use Each:

Use SaveChanges():

  • Console applications
  • Batch processing jobs
  • Synchronous codebases
  • Simple scripts
  • When async is not available

Use SaveChangesAsync():

  • ASP.NET Core Web APIs
  • Web applications
  • Desktop apps with UI
  • High-concurrency scenarios
  • When scalability matters

Important Notes:

  • Mixing sync and async can cause deadlocks
  • Always use async in ASP.NET Core
  • Async doesn't make individual operations faster, but improves scalability
  • Don't use SaveChangesAsync().Wait() or .Result - use proper async/await

Transaction Example:

// Synchronous transaction
using (var transaction = context.Database.BeginTransaction())
{
    try
    {
        context.Products.Add(product);
        context.SaveChanges();
        
        context.Categories.Add(category);
        context.SaveChanges();
        
        transaction.Commit();
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}

// Asynchronous transaction
using (var transaction = await context.Database.BeginTransactionAsync())
{
    try
    {
        context.Products.Add(product);
        await context.SaveChangesAsync();
        
        context.Categories.Add(category);
        await context.SaveChangesAsync();
        
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

Related Resources