Saving Changes in Entity Framework Core
This document covers the various methods and best practices for saving changes to your database using Entity Framework Core (EF Core). EF Core provides a robust and flexible API for managing data persistence.
Understanding the Change Tracker
EF Core's change tracker is central to how it manages saving changes. When you load entities from the database, EF Core tracks their state. Any modifications you make to these tracked entities are recorded by the change tracker.
- Added: Entities that will be inserted into the database.
- Modified: Entities whose properties have been changed.
- Deleted: Entities that will be removed from the database.
- Unchanged: Entities that have not been modified since they were loaded.
The DbContext.SaveChanges()
Method
The primary method for persisting changes is SaveChanges()
on your DbContext
instance. This method:
- Scans the change tracker for entities that have been added, modified, or deleted.
- Generates the appropriate SQL commands to apply these changes to the database.
- Executes the commands transactionally.
Example: Adding and Updating an Entity
using (var context = new MyDbContext())
{
// Add a new blog
var newBlog = new Blog { Url = "http://example.com/new" };
context.Blogs.Add(newBlog);
// Find an existing blog and update its URL
var existingBlog = context.Blogs.Find(1);
if (existingBlog != null)
{
existingBlog.Url = "http://example.com/updated";
}
// Save all pending changes
int recordsAffected = context.SaveChanges();
Console.WriteLine($"{recordsAffected} records affected.");
}
Saving Multiple Entities
SaveChanges()
automatically handles saving all changes detected by the change tracker in a single transaction. This means that either all changes are applied successfully, or none are. This ensures data integrity.
SaveChanges(bool acceptAllChangesOnSuccess)
The overload SaveChanges(true)
is the default behavior and resets the change tracker after a successful save. If set to false
, the change tracker's state is preserved, which can be useful in scenarios where you might want to perform further operations on the same entities.
Tip
Using SaveChanges(true)
(the default) is generally recommended to prevent unexpected behavior with the change tracker after saves.
Batching Changes (SaveChangesInBatch()
)
For performance-critical scenarios, especially when dealing with a large number of independent inserts, updates, or deletes, you might consider using batching. This can significantly reduce the number of round trips to the database. This functionality is often provided by third-party libraries or can be implemented manually.
Handling Concurrency Conflicts
Concurrency conflicts occur when multiple users or processes try to modify the same data simultaneously. EF Core provides strategies to handle these:
- Optimistic Concurrency: Using row versions (e.g., a timestamp column) or modified timestamp columns to detect conflicts.
- Pessimistic Concurrency: Using database locks, though this is less common in EF Core's default saving mechanisms.
When a concurrency exception (DbUpdateConcurrencyException
) is thrown, you need to decide how to resolve it. Common strategies include:
- Retrying the operation.
- Merging changes.
- Discarding the user's changes.
- Prompting the user to resolve the conflict.
Important
Properly handling concurrency is crucial for applications with multiple concurrent users.
Saving Changes in Transactions
By default, SaveChanges()
wraps the operations in a database transaction. You can also explicitly manage transactions using DbContext.Database.BeginTransaction()
for more complex scenarios where you need to combine multiple operations (including those outside of EF Core) into a single atomic unit.
Example: Explicit Transaction Management
using (var context = new MyDbContext())
using (var transaction = context.Database.BeginTransaction())
{
try
{
// Perform EF Core operations
var blog = context.Blogs.Find(1);
if (blog != null)
{
blog.Url = "http://new.url.com";
}
context.SaveChanges();
// Perform other operations that need to be part of the same transaction
// e.g., direct SQL execution, or operations on another DbContext if configured
// context.Database.ExecuteSqlRaw("UPDATE SomeOtherTable SET ... WHERE ...");
// If all operations succeed, commit the transaction
transaction.Commit();
}
catch (Exception ex)
{
// If any operation fails, roll back the transaction
transaction.Rollback();
Console.WriteLine($"An error occurred: {ex.Message}");
// Handle the exception appropriately
}
}
Saving Changes with Asynchronous Operations
EF Core supports asynchronous saving using SaveChangesAsync()
. This is highly recommended for I/O-bound operations like database interactions to keep your application responsive.
Example: Asynchronous Saving
public async Task AddAndSaveBlogAsync(Blog newBlog)
{
using (var context = new MyDbContext())
{
context.Blogs.Add(newBlog);
int recordsAffected = await context.SaveChangesAsync();
Console.WriteLine($"Async save: {recordsAffected} records affected.");
}
}