Saving Data with Entity Framework
Saving data is a fundamental operation when working with databases. Entity Framework (EF) simplifies this process by allowing you to interact with your database using familiar .NET objects and methods. This section details how to add, update, and delete entities and persist these changes to the data source.
Understanding the DbContext
The DbContext
is the primary class for interacting with your data. It represents a session with the database and allows you to query from a given data source and track changes to your entities. When you save changes, the DbContext
figures out what has been added, modified, or deleted and generates the appropriate SQL commands.
Key Operations
- Adding New Entities: Use the
Add
orAddRange
methods on theDbSet<TEntity>
associated with your entity type. - Modifying Existing Entities: Once an entity is tracked by the context, simply modify its properties. The context will detect these changes.
- Deleting Entities: Use the
Remove
orRemoveRange
methods. - Persisting Changes: Call the
SaveChanges
orSaveChangesAsync
method on theDbContext
instance.
Adding New Entities
To add a new entity, instantiate it, set its properties, and then add it to the corresponding DbSet
in your DbContext
.
using (var context = new MyDbContext())
{
var newProduct = new Product
{
Name = "Super Widget",
Price = 19.99m,
Category = "Electronics"
};
context.Products.Add(newProduct);
await context.SaveChangesAsync(); // Asynchronous save
}
For adding multiple entities at once, you can use AddRange
:
var productsToAdd = new List
{
new Product { Name = "Gadget Pro", Price = 149.50m, Category = "Gadgets" },
new Product { Name = "Tech Gizmo", Price = 75.00m, Category = "Gadgets" }
};
context.Products.AddRange(productsToAdd);
await context.SaveChangesAsync();
Modifying Existing Entities
Entity Framework tracks entities that are retrieved from the database. When you fetch an entity, modify its properties, and then call SaveChanges
, EF will generate an `UPDATE` statement for you.
using (var context = new MyDbContext())
{
var productToUpdate = await context.Products.FindAsync(1); // Assuming Product with ID 1 exists
if (productToUpdate != null)
{
productToUpdate.Price = 21.50m;
productToUpdate.Name = "Super Widget Deluxe";
await context.SaveChangesAsync();
}
}
If you have detached entities (entities not currently tracked by the context) that you wish to update, you can reattach them and mark them as modified:
var detachedProduct = new Product { Id = 2, Name = "Updated Gadget", Price = 155.00m };
context.Entry(detachedProduct).State = EntityState.Modified;
await context.SaveChangesAsync();
Tip: When updating detached entities, ensure you set the primary key property correctly so EF can identify the correct record to update.
Deleting Entities
To delete an entity, first retrieve it (or ensure it's tracked by the context) and then call the Remove
or RemoveRange
method.
using (var context = new MyDbContext())
{
var productToDelete = await context.Products.FindAsync(3); // Assuming Product with ID 3 exists
if (productToDelete != null)
{
context.Products.Remove(productToDelete);
await context.SaveChangesAsync();
}
}
Deleting multiple entities:
var productsToRemove = await context.Products
.Where(p => p.Category == "OldCategory")
.ToListAsync();
context.Products.RemoveRange(productsToRemove);
await context.SaveChangesAsync();
Note: When an entity is removed, EF will typically generate a `DELETE` statement. If referential integrity constraints are in place and other entities have foreign keys pointing to the entity being deleted, the operation might fail unless you handle cascading deletes or explicitly remove dependent entities first.
Concurrency Control
Concurrency control is crucial in multi-user environments to prevent one user's changes from overwriting another's unintentionally. EF supports optimistic concurrency by default, often using a timestamp or version column.
When SaveChanges
is called and EF detects that a row has been modified by another process since it was loaded, it throws a DbUpdateConcurrencyException
. You can catch this exception and implement logic to handle it, such as:
- Retrying the save operation.
- Merging the changes.
- Informing the user and allowing them to resolve conflicts.
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
// Handle concurrency conflict, e.g., reload entity, merge changes, inform user
Console.WriteLine("Concurrency conflict detected. Please try again.");
// For more complex scenarios, you might reload the entity from the store
// and then try to re-apply your local changes or inform the user.
}
Bulk Operations
For very large numbers of inserts, updates, or deletes, calling SaveChanges
repeatedly can be inefficient. EF Core provides mechanisms for efficient bulk operations, often through external libraries or specific EF Core features.
Using `AddRange` and `SaveChanges` for Bulk Inserts
While AddRange
followed by a single SaveChanges
is good, for extreme scale, consider libraries like EFCore.BulkExtensions.
Bulk Updates/Deletes
EF Core does not have built-in generic bulk update/delete methods in the same way as inserts. However, you can achieve this by executing raw SQL commands or using the aforementioned external libraries.
// Example using raw SQL for a bulk delete
await context.Database.ExecuteSqlRawAsync("DELETE FROM Products WHERE Category = 'Obsolete'");