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

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:


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'");