2011-05-27 212 views
39

我想实现一个受约束的'审计日志'属性更改到一组类中的属性。我已经成功地找到了如何设置CreatedOn | ModifiedOn类型的属性,但我无法找到如何'找到'已被修改的属性。实体框架4.1 DbContext覆盖SaveChanges审计属性更改

例子:

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这些细节的更改属性的方法吗?

回答

44

非常,非常粗略的想法:

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内部的反射内容可能会成为性能噩梦。

编辑

也许是更好的访问底层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(这是避免反射代码的关键)的相应方法。

编辑2

重要:当使用POCO实体工作上面的代码需要在开始时调用DbContext.ChangeTracker.DetectChanges()。原因是base.SaveChanges在这里调用得太晚了(在方法结束时)。 base.SaveChanges在内部调用DetectChanges,但由于我们之前想要分析和记录更改,因此我们必须手动调用DetectChanges,以便EF可以找到所有已修改的属性并正确设置更改跟踪器中的状态。

有可能的情况下的代码无需调用DetectChanges如果由过去的属性修改后,因为这些方法也叫DetectChanges内部使用的DbContext/DbSet方法,如AddRemove工作,例如。但是,如果例如一个实体刚刚从DB加载,则会更改一些属性,然后调用此派生的SaveChanges,在base.SaveChanges之前不会发生自动更改检测,最终会导致缺少修改的属性的日志条目。

我已经更新了上面的代码。

+0

这个工程,但似乎所有的属性都根据这个修改。将进一步调查,但对原来的问题罚款。我认为这给了我需要的东西。 – 2011-06-01 18:32:52

+0

这不适合我。旧的价值永远是空的。这只适用于POCO和4.1及更高版本吗? – Jonx 2012-02-06 22:06:11

+0

@Jonx:是的,它是EF 4.1('DbContext')和POCO。对于<= EF 4.0('ObjectContext'),你可以做类似的事情,唯一的区别是你不会覆盖SaveChanges,而是实现一个事件处理器('OnSavingChanges'或类似的,我不记得)。 – Slauma 2012-02-07 21:20:05

7

您可以使用Slauma建议的方法,但不是覆盖SaveChanges()方法,而是可以处理SavingChanges事件,以实现更容易的实现。

+0

哦,好点。其实,我上面的代码片段来自'SavingChanges'处理程序,我忘记了。但为什么它让事情变得更简单?这与写日志需要做的事情基本不一样吗? – Slauma 2011-05-27 20:45:07

+0

如果您需要将其添加到它,而不是修改您的覆盖,它可以更容易,您可以添加一个处理程序。您也不必担心传递原始功能,我相信您的示例中不存在这些功能。这些对象永远不会被保存。如果你只是写了一个处理程序,你不必担心这个。 – Jay 2011-05-27 20:51:35

+0

不错,谢谢,我忘了调用基类的SaveChanges(现在添加)。 (这是因为我从我的实际'SavingChanges'处理程序复制了片段的主要部分。) – Slauma 2011-05-27 20:58:43

1

看来Slauma的答案不会审核对复杂类型属性的内部属性的更改。

如果这对您是个问题,我的回答here可能会有所帮助。如果该属性是一个复杂属性,并且它已经更改,则将整个复杂属性序列化为审计日志记录。这不是最有效的解决方案,但它不是太糟糕,它完成了工作。

+0

这可能是因为复杂类型没有代理,请参考https://blog.oneunicorn.com/2012/03/13/secrets-of-detectchanges-part-4-binary-properties-and-complex-types/ – 2016-06-01 12:28:10

1

我真的很喜欢Slauma的解决方案。我通常更愿意跟踪修改后的表并记录主键。这是一个非常简单的方法,你可以用它来做到这一点,调用getEntityKeys(entry)

public static string getEntityKeys(ObjectStateEntry entry) 
    { 
     return string.Join(", ", entry.EntityKey.EntityKeyValues 
           .Select(x => x.Key + "=" + x.Value)); 
    } 
相关问题