如何在SaveChangesInterceptor中获取新增实体的最终主键ID?
解决SaveChangesInterceptor获取新增实体最终主键的问题
你当前在SavingChangesAsync中读取新增实体主键时拿到的是接近-2^31的值,原因是这个方法触发时机在数据提交到数据库之前——EF Core会给自增主键的新增实体临时分配负数占位ID(从-1开始递减),真正的主键值要等数据库生成后,EF Core才会同步回实体。
下面是两种可行的解决方案:
方案1:分阶段处理,先暂存再更新主键
在SavingChangesAsync中完成基础审计信息的收集,暂存新增实体的审计条目,等到SavedChangesAsync(保存成功后触发)再更新最终主键值:
public class CustomInterceptor : SaveChangesInterceptor { // 临时存储待更新主键的审计条目 private readonly List<AuditEntry> _pendingAuditEntries = new(); public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { var context = eventData.Context; if (context == null) return base.SavingChangesAsync(eventData, result, cancellationToken); var trackedEntries = context.ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted); foreach (var entry in trackedEntries) { var auditEntry = new AuditEntry { EntityType = entry.Entity.GetType().Name, Operation = entry.State.ToString(), KeyValues = new Dictionary<string, object>(), Entity = entry.Entity // 关联实体,后续用于匹配更新 }; foreach (var property in entry.Properties) { var propName = property.Metadata.Name; if (property.Metadata.IsPrimaryKey()) { // 新增实体此时是临时ID,先占位 auditEntry.KeyValues[propName] = property.CurrentValue; continue; } // 处理其他属性的审计记录(如旧值、新值) } if (entry.State == EntityState.Added) { _pendingAuditEntries.Add(auditEntry); } // 处理编辑、删除实体的审计逻辑 } return base.SavingChangesAsync(eventData, result, cancellationToken); } public override ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default) { var context = eventData.Context; if (context == null) return base.SavedChangesAsync(eventData, result, cancellationToken); // 保存成功后,更新新增实体的最终主键 foreach (var auditEntry in _pendingAuditEntries) { var entry = context.ChangeTracker.Entries().FirstOrDefault(e => e.Entity == auditEntry.Entity); if (entry != null) { foreach (var keyProp in entry.Properties.Where(p => p.Metadata.IsPrimaryKey())) { auditEntry.KeyValues[keyProp.Metadata.Name] = keyProp.CurrentValue; } } // 此处将最终的auditEntry写入审计日志 } _pendingAuditEntries.Clear(); return base.SavedChangesAsync(eventData, result, cancellationToken); } } // 示例审计条目类 public class AuditEntry { public string EntityType { get; set; } public string Operation { get; set; } public Dictionary<string, object> KeyValues { get; set; } public object Entity { get; set; } // 可添加其他字段:操作时间、操作人等 }
方案2:直接在SavedChangesAsync中处理新增实体审计
如果不需要在保存前做过多预处理,可直接把新增实体的审计逻辑放在SavedChangesAsync中,此时读取到的主键就是数据库生成的最终值:
public class CustomInterceptor : SaveChangesInterceptor { public override ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default) { var context = eventData.Context; if (context == null) return base.SavedChangesAsync(eventData, result, cancellationToken); // 处理新增实体审计,此时主键已生成 var addedEntries = context.ChangeTracker.Entries() .Where(e => e.State == EntityState.Added); foreach (var entry in addedEntries) { var auditEntry = new AuditEntry { EntityType = entry.Entity.GetType().Name, Operation = EntityState.Added.ToString(), KeyValues = new Dictionary<string, object>() }; // 直接读取最终主键值 foreach (var keyProp in entry.Properties.Where(p => p.Metadata.IsPrimaryKey())) { auditEntry.KeyValues[keyProp.Metadata.Name] = keyProp.CurrentValue; } // 处理其他属性、写入审计日志 } // 处理编辑、删除实体的审计逻辑(可放在SavingChangesAsync中) return base.SavedChangesAsync(eventData, result, cancellationToken); } }
关键说明
EF Core针对自增主键(如SQL Server的IDENTITY),会在SaveChanges执行完成后自动将数据库生成的ID同步回实体的主键属性,因此SavedChangesAsync阶段读取到的就是最终有效的主键值。
内容的提问来源于stack exchange,提问作者Yoda
相关产品推荐
相关产品推荐

