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:
| Aspect | SaveChanges() | SaveChangesAsync() |
|---|---|---|
| Thread Blocking | Blocks thread | Non-blocking |
| Return Type | int | Task<int> |
| Performance | Can bottleneck | Better scalability |
| Use Case | Console apps, batch jobs | Web APIs, UI apps |
| Thread Pool | Occupies thread | Releases 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;
}
}