在我们的日常开发中,有时候需要记录数据库表中值的变化, 这时候我们通常会使用触发器或者使用关系型数据库中临时表(Temporal Table)或数据变更捕获(Change Data Capture)特性来记录数据库表中字段的值变化。原文的作者Gérald Barré讲解了如何使用Entity Freamwork Core上下文中的ChangeTracker来获取并保存实体的变化记录。
原文链接 Entity Framework Core: History / Audit table
ChangeTracker是Entity Framework Core记录实体变更的核心对象(这一点和以前版本的Entity Framework一致)。当你使用Entity Framework Core进行获取实体对象、添加实体对象、删除实体对象、更新实体对象、附加实体对象等操作时,ChangeTracker都会记录下来对应的实体引用和对应的实体状态。 我们可以通过ChangeTracker.Entries()方法, 获取到当前上下文中使用的所有实体对象, 以及每个实体对象的状态属性State。
Entity Framework Core中可用的实体状态属性有以下几种
DetachedUnchangedDeletedModifiedAdded所以如果我们要记录实体的变更,只需要从ChangeTracker中取出所有Added, Deleted, Modified状态的实体, 并将其记录到一个日志表中即可。
我们以下面这个例子为例。 当前我们有一个顾客表Customer和一个日志表Audit, 其对应的实体对象及Entity Framework上下文如下:
我们希望当执行以下代码之后, 在Audit表中产生如下数据
class Program { static void Main(string[] args) { using (var context = new SampleContext()) { // Insert a row var customer = new Customer(); customer.FirstName = "John"; customer.LastName = "doe"; context.Customers.Add(customer); context.SaveChangesAsync().Wait(); // Update the first customer customer.LastName = "Doe"; context.SaveChangesAsync().Wait(); // Delete the customer context.Customers.Remove(customer); context.SaveChangesAsync().Wait(); } } }首先我们添加一个AuditEntry类, 来生成变更记录。
public class AuditEntry { public AuditEntry(EntityEntry entry) { Entry = entry; } public EntityEntry Entry { get; } public string TableName { get; set; } public Dictionary<string, object> KeyValues { get; } = new Dictionary<string, object>(); public Dictionary<string, object> OldValues { get; } = new Dictionary<string, object>(); public Dictionary<string, object> NewValues { get; } = new Dictionary<string, object>(); public List<PropertyEntry> TemporaryProperties { get; } = new List<PropertyEntry>(); public bool HasTemporaryProperties => TemporaryProperties.Any(); public Audit ToAudit() { var audit = new Audit(); audit.TableName = TableName; audit.DateTime = DateTime.UtcNow; audit.KeyValues = JsonConvert.SerializeObject(KeyValues); audit.OldValues = OldValues.Count == 0 ? null : JsonConvert.SerializeObject(OldValues); audit.NewValues = NewValues.Count == 0 ? null : JsonConvert.SerializeObject(NewValues); return audit; } }然后我们打开SampleContext.cs, 复写方法SaveChangeAsync代码如下。
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) { var auditEntries = OnBeforeSaveChanges(); var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); await OnAfterSaveChanges(auditEntries); return result; } private List<AuditEntry> OnBeforeSaveChanges() { throw new NotImplementedException(); } private Task OnAfterSaveChanges(List<AuditEntry> auditEntries) { throw new NotImplementedException(); }然后我们来修改OnBeforeSaveChanges方法, 代码如下
private List<AuditEntry> OnBeforeSaveChanges() { ChangeTracker.DetectChanges(); var auditEntries = new List<AuditEntry>(); foreach (var entry in ChangeTracker.Entries()) { if (entry.Entity is Audit || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged) continue; var auditEntry = new AuditEntry(entry); auditEntry.TableName = entry.Metadata.Relational().TableName; auditEntries.Add(auditEntry); foreach (var property in entry.Properties) { if (property.IsTemporary) { // value will be generated by the database, get the value after saving auditEntry.TemporaryProperties.Add(property); continue; } string propertyName = property.Metadata.Name; if (property.Metadata.IsPrimaryKey()) { auditEntry.KeyValues[propertyName] = property.CurrentValue; continue; } switch (entry.State) { case EntityState.Added: auditEntry.NewValues[propertyName] = property.CurrentValue; break; case EntityState.Deleted: auditEntry.OldValues[propertyName] = property.OriginalValue; break; case EntityState.Modified: if (property.IsModified) { auditEntry.OldValues[propertyName] = property.OriginalValue; auditEntry.NewValues[propertyName] = property.CurrentValue; } break; } } } }最后我们修改一下OnAfterSaveChanges, 代码如下
private Task OnAfterSaveChanges(List<AuditEntry> auditEntries) { if (auditEntries == null || auditEntries.Count == 0) return Task.CompletedTask; foreach (var auditEntry in auditEntries) { // Get the final value of the temporary properties foreach (var prop in auditEntry.TemporaryProperties) { if (prop.Metadata.IsPrimaryKey()) { auditEntry.KeyValues[prop.Metadata.Name] = prop.CurrentValue; } else { auditEntry.NewValues[prop.Metadata.Name] = prop.CurrentValue; } } // Save the Audit entry Audits.Add(auditEntry.ToAudit()); } return SaveChangesAsync(); }本篇源代码
转载于:https://www.cnblogs.com/lwqlun/p/9693970.html
相关资源:数据结构—成绩单生成器