Entity Framework: Saving Changes
This document explains how to save changes made to your data using Entity Framework (EF). EF provides mechanisms to track changes to entities and persist those changes to the database efficiently.
Understanding Change Tracking
Entity Framework automatically tracks changes made to entities retrieved from the database. When you modify a property of an entity or add/remove entities from a context, EF records these changes.
Saving Changes to the Database
The primary method for persisting changes to the database is DbContext.SaveChanges()
. This method iterates through all tracked entities, detects changes, and generates the appropriate SQL commands to update the database.
Adding New Entities
To add a new entity to the database, first create an instance of your entity class, populate its properties, and then add it to the appropriate DbSet<TEntity>
on your DbContext
.
using (var context = new MyDbContext())
{
var newProduct = new Product
{
Name = "New Gadget",
Price = 99.99m,
Category = "Electronics"
};
context.Products.Add(newProduct);
context.SaveChanges();
}
Modifying Existing Entities
When you retrieve an entity from the context and modify its properties, EF automatically detects these changes when SaveChanges()
is called.
using (var context = new MyDbContext())
{
var productToUpdate = context.Products.Find(1); // Find product with ID 1
if (productToUpdate != null)
{
productToUpdate.Price = 109.99m; // Modify a property
context.SaveChanges();
}
}
Deleting Entities
To delete an entity, retrieve it from the context and then call the Remove()
method on the context's DbSet<TEntity>
.
using (var context = new MyDbContext())
{
var productToDelete = context.Products.Find(2); // Find product with ID 2
if (productToDelete != null)
{
context.Products.Remove(productToDelete);
context.SaveChanges();
}
}
DbContext.SaveChanges()
Behavior
- EF determines the state of each tracked entity (Added, Modified, Deleted, Unchanged).
- It generates the necessary SQL INSERT, UPDATE, or DELETE statements.
- These statements are executed as a single database transaction by default.
SaveChanges()
encounters an error during execution, it will throw an exception, and the entire transaction will be rolled back.
Saving Changes Efficiently: Bulk Operations
For large numbers of additions, updates, or deletions, calling SaveChanges()
repeatedly can be inefficient. EF Core provides ways to optimize this.
AddRange()
, UpdateRange()
, RemoveRange()
These methods allow you to add, update, or remove multiple entities at once, which can be more efficient than iterating and calling Add()
, Update()
, or Remove()
individually.
using (var context = new MyDbContext())
{
var newProducts = new List<Product>
{
new Product { Name = "Item A", Price = 10.0m },
new Product { Name = "Item B", Price = 20.0m }
};
context.Products.AddRange(newProducts);
var product1 = context.Products.Find(3);
if(product1 != null) product1.Price = 15.0m;
var product2 = context.Products.Find(4);
if(product2 != null) product2.Price = 25.0m;
context.Products.UpdateRange(new List<Product> { product1, product2 });
var productToDelete = context.Products.Find(5);
if(productToDelete != null) context.Products.Remove(productToDelete);
context.SaveChanges();
}
Handling Concurrency Conflicts
Concurrency conflicts occur when multiple users or processes try to modify the same data simultaneously. EF provides strategies to handle these:
- Optimistic Concurrency: This is the most common approach. EF typically uses a row versioning property (like a timestamp or version number) to detect conflicts. If a conflict is detected during
SaveChanges()
, an exception (e.g.,DbUpdateConcurrencyException
) is thrown. You then need to decide how to resolve it (e.g., re-fetch the data, apply your changes, and try again). - Pessimistic Concurrency: Less common with EF alone, this involves locking the database row when it's read.
When a DbUpdateConcurrencyException
is caught, you can inspect the Entries
property to see which entities caused the conflict and implement custom resolution logic.
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
var databaseValue = entry.GetDatabaseValues();
if (databaseValue == null)
{
// The entity was deleted by another user.
// Handle appropriately (e.g., inform the user, remove from context).
}
else
{
var proposedValues = entry.CurrentValues;
var databaseEntry = context.Entry(entry.Entity);
databaseEntry.OriginalValues.SetValues(databaseValue);
// Decide how to resolve the conflict.
// For example, to accept the database values:
databaseEntry.CurrentValues.SetValues(databaseValue);
// Or to merge changes, or to inform the user.
// You might need to re-call SaveChanges() after resolution.
context.SaveChanges();
}
}
}
Transactions
By default, each call to SaveChanges()
is wrapped in its own database transaction. If you need to perform multiple operations within a single atomic transaction, you can explicitly manage transactions using DbContext.Database.BeginTransaction()
.
using (var context = new MyDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
var order = new Order { CustomerId = 1 };
context.Orders.Add(order);
context.SaveChanges(); // Saves changes within the transaction
var orderDetail = new OrderDetail { OrderId = order.OrderId, ProductId = 10, Quantity = 2 };
context.OrderDetails.Add(orderDetail);
context.SaveChanges(); // Saves changes within the same transaction
transaction.Commit(); // Commit the transaction
}
catch (Exception)
{
transaction.Rollback(); // Roll back the transaction on error
throw; // Re-throw the exception
}
}
}
Next Steps
Now that you know how to save changes, you can explore other aspects of Entity Framework, such as advanced querying techniques or database migrations.