C# tutorials > Language Integrated Query (LINQ) > LINQ to Entities (Entity Framework Core) > What is change tracking in EF Core?

What is change tracking in EF Core?

Change tracking in Entity Framework Core (EF Core) is the mechanism by which EF Core monitors the state of entities retrieved from or added to the database. It automatically detects modifications made to these entities. This allows EF Core to efficiently generate SQL update statements only for the properties that have actually changed, optimizing database interactions and improving application performance. Without change tracking, you would have to manually track and inform EF Core about every single modification you make to your entities, which would be a tedious and error-prone process.

The Basics of Change Tracking

EF Core maintains a snapshot of the original values of the entities when they are queried from the database or added to the context. When SaveChanges() is called, EF Core compares the current values of the entities with the original values stored in the snapshot. If any differences are detected, EF Core generates the appropriate UPDATE statements to persist these changes to the database. If no changes are detected, no UPDATE statements are executed.

Entity States

EF Core tracks entities in various states:

  • Added: The entity has been added to the context but not yet saved to the database.
  • Modified: The entity has been retrieved from the database and its properties have been modified.
  • Deleted: The entity has been marked for deletion from the database.
  • Unchanged: The entity has been retrieved from the database and its properties have not been modified.
  • Detached: The entity is not being tracked by the context.

Example: Modifying an Entity and Saving Changes

In this example, we retrieve a Blog entity from the database. We then modify the Url property and call SaveChanges(). EF Core's change tracking automatically detects that the Url property has been modified and generates the corresponding UPDATE statement.

using Microsoft.EntityFrameworkCore;

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

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("YourConnectionString");
}

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

public class Example
{
    public static void UpdateBlogUrl(int blogId, string newUrl)
    {
        using (var context = new BloggingContext())
        {
            var blog = context.Blogs.Find(blogId);
            if (blog != null)
            {
                blog.Url = newUrl;
                context.SaveChanges(); // EF Core detects the change and generates the UPDATE statement.
            }
        }
    }
}

Accessing the State of an Entity

This example demonstrates how to access the state of an entity using context.Entry(entity). You can then access the State property of the EntityEntry to determine the current state of the entity. In this case we see the blog moves from Unchanged to Modified, then after SaveChanges() it's back to Unchanged.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;

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

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("YourConnectionString");
}

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

public class Example
{
    public static void CheckBlogState(int blogId)
    {
        using (var context = new BloggingContext())
        {
            var blog = context.Blogs.Find(blogId);
            if (blog != null)
            {
                EntityEntry entry = context.Entry(blog);
                Console.WriteLine($"Blog State: {entry.State}");

                blog.Url = "New URL";
                Console.WriteLine($"Blog State after modification: {context.Entry(blog).State}");

                context.SaveChanges();
                Console.WriteLine($"Blog State after SaveChanges: {context.Entry(blog).State}");
            }
        }
    }
}

Real-Life Use Case: Audit Logging

Change tracking is invaluable for implementing audit logging. By overriding the SaveChanges() method, you can intercept changes to entities before they are persisted to the database. This allows you to create audit logs recording who made the changes, when, and what was changed. This example captures the entity name, the state of the entity (added, modified, deleted), the timestamp of the change, and details about specific property changes.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Linq;

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<AuditLog> AuditLogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("YourConnectionString");

    public override int SaveChanges()
    {
        var changedEntities = ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Added ||
                        e.State == EntityState.Modified ||
                        e.State == EntityState.Deleted)
            .ToList();

        foreach (var entry in changedEntities)
        {
            var auditLog = new AuditLog
            {
                EntityName = entry.Entity.GetType().Name,
                State = entry.State.ToString(),
                Timestamp = DateTime.UtcNow,
                Changes = GetChanges(entry)
            };

            AuditLogs.Add(auditLog);
        }

        return base.SaveChanges();
    }

    private string GetChanges(EntityEntry entry)
    {
        var changes = new List<string>();
        foreach (var property in entry.Properties)
        {
            if (property.IsModified)
            {
                changes.Add($"{property.Metadata.Name}: OldValue = {property.OriginalValue}, NewValue = {property.CurrentValue}");
            }
        }
        return string.Join(", ", changes);
    }
}

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

public class AuditLog
{
    public int AuditLogId { get; set; }
    public string EntityName { get; set; }
    public string State { get; set; }
    public DateTime Timestamp { get; set; }
    public string Changes { get; set; }
}

Best Practices

  • Keep Contexts Short-Lived: Create and dispose of DbContext instances within a using statement to ensure resources are released and change tracking is properly managed.
  • Be Mindful of Detached Entities: If you're working with detached entities (entities not tracked by the context), you may need to explicitly attach them to the context using Attach or Update before saving changes.
  • Use AsNoTracking() for Read-Only Queries: If you are only querying data and not making modifications, use the AsNoTracking() method to disable change tracking and improve performance.

Interview Tip

When discussing change tracking in interviews, be prepared to explain how EF Core detects changes, the different entity states, and how change tracking can be used for implementing features like audit logging. Also, be ready to discuss the performance implications of change tracking and how to optimize it.

When to use AsNoTracking()

Use AsNoTracking() when retrieving data for display or reporting purposes where modifications are not required. This can significantly improve performance, especially for large datasets, as EF Core does not need to maintain snapshots of the entities.

Memory Footprint

Change tracking can increase the memory footprint of your application because EF Core needs to store snapshots of the entities being tracked. Be mindful of the number of entities being tracked, especially in long-running contexts. Using AsNoTracking() can help reduce the memory overhead when tracking is not needed.

Alternatives to EF Core's Change Tracking

While EF Core's change tracking is convenient, in certain scenarios, you might consider alternatives:

  • Dapper: A micro-ORM that provides raw SQL querying capabilities with object mapping, offering greater control over data access and no built-in change tracking.
  • Stored Procedures: Using stored procedures allows you to handle data manipulation logic directly in the database, bypassing the need for change tracking in the application layer.
  • Explicit State Management: Manually tracking changes and generating SQL statements can be useful for highly customized data access scenarios.

Pros of Change Tracking

  • Simplified Development: Automatically detects changes, reducing boilerplate code.
  • Optimized Updates: Generates only the necessary UPDATE statements, minimizing database traffic.
  • Built-in Auditing Capabilities: Facilitates the implementation of audit logging.

Cons of Change Tracking

  • Performance Overhead: Maintaining snapshots can increase memory usage and processing time.
  • Complexity: Can be complex to manage in detached scenarios or when working with complex object graphs.
  • Potential for Unexpected Behavior: Incorrectly managed change tracking can lead to unexpected updates or data inconsistencies.

FAQ

  • How do I disable change tracking for a specific query?

    Use the AsNoTracking() method when querying the data. For example: context.Blogs.AsNoTracking().ToList();
  • What happens if I modify an entity that is not being tracked by the context?

    EF Core will not detect the changes. You need to explicitly attach the entity to the context using Attach or Update before calling SaveChanges(). Attach will mark all properties as Unchanged. Update will mark all properties as Modified. You can selectively mark individual properties as modified after attaching as well.
  • Can I prevent specific properties from being tracked?

    No, EF Core does not provide a mechanism to selectively disable change tracking for individual properties. You either track the entire entity or disable tracking for the entire query using AsNoTracking(). You could, however, ignore properties from your model entirely so that they are never tracked. Or, in audit logging scenarios, you could choose to not log changes to specific properties.