C# tutorials > Frameworks and Libraries > Entity Framework Core (EF Core) > Tracking and saving changes (SaveChanges, Add, Update, Remove)

Tracking and saving changes (SaveChanges, Add, Update, Remove)

Entity Framework Core (EF Core) is an ORM (Object-Relational Mapper) that simplifies database interactions in .NET applications. A core aspect of EF Core is tracking changes made to entities and persisting those changes to the database. This tutorial explores the SaveChanges, Add, Update, and Remove methods, which are fundamental to managing data persistence with EF Core.

Understanding Entity Tracking

EF Core automatically tracks changes made to entities retrieved from the database or added to the DbContext. This tracking mechanism allows EF Core to determine which entities need to be inserted, updated, or deleted when SaveChanges is called.

The DbContext maintains a snapshot of each entity's original state. When SaveChanges is invoked, EF Core compares the current state of the entity with its original state. Any differences are translated into appropriate SQL commands (INSERT, UPDATE, DELETE) that are executed against the database.

Adding New Entities (Add)

The Add method marks an entity as new and signals to EF Core that it should be inserted into the database. This is demonstrated in the code snippet above, where a new Blog object is created and added to the Blogs DbSet. When SaveChanges is called, EF Core will generate and execute an INSERT statement for this new blog.

using Microsoft.EntityFrameworkCore;

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite("Data Source=blogging.db");
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

using (var context = new BloggingContext())
{
    context.Blogs.Add(new Blog { Url = "http://example.com" });
    context.SaveChanges();
}

Updating Existing Entities (Update)

The Update method explicitly marks an entity as modified. While EF Core often automatically detects changes, Update is useful when dealing with disconnected entities (entities that were not originally retrieved from the DbContext). In the code snippet, a Blog is retrieved, its Url property is modified, and then Update is called to ensure EF Core recognizes the change. After calling SaveChanges, EF Core will generate an UPDATE statement reflecting the modified URL.

Important: In many cases, EF Core automatically detects changes without explicitly calling Update if the entity is being tracked. However, if you're working with disconnected entities or want to force an update, Update is necessary.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1); // Assuming a blog with ID 1 exists
    if (blog != null)
    {
        blog.Url = "http://new-example.com";
        context.Blogs.Update(blog); //explicitly mark to update
        //context.ChangeTracker.DetectChanges();
        context.SaveChanges();
    }
}

Deleting Entities (Remove)

The Remove method marks an entity for deletion. In the code snippet, a Blog is retrieved and then marked for deletion using Remove. When SaveChanges is called, EF Core will generate and execute a DELETE statement for the specified blog.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1); // Assuming a blog with ID 1 exists
    if (blog != null)
    {
        context.Blogs.Remove(blog);
        context.SaveChanges();
    }
}

Saving Changes (SaveChanges)

The SaveChanges method is the central point for persisting changes to the database. It examines all tracked entities and generates the appropriate SQL commands (INSERT, UPDATE, DELETE) based on the changes detected. It then executes these commands within a transaction. If any command fails, the entire transaction is rolled back, ensuring data consistency.

using (var context = new BloggingContext())
{
    context.Blogs.Add(new Blog { Url = "http://example.com" });
    context.SaveChanges(); // Persists changes to the database
}

SaveChangesAsync (Asynchronous Saving)

The SaveChangesAsync method is the asynchronous version of SaveChanges. It is highly recommended to use SaveChangesAsync in ASP.NET Core applications and other asynchronous environments to avoid blocking the main thread, improving the application's responsiveness.

using (var context = new BloggingContext())
{
    context.Blogs.Add(new Blog { Url = "http://example.com" });
    await context.SaveChangesAsync(); // Persists changes to the database asynchronously
}

Concepts behind the snippet

These methods directly interact with EF Core's change tracking mechanism and underlying database provider. EF Core translates object-oriented operations into relational database commands. Understanding the role of DbContext, Entity Tracking, and the purpose of each method (Add, Update, Remove, SaveChanges) is key to effectively managing data in EF Core applications.

Real-Life Use Case

Imagine an e-commerce application. When a user adds a product to their shopping cart, you'd use Add to insert a new record in the database representing the cart item. If the user updates the quantity of an item, you'd use Update. When the user removes an item, you'd use Remove. Finally, SaveChanges is called to persist all these changes to the database.

Best Practices

  • Use Asynchronous operations: Always prefer SaveChangesAsync over SaveChanges in web applications to prevent blocking threads.
  • Keep DbContext scope short: Create DbContext instances within a using statement to ensure proper disposal and avoid memory leaks.
  • Validate data before saving: Implement validation logic to ensure data integrity before calling SaveChanges.
  • Handle exceptions: Wrap SaveChanges calls in a try-catch block to handle potential database exceptions.
  • Use transactions where appropriate: For complex operations involving multiple entities, use explicit transactions (context.Database.BeginTransaction()) to ensure atomicity.

Interview Tip

Be prepared to explain the difference between Add, Update, and Remove, and how they interact with SaveChanges. Also, understand the importance of using SaveChangesAsync in asynchronous environments. You might also be asked about disconnected entities and scenarios where Update is essential.

When to Use Them

  • Add: When you need to insert a new record into the database.
  • Update: When you need to modify an existing record in the database. Especially useful for disconnected scenarios.
  • Remove: When you need to delete a record from the database.
  • SaveChanges/SaveChangesAsync: After you've performed Add, Update, and/or Remove operations and want to persist those changes to the database.

Alternatives

While EF Core provides a convenient way to interact with the database, alternative approaches exist:

  • Dapper: A lightweight ORM that offers better performance in some scenarios, but requires you to write SQL queries.
  • Raw SQL: You can execute raw SQL queries directly against the database using context.Database.ExecuteSqlRaw or context.Database.ExecuteSqlInterpolated. This gives you full control but requires more effort.

Pros

  • Simplified data access: EF Core abstracts away the complexities of database interactions.
  • Object-Relational Mapping: Maps database tables to .NET objects, making it easier to work with data.
  • Change tracking: Automatically tracks changes to entities, reducing boilerplate code.
  • Asynchronous support: Provides asynchronous methods for improved performance.

Cons

  • Performance overhead: Can be slower than raw SQL or micro-ORMs like Dapper in some scenarios.
  • Learning curve: Requires understanding of EF Core concepts and conventions.
  • Can generate inefficient queries: Poorly designed queries can lead to performance issues.
  • Abstraction can hide underlying database issues: It's essential to understand the SQL being generated.

FAQ

  • What happens if `SaveChanges` fails?

    If SaveChanges fails (e.g., due to a database constraint violation or a network issue), an exception will be thrown. Any changes made within the DbContext will be rolled back, preventing data corruption. It's important to wrap SaveChanges in a try-catch block to handle potential exceptions.

  • Does EF Core automatically detect all changes?

    EF Core automatically detects changes to tracked entities. However, if you're working with detached entities (entities that were not originally retrieved from the DbContext or were serialized/deserialized), you might need to explicitly call Update to mark the entity as modified.

  • How can I improve the performance of `SaveChanges`?

    • Only load the data you need: Use projections (Select) to retrieve only the necessary columns.
    • Disable change tracking when not needed: Use AsNoTracking() when querying data that you don't intend to modify.
    • Batch updates: For large numbers of updates, consider using techniques like bulk insert/update libraries.
    • Optimize database indexes: Ensure that your database indexes are properly configured.