2011-05-27 138 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)); 
    } 
相關問題