entity framework4.1 DbContext重写SaveChanges以审计属性更改
我试图实现一个受约束的“审计日志”属性更改为一组类属性。 我已经成功find了如何设置CreatedOn | ModifiedOntypes的属性,但我没有find如何find已被修改的属性。
例:
public class TestContext : DbContext { public override int SaveChanges() { var utcNowAuditDate = DateTime.UtcNow; var changeSet = ChangeTracker.Entries<IAuditable>(); if (changeSet != null) foreach (DbEntityEntry<IAuditable> dbEntityEntry in changeSet) { switch (dbEntityEntry.State) { case EntityState.Added: dbEntityEntry.Entity.CreatedOn = utcNowAuditDate; dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate; break; case EntityState.Modified: dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate; //some way to access the name and value of property that changed here var changedThing = SomeMethodHere(dbEntityEntry); Log.WriteAudit("Entry: {0} Origianl :{1} New: {2}", changedThing.Name, changedThing.OrigianlValue, changedThing.NewValue) break; } } return base.SaveChanges(); } } 那么,有没有一种方法可以访问在EF 4.1 DbContext中随这个级别更改的属性?
非常非常粗略的想法:
 foreach (var property in dbEntityEntry.Entity.GetType().GetProperties()) { DbPropertyEntry propertyEntry = dbEntityEntry.Property(property.Name); if (propertyEntry.IsModified) { Log.WriteAudit("Entry: {0} Original :{1} New: {2}", property.Name, propertyEntry.OriginalValue, propertyEntry.CurrentValue); } } 
 我不知道这是否真的有效,但这是第一步。 当然,可能有更多的一个属性已经改变了,因此WriteAudit的循环和可能的多个调用。 
SaveChanges内部的reflection内容可能会成为性能噩梦。
编辑
 也许最好是访问底层的ObjectContext 。 那么这样的事情是可能的: 
 public class TestContext : DbContext { public override int SaveChanges() { ChangeTracker.DetectChanges(); // Important! ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext; List<ObjectStateEntry> objectStateEntryList = ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted) .ToList(); foreach (ObjectStateEntry entry in objectStateEntryList) { if (!entry.IsRelationship) { switch (entry.State) { case EntityState.Added: // write log... break; case EntityState.Deleted: // write log... break; case EntityState.Modified: { foreach (string propertyName in entry.GetModifiedProperties()) { DbDataRecord original = entry.OriginalValues; string oldValue = original.GetValue( original.GetOrdinal(propertyName)) .ToString(); CurrentValueRecord current = entry.CurrentValues; string newValue = current.GetValue( current.GetOrdinal(propertyName)) .ToString(); if (oldValue != newValue) // probably not necessary { Log.WriteAudit( "Entry: {0} Original :{1} New: {2}", entry.Entity.GetType().Name, oldValue, newValue); } } break; } } } } return base.SaveChanges(); } } 
 我已经在EF 4.0中使用过了。 在DbContext API中找不到GetModifiedProperties (这是避免reflection代码的关键)的相应方法。 
编辑2
  重要提示 :使用POCO实体时,上面的代码需要在开始时调用DbContext.ChangeTracker.DetectChanges() 。 原因是base.SaveChanges在这里调用得太晚了(在方法结束的时候)。  base.SaveChanges DetectChanges内部调用DetectChanges ,但是因为我们之前想要分析和logging更改,所以我们必须手动调用DetectChanges ,以便EF可以find所有已修改的属性,并正确设置更改跟踪器中的状态。 
 有可能的情况下代码可以不调用DetectChanges ,例如,如果DbContext / DbSet方法(如Add或Remove在最后修改属性后使用,因为这些方法也在内部调用DetectChanges 。 但是,如果例如一个实体刚刚从数据库加载,一些属性被改变,然后这个派生的SaveChanges被调用,自动更改检测不会发生在base.SaveChanges之前,最后导致缺less修改的属性的日志条目。 
我已经更新了上面的代码。
 您可以使用Slaumabuild议的方法,但不是覆盖SaveChanges()方法,而是可以处理SavingChanges事件以实现更简单的实现。 
看来Slauma的答案不会审核对复杂types属性内部属性的更改。
如果这对你是一个问题,我的答案可能会有所帮助。 如果该属性是一个复杂的属性,它已经改变了,我将整个复杂的属性串行化为审计日志logging。 这不是最有效的解决scheme,但它不是太糟糕,它完成了工作。
 我真的很喜欢Slauma的解决scheme。 我通常更喜欢跟踪修改表并logging主键。 这是一个非常简单的方法,你可以用它来调用getEntityKeys(entry) 
  public static string getEntityKeys(ObjectStateEntry entry) { return string.Join(", ", entry.EntityKey.EntityKeyValues .Select(x => x.Key + "=" + x.Value)); } 
请参阅使用entity framework4.1 DbContext更改跟踪以进行审计日志logging 。
用DbEntityEntry。 详细审计添加,删除,修改