Entity Framework Core Change Tracking

Entity Framework Core (EF Core) provides a robust change tracking mechanism that automatically detects and manages changes made to entities within a DbContext. This allows you to save these changes to the database with minimal effort.

Key Concept: The DbContext keeps track of every entity instance it loads. For each entity, it records its current state (Added, Modified, Deleted, or Unchanged) and the original values of its properties.

How Change Tracking Works

When you query entities from the database using EF Core, the DbContext's change tracker begins monitoring them. Any modifications you make to these entities' properties are automatically detected.

States of an Entity

EF Core associates one of the following states with each entity it tracks:

Detecting Changes

EF Core automatically detects changes when:

For simple property changes, EF Core compares the current property values with the original values it stored when the entity was first tracked. If there's a difference, the entity's state is set to Modified.

Working with Change Tracking

1. Loading and Modifying Entities

When you retrieve an entity and modify its properties, EF Core automatically marks it as Modified.


using (var context = new MyDbContext())
{
    var product = context.Products.Find(1);
    if (product != null)
    {
        product.Name = "Updated Product Name";
        product.Price = 29.99M;
        // EF Core automatically tracks these changes.
        context.SaveChanges();
    }
}
            

2. Adding New Entities

New entities can be added directly to a DbSet. EF Core will mark them as Added.


using (var context = new MyDbContext())
{
    var newProduct = new Product
    {
        Name = "New Gadget",
        Price = 99.99M
    };
    context.Products.Add(newProduct);
    // The newProduct is now in the Added state.
    context.SaveChanges();
}
            

3. Deleting Entities

Entities can be removed from a DbSet. EF Core will mark them as Deleted.


using (var context = new MyDbContext())
{
    var productToDelete = context.Products.Find(2);
    if (productToDelete != null)
    {
        context.Products.Remove(productToDelete);
        // productToDelete is now in the Deleted state.
        context.SaveChanges();
    }
}
            

Manual Change Tracking

In some scenarios, you might need more control over how EF Core tracks changes. You can use the Entry API for this.

Marking an Entity as Modified

If you've updated an entity detached from the context, or if automatic detection isn't sufficient, you can manually mark it.


using (var context = new MyDbContext())
{
    var detachedProduct = new Product { Id = 3, Name = "Detached Product", Price = 50.00M };
    context.Attach(detachedProduct); // Attach the entity
    context.Entry(detachedProduct).State = EntityState.Modified; // Manually mark as Modified
    // You can also mark specific properties as modified:
    // context.Entry(detachedProduct).Property(p => p.Name).IsModified = true;
    context.SaveChanges();
}
            

Ignoring Properties

You can configure EF Core to ignore certain properties during change tracking or for specific operations.


// In your DbContext configuration (e.g., OnModelCreating)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .Ignore(p => p.InternalNotes); // Ignore the InternalNotes property
}
            

Change Tracking Options

You can configure change tracking behavior globally or per-context.

Disabling Change Tracking

For read-only scenarios, disabling change tracking can improve performance.


// Option 1: Use AsNoTracking() for individual queries
var products = await context.Products.AsNoTracking().ToListAsync();

// Option 2: Configure DbContextOptions
var options = new DbContextOptionsBuilder<MyDbContext>()
    .UseInMemoryDatabase("MyDatabase")
    .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.SensitiveDataLoggingEnabled)) // Example of other config
    .Options;

// Note: Disabling tracking globally is often done via options or specific query methods,
// not a direct DbContext setting that is mutually exclusive with tracking.
// The most common way is AsNoTracking().
            

Advanced Scenarios

Handling Concurrency Conflicts

EF Core can help detect and handle concurrency conflicts where multiple users might try to update the same row simultaneously. This is typically done using row versions (e.g., timestamps or version numbers).

Example using a row version:


public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [Timestamp] // This property will be used for concurrency checks
    public byte[] Version { get; set; }
}
            

When a concurrency conflict occurs, EF Core throws a DbUpdateConcurrencyException, which you can catch and handle appropriately (e.g., by re-loading the data, asking the user to resolve, or choosing the latest version).

Concurrency Handling: Always consider concurrency management for applications where data might be modified by multiple users simultaneously. The [Timestamp] attribute is a common and effective way to implement optimistic concurrency.

Interceptors

Interceptors allow you to hook into EF Core's operation pipeline, including change tracking events, to perform custom logic.

Summary

EF Core's change tracking is a powerful feature that simplifies data persistence. By understanding entity states and leveraging the DbContext's capabilities, you can efficiently manage your data operations.