changetracker.debugview.shortview 是调试 ef core 变更追踪最直接的方法,一行代码即可查看所有被跟踪实体的状态和主键值,如 blog {id: 1} modified,仅限开发/测试环境使用。

ChangeTracker.DebugView 看一眼就明白当前跟踪了什么
调试变更追踪最直接的办法,就是把 ChangeTracker.DebugView.ShortView 打出来——它不依赖断点、不卡线程,一行代码就能看清所有被跟踪实体的状态和主键值。
- 用法极简:
Console.WriteLine(context.ChangeTracker.DebugView.ShortView); - 输出类似:
Blog {Id: 1} Modified、Post {Id: -2147482643} Added、Post {Id: 4} Deleted,一目了然哪些是新对象、哪些改过、哪些要删 - 注意:只在开发/测试环境用,生产环境别留这行;
ShortView不含属性值,想看完整快照(比如原始值 vs 当前值)得用LongView,但性能开销明显增大
状态不是“写死”的,而是靠 DetectChanges 自动推断
EF Core 默认会在 SaveChanges() 前自动调用 DetectChanges(),扫描所有被跟踪实体的属性快照差异,再更新 EntityState。你手动改了属性,状态就变;你不改,它就一直保持 Unchanged。
- 常见误区:查出实体后只改导航属性(如
blog.Posts.Add(newPost)),但没碰blog.Name→ 主实体仍为Unchanged,只有新增的Post是Added - 需要强制刷新?调
context.ChangeTracker.DetectChanges(),比如你在非 EF 方式修改了实体(反射赋值、JSON 反序列化),EF 不会自动感知,必须手动触发 - 性能敏感场景可关自动检测:
context.ChangeTracker.AutoDetectChangesEnabled = false,但之后所有状态变更都得自己Entry(e).State = ...显式设
Entry() 是绕过自动逻辑、接管状态的唯一入口
当你手里有个“带主键但没查过库”的对象(比如 API 接收的 DTO 转成的 new User { Id = 123, Name = "张三" }),EF Core 默认当它是新数据(Added),但你想走 UPDATE ——这时候必须用 Entry() 手动设状态。
-
context.Entry(user).State = EntityState.Modified:让 EF 忽略快照比对,直接生成 UPDATE 全字段语句(含未改字段) -
context.Entry(user).Property(e => e.Name).IsModified = true:更精细,只更新指定字段,其余字段不进 SQL - 危险操作:
context.Entry(user).State = EntityState.Added+ 主键已存在 → SaveChanges 时直接报主键冲突,不是覆盖 - 软删除典型用法:遍历
Entries().Where(e => e.State == EntityState.Deleted && e.Entity is ISoftDelete),然后设e.State = EntityState.Modified并改IsDeleted = true
AsNoTracking() 不是“不跟踪”,而是“不注册到 ChangeTracker”
很多人以为 .AsNoTracking() 是为了提速,其实核心作用是**切断变更追踪链路**:返回的对象状态永远是 Detached,改了也不会被 SaveChanges 感知,自然不会生成任何 SQL。
- 适用场景:纯读取、导出、DTO 构建,后续绝对不调
SaveChanges() - 误用后果:查出来改了属性,再
context.Update(entity)—— EF 会认为这是个全新对象(因为之前没跟踪),可能触发 INSERT 而非 UPDATE - 替代方案:如果只是想省快照内存,但还要保留更新能力,用
AsNoTrackingWithIdentityResolution()(EF Core 5+),它仍能识别主键重复,避免重复附加
真正难的不是记住五种状态,而是理解“谁在什么时候触发了状态流转”。多数诡异问题(比如该更新却插入、该插入却报错)都源于对 DetectChanges 时机或 Entry() 手动干预边界的误判。多打几次 DebugView.ShortView,比翻文档管用。










