2014-06-09 49 views
3

我是在實現一個數據庫審計線索,從而CRUD操作通過我的控制器在我的Web API項目的執行將序列的新老POCO的和存儲他們的值,以後檢索(中間歷史,回滾等)。實體框架代碼優先和SQL Server 2012的序列

當我得到它的所有工作,我不喜歡它是如何使我的控制器在POST期間看起來,因爲我最後不得不打電話SaveChanges()兩次,一次爲插入的實體獲取ID,然後再次提交審計記錄哪些需要知道該ID。

我設置了轉換項目(尚處於起步階段)使用序列來代替標識列。這還有進一步從SQL Server中抽象出來的好處,雖然這不是一個真正的問題,但它也允許我減少提交次數,並讓我從邏輯控制器中提取出邏輯並將其填充到我的服務層中從存儲庫中抽象出我的控制器,並讓我在這個「填充」層中執行審計工作。

一旦Sequence對象的創建和存儲過程來揭露它,我創建了以下類:

public class SequentialIdProvider : ISequentialIdProvider 
{ 
    private readonly IService<SequenceValue> _sequenceValueService; 

    public SequentialIdProvider(IService<SequenceValue> sequenceValueService) 
    { 
     _sequenceValueService = sequenceValueService; 
    } 

    public int GetNextId() 
    { 
     var value = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = 1 }).ToList(); 
     if (value.First() == null) 
     { 
      throw new Exception("Unable to retrieve the next id's from the sequence."); 
     } 

     return value.First().FirstValue; 
    } 

    public IList<int> GetNextIds(int numberOfIds) 
    { 
     var values = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = numberOfIds }).ToList(); 
     if (values.First() == null) 
     { 
      throw new Exception("Unable to retrieve the next id's from the sequence."); 
     } 

     var list = new List<int>(); 
     for (var i = values.First().FirstValue; i <= values.First().LastValue; i++) 
     { 
      list.Add(i); 
     } 

     return list; 
    } 
} 

它只是提供了兩種方式來獲得的ID,一個和範圍。

這在第一組單元測試中效果很好,但一旦我開始在真實世界的場景中測試它,我很快意識到一次調用GetNextId()將返回相同的值,直到SaveChanges()被調用,從而否定任何真正的好處。

我不確定是否有辦法創建第二個上下文(而不是選項)或去老式的ADO.NET並直接進行SQL調用並使用AutoMapper獲得相同的淨結果。這些都不吸引我,所以我希望別人有一個想法。

+0

您的審計記錄是怎樣的?每桌一桌,一桌一桌?審覈中存儲了哪些信息?您審覈了哪些操作?你有通用的數據層嗎? – JotaBe

+0

包含以下數據的單一審計表:包含舊數據或新數據的序列化POCO(或PUT情況下的兩者),受影響的表,該表中的記錄ID,更改的列的列表以及執行的操作生成審計(插入,刪除,更新)。 –

+0

然後沒有辦法做到這一點。兩個SaveChanges選項看起來像唯一的解決方案。您不應該擔心兩件事,原因有兩個:1).NET使用連接池,2)EF不會逐批發送查詢,而是逐個發送查詢。所以使用兩個SaveChanges之間的差異應該可以忽略不計。唯一的其他選擇是使用GUID作爲PK:http://stackoverflow.com/questions/18200817/how-to-set-newid-for-guid-in-entity-framework但這增加了更多的缺點(糟糕的索引性能),因爲GUID太長。 – JotaBe

回答

0

不知道這可能會幫助你,但是這是使用代碼我的審計日誌線索第一我是怎麼做。 以下代碼被編入從DbContext繼承的類中。

在我的構造函數中,我有以下

IObjectContextAdapter objectContextAdapter = (this as IObjectContextAdapter); 
objectContextAdapter.ObjectContext.SavingChanges += SavingChanges; 

這是我保存更改方法有線了以前

void SavingChanges(object sender, EventArgs e) { 
     Debug.Assert(sender != null, "Sender can't be null"); 
     Debug.Assert(sender is ObjectContext, "Sender not instance of ObjectContext"); 

     ObjectContext context = (sender as ObjectContext); 
     IEnumerable<ObjectStateEntry> modifiedEntities = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified); 
     IEnumerable<ObjectStateEntry> addedEntities = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added); 

     addedEntities.ToList().ForEach(a => { 
      //Assign ids to objects that don't have 
      if (a.Entity is IIdentity && (a.Entity as IIdentity).Id == Guid.Empty) 
       (a.Entity as IIdentity).Id = Guid.NewGuid(); 

      this.Set<AuditLogEntry>().Add(AuditLogEntryFactory(a, _AddedEntry)); 
     }); 

     modifiedEntities.ToList().ForEach(m => { 
      this.Set<AuditLogEntry>().Add(AuditLogEntryFactory(m, _ModifiedEntry)); 
     }); 
    } 

,這些都是使用previosly的方法來建立審計日誌的詳細信息

private AuditLogEntry AuditLogEntryFactory(ObjectStateEntry entry, string entryType) { 
     AuditLogEntry auditLogEntry = new AuditLogEntry() { 
      EntryDate = DateTime.Now, 
      EntryType = entryType, 
      Id = Guid.NewGuid(), 
      NewValues = AuditLogEntryNewValues(entry), 
      Table = entry.EntitySet.Name, 
      UserId = _UserId 
     }; 

     if (entryType == _ModifiedEntry) auditLogEntry.OriginalValues = AuditLogEntryOriginalValues(entry); 

     return auditLogEntry; 
    } 

    /// <summary> 
    /// Creates a string of all modified properties for an entity. 
    /// </summary> 
    private string AuditLogEntryOriginalValues(ObjectStateEntry entry) { 
     StringBuilder stringBuilder = new StringBuilder(); 

     entry.GetModifiedProperties().ToList().ForEach(m => { 
      stringBuilder.Append(String.Format("{0} = {1},", m, entry.OriginalValues[m])); 
     }); 

     return stringBuilder.ToString(); 
    } 

    /// <summary> 
    /// Creates a string of all modified properties' new values for an entity. 
    /// </summary> 
    private string AuditLogEntryNewValues(ObjectStateEntry entry) { 
     StringBuilder stringBuilder = new StringBuilder(); 

     for (int i = 0; i < entry.CurrentValues.FieldCount; i++) { 
      stringBuilder.Append(String.Format("{0} = {1},", 
       entry.CurrentValues.GetName(i), entry.CurrentValues.GetValue(i))); 
     } 

     return stringBuilder.ToString(); 
    } 

希望這可能意味着你進入一個方向,可以幫助英語新堅持你的問題。

+0

我發現使用這種方法(我將它全部連接起來並工作)是「CurrentValues」和「OriginalValues 「在分離的代碼優先存儲庫模式方法中是相同的。例如,在我的REST風格的web api中,當更新進入時,我會查找正在更新的ID(它是已分離的),然後進行必要的更改,然後將更新傳遞到存儲庫。當EF附加實體時,原始值和當前值都是相同的。這是一個已知的限制(當時不是我)的框架。你可以在這裏看到功能投票http://entityframework.codeplex.com/workitem/864 –

+0

感謝評論@JDBuckSavage這是我不知道的事情,需要去檢查我的單元測試,以確保我的應用程序正常運行。 – 3dd

相關問題