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.
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:
Added
: The entity is new and will be inserted into the database.Modified
: The entity existed in the database, but its properties have been changed. EF Core will generate anUPDATE
statement for it.Deleted
: The entity existed in the database, and it has been marked for deletion. EF Core will generate aDELETE
statement for it.Unchanged
: The entity has not been changed since it was loaded or attached to the context.
Detecting Changes
EF Core automatically detects changes when:
- A property value is modified.
- An entity is attached or added to the context.
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).
[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.