Song的学习笔记

首页 新随笔 联系 管理

EF Core ChangeTracker 机制详解

概述

EF Core 的 变更跟踪器(ChangeTracker) 是一个核心组件,负责追踪实体的增删改查状态,并在 SaveChanges() 时自动生成对应的 SQL 语句。

核心原理

当对 DbContext 执行操作时,EF Core 会自动跟踪实体的状态变更:

using var dbContext = new AppDbContext();

// 新增实体 → 状态变为 Added
dbContext.Authors.Add(author);

// 修改实体 → 状态变为 Modified
author.Name = "新名称";
dbContext.Authors.Update(author);

// 删除实体 → 状态变为 Deleted
dbContext.Authors.Remove(author);

// 查询实体 → 状态变为 Unchanged
var existing = await dbContext.Authors.FindAsync(1);

5 种实体状态

状态 说明 触发方式
EntityState.Added 新增(待插入) Add()AddAsync()
EntityState.Modified 已修改(待更新) 修改已跟踪实体的属性
EntityState.Deleted 已删除(待删除) Remove()
EntityState.Unchanged 未改变 查询得到的实体
EntityState.Detached 未被跟踪 新创建或 AsNoTracking()

访问变更跟踪器

// 获取所有被跟踪的实体
var entries = dbContext.ChangeTracker.Entries();

// 按类型过滤
var bookEntries = dbContext.ChangeTracker.Entries<Book>();

// 筛选特定状态的实体
var addedEntries = dbContext.ChangeTracker.Entries()
    .Where(e => e.State == EntityState.Added);

实战:审计字段自动填充

场景需求

自动填充实体的审计字段(创建时间、修改时间、软删除标记)。

实现代码

public class AppDbContext : DbContext
{
    public override int SaveChanges()
    {
        SetAuditFields();
        return base.SaveChanges();
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        SetAuditFields();
        return await base.SaveChangesAsync(cancellationToken);
    }

    private void SetAuditFields()
    {
        // 获取所有被跟踪的 BaseEntity 子类实体
        var entries = ChangeTracker.Entries<BaseEntity>()
            .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted);

        foreach (var entry in entries)
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    // 新增:设置创建时间
                    entry.Entity.CreatedAt = DateTime.Now;
                    entry.Entity.CreatedBy = GetCurrentUserId();
                    break;

                case EntityState.Modified:
                    // 修改:设置修改时间
                    entry.Entity.ModifiedAt = DateTime.Now;
                    entry.Entity.ModifiedBy = GetCurrentUserId();
                    break;

                case EntityState.Deleted:
                    // 删除 → 转为软删除
                    entry.Entity.IsDeleted = true;
                    entry.Entity.ModifiedAt = DateTime.Now;
                    entry.Entity.ModifiedBy = GetCurrentUserId();
                    // 关键:将状态改为 Modified,EF Core 会执行 UPDATE 而非 DELETE
                    entry.State = EntityState.Modified;
                    break;
            }
        }
    }

    private int GetCurrentUserId()
    {
        // 从 HttpContext 或 ClaimsPrincipal 获取当前用户 ID
        // 这里简化处理,返回固定值
        return 1;
    }
}

审计字段基类

public abstract class BaseEntity
{
    public int Id { get; set; }
    public DateTime CreatedAt { get; set; }
    public int CreatedBy { get; set; }
    public DateTime? ModifiedAt { get; set; }
    public int? ModifiedBy { get; set; }
    public bool IsDeleted { get; set; }
}

public class Book : BaseEntity
{
    public string Title { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

状态转换流程

用户操作                    EF Core 状态      SaveChanges() 生成 SQL
──────────────────────────────────────────────────────────────
dbContext.Add(entity)      Added            INSERT INTO ...
实体属性被修改              Modified         UPDATE ...
dbContext.Remove(entity)   Deleted          DELETE FROM ...

软删除场景:
dbContext.Remove(entity)   → Deleted → 改为 Modified → UPDATE ... IsDeleted = 1

关键技巧:软删除实现

软删除的核心是阻止物理删除,将删除操作转换为更新操作:

case EntityState.Deleted:
    entry.Entity.IsDeleted = true;
    entry.State = EntityState.Modified;  // 这一行至关重要!
    break;

如果不改状态,EF Core 会执行:

DELETE FROM Books WHERE Id = 1

改状态后,EF Core 会执行:

UPDATE Books SET IsDeleted = 1, ModifiedAt = '2026-01-23' WHERE Id = 1

使用查询过滤器实现软删除

结合 HasQueryFilter 实现自动过滤已删除记录:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>()
        .HasQueryFilter(b => !b.IsDeleted);  // 自动添加 WHERE IsDeleted = false
}

查询时:

// 普通查询 - 自动过滤已删除
var books = await dbContext.Books.ToListAsync();

// 忽略过滤器 - 查询所有(包括已删除)
var allBooks = await dbContext.Books
    .IgnoreQueryFilters()
    .ToListAsync();

常见问题

Q: 如何关闭变更跟踪以提升性能?

// 全局设置
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

// 单次查询
var books = await dbContext.Books.AsNoTracking().ToListAsync();

Q: 如何获取原始状态(修改前的值)?

var entry = dbContext.ChangeTracker.Entries<Book>().First();

// 修改前的值
var originalValue = entry.OriginalValues[nameof(Book.Title)];
// 修改后的值
var currentValue = entry.CurrentValues[nameof(Book.Title)];

Q: 批量操作时 ChangeTracker 不生效?

批量操作(AddRangeUpdateRangeRemoveRange)不受 ChangeTracker 跟踪,需要使用原生 SQL 或第三方库(如 EFCore.BulkExtensions)。

总结

功能 方法
获取所有被跟踪实体 ChangeTracker.Entries<T>()
过滤特定状态 .Where(e => e.State == EntityState.Added)
改状态(软删除) entry.State = EntityState.Modified
获取原始值 entry.OriginalValues["PropertyName"]
忽略全局查询过滤器 .IgnoreQueryFilters()
posted on 2026-01-24 08:00  Song的学习笔记  阅读(5)  评论(0)    收藏  举报