EF Core之自动历史记录
有的场景下需要记录特定表的增删改操作,以便追溯。传统的做法是在增删改的方法里同步做记录,很繁琐。在这里我们可以配合EF Core的DBContext做一个全局管控
-
全局管控自然就要有固定的格式
-
我们可以建一个空接ITrackable口来标明需要追踪的表:
public interface ITrackable { }
-
对于需要追踪的表,实现ITrackable作为标识:
public class TableNeedTrack : ITrackable { }
-
为历史记录表提供一个接口:
public interface IHistory { /// <summary> /// 记录时间 /// </summary> DateTime Hist_Created { get; set; } /// <summary> /// 记录行为 /// </summary> string Hist_Action { get; set; } /// <summary> /// 记录对象id /// </summary> int? Hist_SourceId { get; set; } }
-
对于历史记录表,实现IHistory,命名必须严格按照{TableNeedTrack}_Hist的格式,以便全局管控:
public class TableNeedTrack_Hist : IHistory { /// <summary> /// Id /// </summary> public int Id { get; set; } /// <summary> /// 记录时间 /// </summary> [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public DateTime Hist_Created { get; set; } = DateTime.Now; /// <summary> /// 记录行为 /// </summary> public string Hist_Action { get; set; } /// <summary> /// 记录对象id /// </summary> public int? Hist_SourceId { get; set; } }
- 历史记录有两种方式:1.将所有变化记录在一个Json字符串(判断真正变更的字符比较麻烦也损耗性能,不一定有必要);2.把原对象所有字段复制到历史记录表(本文选用此方法作为说明);
-
-
做好以上准备后,就可以到DBContext里做全局管控了:
//在SaveChanges里管控历史记录,这样就可以做到全自动处理 public override int SaveChanges(bool acceptAllChangesOnSuccess) { //获取原始历史记录 var histEntries = OnBeforeSaving(); //自动保存 var result = base.SaveChanges(acceptAllChangesOnSuccess); //保存后再添加历史记录:有的信息需要数据库生成,比如id在保存前的话会得到-2147482647 OnAfterSaving(histEntries); return result; } /// <summary> /// 保存前的操作:历史记录 /// </summary> /// <returns>历史记录基础数据</returns> private List<HistoryEntry> OnBeforeSaving() { //预先保留需要做历史记录且有更改的Entry var histEntries = ChangeTracker.Entries<ITrackable>() .Where(e => e.State != EntityState.Detached && e.State != EntityState.Unchanged) .Select(e => new HistoryEntry { EntityEntry = e, State = e.State.ToString() }) .ToList(); return histEntries; } /// <summary> /// 保存后的操作:保存历史记录 /// </summary> /// <param name="entries">历史记录基础数据</param> private void OnAfterSaving(List<HistoryEntry> entries) { if (entries.IsNullOrEmpty()) { //这里很重要,确保只做正确的历史记录,保证历史记录的记录不能进行 return; } foreach (var entry in entries) { var entity = (IEntity)entry.EntityEntry.Entity; Type entityType = entity.GetType(); //历史记录必须和原对象同命名空间,名字=原对象+"_Hist"的格式 var histTypeName = $"{entityType.Namespace}.{entityType.Name + "_Hist"}, {entityType.GetTypeInfo().Assembly.FullName}"; Type histType = Type.GetType(histTypeName, false); if (histType != null) { var histEntity = (IHistory)Activator.CreateInstance(histType); if (histEntity != null) { var histFields = histType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(k => k.Name); var entityFields = entityType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance); //这里过滤了Id(主键) foreach (var field in entityFields.Where(af => histFields.ContainsKey(af.Name) && af.Name != "Id").ToList()) { //将修改内容赋值给历史记录表同样的字段 var value = field.GetValue(entity); histFields[field.Name].SetValue(histEntity, value); } histEntity.Hist_SourceId = entity.Id; histEntity.Hist_Action = entry.State; Add(histEntity); } } } SaveChanges(); }
-
这样就能够实现自动历史记录了