Entity Framework Core Change Tracking

Understanding how EF Core tracks changes to your entities.

Introduction to EF Core Change Tracking

Entity Framework Core (EF Core) automatically tracks changes made to entities that are retrieved from the database. This tracking mechanism is fundamental to how EF Core manages data persistence. When you call SaveChanges(), EF Core uses the tracked state of your entities to generate the appropriate SQL commands (INSERT, UPDATE, DELETE) to synchronize the database with your application's state.

The Change Tracker is a service within EF Core that maintains a snapshot of entity states. Each entity instance is associated with a specific EntityState value. This state indicates whether an entity is:

  • Detached: The entity is not being tracked by the context.
  • Unchanged: The entity has been retrieved from the database and its current state matches the database.
  • Added: The entity is new and will be inserted into the database.
  • Deleted: The entity has been marked for deletion from the database.
  • Modified: The entity's current state differs from its state when it was retrieved or attached.

How Change Tracking Works

When you query entities using an EF Core DbContext, the Change Tracker automatically begins tracking them. For each tracked entity, EF Core stores its original values. When a property of a tracked entity is modified, EF Core can compare the current value with the original value to detect the modification.

Property Value Comparers

EF Core uses property value comparers to determine if a value has changed. By default, these comparers use standard equality checks. For complex types or custom comparisons, you can configure custom value comparers.

Example: Tracking a New Entity

Let's say you create a new Product entity and add it to the context:

var newProduct = new Product { Name = "New Gadget", Price = 99.99m }; context.Products.Add(newProduct); // At this point, newProduct is tracked with EntityState.Added

Example: Modifying an Existing Entity

If you retrieve a product and change its price, EF Core will mark it as modified:

var existingProduct = context.Products.Find(1); // Assume Product with ID 1 exists if (existingProduct != null) { existingProduct.Price = 120.50m; // EF Core detects the change and sets EntityState.Modified for existingProduct }

Example: Deleting an Entity

To delete an entity:

var productToDelete = context.Products.Find(2); // Assume Product with ID 2 exists if (productToDelete != null) { context.Products.Remove(productToDelete); // EF Core sets EntityState.Deleted for productToDelete }

Accessing and Modifying Entity States

You can inspect and sometimes manipulate the state of tracked entities. The DbContext.ChangeTracker.Entries() method provides access to all tracked entities and their states.

foreach (var entry in context.ChangeTracker.Entries()) { Console.WriteLine($"Entity: {entry.Entity.GetType().Name}, State: {entry.State}"); }

You can also explicitly set the state of an entity if needed, although this is less common as EF Core usually manages it automatically:

var product = new Product { Id = 5, Name = "Old Item", Price = 50m }; context.Attach(product).State = EntityState.Modified; // Now, this detached product is tracked as modified.

DbContext Lifetime

The Change Tracker operates within the scope of a DbContext instance. Once a DbContext is disposed, its Change Tracker is also disposed, and all tracking information is lost.

Key Benefits of Change Tracking

  • Automatic SQL Generation: Simplifies data persistence by automatically generating the correct SQL.
  • Concurrency Control: EF Core can use tracked information for optimistic concurrency checks.
  • Performance: By tracking changes, EF Core avoids sending unnecessary UPDATE statements for unchanged entities.
  • Change State Management: Provides clear visibility into the status of your entities.

Conclusion

EF Core's change tracking is a powerful and essential feature for developers working with relational databases. Understanding how it works allows you to leverage its capabilities effectively, leading to more robust and efficient data access code.