我想为特定实体创建历史/审计表。这是一个包含许多子表的复杂实体,我们正在为我们的应用程序使用Repository Patter。 我查看了重写DbContext SaveChanges?将这个专门用于一个实体是一种好习惯吗? 我的其他选择是什么?。
提前致谢。
答案 0 :(得分:5)
我一直在创建可能有用的图书馆。
查看Audit.EntityFramework库,它拦截SaveChanges()
并可以configured来过滤您要审核的实体。
答案 1 :(得分:0)
@ thepirat000解决方案可能工作正常,但我希望拥有最少的NuGet依赖项,最好是0,这些依赖项不受大型社区/公司的支持,并且严重依赖于单个开发人员。
https://github.com/thepirat000/Audit.NET/graphs/contributors
您可以在没有任何外部库的情况下这样做:
using (var context = new SampleContext())
{
// Insert a row
var customer = new Customer();
customer.FirstName = "John";
customer.LastName = "doe";
context.Customers.Add(customer);
await context.SaveChangesAsync();
// Update the first customer
customer.LastName = "Doe";
await context.SaveChangesAsync();
// Delete the customer
context.Customers.Remove(customer);
await context.SaveChangesAsync();
}
型号:
public class Audit
{
public int Id { get; set; }
public string TableName { get; set; }
public DateTime DateTime { get; set; }
public string KeyValues { get; set; }
public string OldValues { get; set; }
public string NewValues { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class SampleContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Audit> Audits { get; set; }
}
DbContext:
public class SampleContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Audit> Audits { get; set; }
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()
{
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;
}
}
}
// Save audit entities that have all the modifications
foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
{
Audits.Add(auditEntry.ToAudit());
}
// keep a list of entries where the value of some properties are unknown at this step
return auditEntries.Where(_ => _.HasTemporaryProperties).ToList();
}
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();
}
}
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;
}
}
来源:
https://www.meziantou.net/entity-framework-core-history-audit-table.htm和@rasputino的评论
您还可以阅读有关Slowly changing dimension类型的更多信息,并从那里创建适合您需求的解决方案。
如果您需要整个Entity Framework快照历史记录,请查看this answer。