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。 详细审计添加,删除,修改