SaveChanges 拦截器里怎么拿到待保存的实体
EF Core 6+ 提供了
SaveChangesInterceptor,但它不直接暴露变更实体列表;真正能安全遍历待提交实体的地方是重写
DbContext.SaveChanges或使用
SaveChangesAsync的 override 版本。拦截器更适合做日志、异常包装等横切操作,自动更新时间这类逻辑建议放在
SaveChanges调用前统一处理。
关键点:调用
ChangeTracker.DetectChanges()确保状态最新,再遍历
ChangeTracker.Entries()获取所有
Added和
Modified实体。
如何识别并更新 CreatedAt / UpdatedAt 字段
不能硬编码字段名去反射赋值,否则耦合严重、易出错。推荐定义接口约束,比如:
public interface ITrackTime
{
DateTime CreatedAt { get; set; }
DateTime UpdatedAt { get; set; }
}然后在
SaveChanges中统一处理: 对
Added实体:设置
CreatedAt和
UpdatedAt为当前时间(如
DateTime.UtcNow) 对
Modified实体:只更新
UpdatedAt,避免覆盖原有
CreatedAt跳过未实现
ITrackTime的实体,保持兼容性
为什么不用 ValueGeneratedOnAdd / OnUpdate 配置
数据库端生成(如 SQL Server 的
GETUTCDATE())看似省事,但有明显缺陷: 应用层无法在保存前读取到生成的时间值,影响后续业务逻辑(比如日志、缓存键计算) 并发场景下,若多个实体依赖同一时间戳,数据库生成会导致微小偏差 单元测试难模拟,因为绕过了 C# 层控制
ValueGeneratedOnAddOrUpdate在 EF Core 中并不存在,官方不支持“修改时自动生成”这种混合策略
要注意的坑:并发修改、软删除、导航属性
实际项目中容易漏掉这些情况:
手动调用Entry(entity).State = EntityState.Modified时,
ChangeTracker可能不会标记所有属性为已修改,导致
UpdatedAt更新失败 —— 建议改用
Attach+ 显式
Property(...).IsModified = true软删除实体(如含
IsDeleted字段)通常也需更新
UpdatedAt,别只判断
Added/
Modified导航集合里的子实体不会被父实体的
SaveChanges自动触发时间更新,必须确保子类也实现
ITrackTime,并在同一轮遍历中被捕获 如果用了
AsNoTracking()查询后直接修改再
Update(),要确认实体确实进入了
Modified状态,否则时间字段不会更新
最稳妥的做法是把时间更新逻辑封装成一个可复用的方法,在每个
SaveChanges入口调用,而不是依赖某一种配置或拦截时机。
