Entity Framework 6 中自动创建实体历史表的实现方法
嘿,刚上手EF6要做全实体的历史记录是吧?这需求太常见了,我给你分享几个实用的方案,从原生实现到第三方工具都有,你可以根据自己的项目情况选:
方案一:重写DbContext的SaveChanges(原生实现,无依赖)
这是最灵活的方式,不用加任何第三方库,完全自己控制逻辑:
步骤1:定义通用的历史表实体
先创建一个EntityHistory类,用来存储所有实体的变更记录:
public class EntityHistory { public int Id { get; set; } public string EntityName { get; set; } // 变更的实体类名(比如"User"、"Order") public string EntityId { get; set; } // 变更实体的主键值 public DateTime ChangedAt { get; set; } // 变更时间 public string ChangedBy { get; set; } // 操作人(可以从当前用户获取) public string OldValues { get; set; } // 变更前的旧值(用JSON序列化存储) public string ChangeType { get; set; } // 操作类型:Insert/Update/Delete }
步骤2:在你的DbContext里重写SaveChanges方法
在继承DbContext的类里,覆盖SaveChanges方法,捕获所有变更的实体并写入历史表:
using Newtonsoft.Json; // 需要安装Newtonsoft.Json NuGet包 public class YourDbContext : DbContext { // 注册历史表DbSet public DbSet<EntityHistory> EntityHistories { get; set; } public override int SaveChanges() { // 获取所有状态为新增、修改、删除的实体 var changedEntries = ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted); foreach (var entry in changedEntries) { var historyRecord = new EntityHistory { EntityName = entry.Entity.GetType().Name, ChangedAt = DateTime.Now, ChangedBy = HttpContext.Current?.User?.Identity?.Name ?? "Unknown", // 适配ASP.NET场景 ChangeType = entry.State.ToString() }; // 获取实体主键值(假设所有实体都有名为Id的主键,通用场景可以用反射获取主键) var primaryKey = entry.Property("Id").CurrentValue; historyRecord.EntityId = primaryKey?.ToString() ?? "N/A"; // 处理旧值:新增没有旧值,修改和删除序列化原始值 if (entry.State != EntityState.Added) { historyRecord.OldValues = JsonConvert.SerializeObject(entry.OriginalValues.ToObject()); } // 将历史记录添加到上下文 EntityHistories.Add(historyRecord); } // 先保存历史记录,再保存主实体变更 return base.SaveChanges(); } }
步骤3:生成历史表
用EF Code-First迁移生成数据库表:
- 打开Package Manager Console
- 执行
Add-Migration AddEntityHistoryTable - 执行
Update-Database
方案二:用Audit.NET快速实现(第三方工具,省心省力)
如果不想自己写太多代码,Audit.NET是专门做审计记录的成熟库,对EF6支持很好:
步骤1:安装NuGet包
Install-Package Audit.EntityFramework
步骤2:配置Audit.NET
在项目启动的地方(比如Global.asax的Application_Start)配置审计规则:
using Audit.Core; using Audit.EntityFramework; using Newtonsoft.Json; Audit.Core.Configuration.Setup() .UseEntityFramework(options => options // 指定审计记录对应的实体类 .AuditTypeMapper(_ => typeof(EntityHistory)) // 映射审计事件到EntityHistory字段 .AuditEntityAction<EntityHistory>((auditEvent, dbEntry, historyEntity) => { historyEntity.EntityName = dbEntry.EntityType.Name; historyEntity.EntityId = dbEntry.PrimaryKey.First().Value.ToString(); historyEntity.ChangedAt = DateTime.Now; historyEntity.ChangedBy = HttpContext.Current?.User?.Identity?.Name ?? "Unknown"; historyEntity.ChangeType = dbEntry.Action; historyEntity.OldValues = dbEntry.OldValues != null ? JsonConvert.SerializeObject(dbEntry.OldValues) : null; }) // 忽略和EntityHistory实体匹配的属性(避免循环引用) .IgnoreMatchedProperties());
步骤3:修改DbContext继承AuditDbContext
把你的DbContext改成继承AuditDbContext:
public class YourDbContext : AuditDbContext { // 你的其他DbSet... public DbSet<EntityHistory> EntityHistories { get; set; } public YourDbContext() : base("YourConnectionString") { } }
这样每次调用SaveChanges,Audit.NET会自动帮你生成历史记录,不用自己写拦截逻辑。
注意事项
- 主键通用处理:如果你的实体主键不是叫
Id,方案一里需要用反射动态获取主键字段,比如通过entry.Metadata.FindPrimaryKey()来获取主键属性。 - 性能优化:如果是高并发场景,批量写入历史记录可以提升性能,比如把历史记录先缓存,定时批量插入。
- 敏感数据:如果实体包含敏感信息(比如密码),序列化的时候要排除这些字段,避免泄露。
内容的提问来源于stack exchange,提问作者Josef
相关产品推荐
相关产品推荐

