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方法(如AddRemove在最后修改属性后使用,因为这些方法也在内部调用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。 详细审计添加,删除,修改