4

大多數大型項目的一個常見方面是需要在許多域實體上使用公共跟蹤數據。例如,大多數大型項目,跟蹤以下屬性爲衆多領域中的實體:域實體跟蹤數據的最佳實踐?基礎課程還是作文?

DateTime DateCreated 
User CreatedBy 
DateTime LastModified 
User LastModifiedBy 

這個數據是不言自明的,該數據是用來跟蹤誰做了什麼時,Domain對象。

問題是在爲大型應用程序設計領域模型時,處理此跟蹤數據的最佳方法是什麼。

經典的方法是使用基類,然後讓相關的域類從該基類繼承。但是,這引發了我的favor composition over inheritance警鐘。我工作的項目越多,我越是拒絕繼承,但這並不是說有些情況下它是最好的選擇,例如在這種情況下。另一種繼承解決方案是使用接口,但雖然此解決方案耦合性較差,但我沒有看到在域實體上使用此方法的許多示例。

第二種方法是使用組合爲每個域實體添加排序的跟蹤對象。唯一的問題是數據層必須特別指示不要將它們表示爲單獨的表格。這是一項小任務,但如果沒有回報,很難說明問題。

處理跟蹤數據的最後一種方法是配置數據層以透明地執行此操作。我認爲這可能與Entity-Framework這樣做,但過去未實現此解決方案,這將是最先期的時間密集型解決方案。很難預見這種解決方案是否值得遇到麻煩。

儘管這個問題看起來很客觀,但這實際上是大多數大型項目必須以這種或那種方式處理的常見任務。

什麼是設計域模型和/或大項目跟蹤元數據的最佳方式?

+1

對於組合,你可以添加一個複雜的類型,它將映射到同一個表中的列(即不是一個單獨的表) – qujck 2013-02-22 16:14:40

回答

2

最佳實踐往往是主觀的,會導致儘可能多的問題,因爲它解決了。你應該什麼時候繼承?你應該什麼時候寫作?學者們花了數年的時間來爭論一個問題的細節。具有簡單界面的基本繼承是實用和有效的。如果它是的全部的標準功能,那麼您的實體繼承可能是更好的選擇。

我有一個具有審計屬性的基類,併爲這些屬性實現了一個接口。我用下面的代碼攔截了對context.SaveChanges()的呼叫。這很簡單,它的工作原理。如果任何被跟蹤的實體不實現IAudit接口,它可能會延長到失敗。

public override int SaveChanges() 
    { 
     var entities = this.GetChangedAuditDataEntities(); 

     foreach (var entity in entities) 
     { 
      this.SetModificationInfo(entity); 
     } 

     return base.SaveChanges(); 
    } 

    private IEnumerable<IAuditData> GetChangedAuditDataEntities() 
    { 
     return (
      from entry in _context.ChangeTracker.Entries() 
      where entry.State != EntityState.Unchanged 
      select entry.Entity) 
      .OfType<IAuditData>(); 
    } 

    private void SetModificationInfo(IAuditData entity) 
    { 
     entity.lastModifiedBy = _currentUser.Name; 
     entity.lastModified = System.DateTime.Now; 
    } 

這是一個不自動有一個「正確」的答案的問題之一。如果喬恩Skeet要回答它,那麼它將被認爲是最佳做法。唯一的其他正確答案將不得不證實你的偏見,或者至少打擊正確的智力記錄。

多年前,繼承權很盛行,其中一個推動力是「價值構成超越繼承」的口頭禪。好,但繼承是它的地方。

我建議任何具有許多相同類型對象的體系結構層(如域對象(「域對象」意味着通用層)可以通過擁有共同的基類大大增強。 System.Object就是一個很好的例子。我會給你另一個例子。當我們爲我們的解決方案定義異常裝飾器時,我們決定擴展ToString()方法來創建一個唯一標識對象的值。

public override string ToString() 
{ 
    if (this is IAuditData) 
    { 
     IAuditDataidentifiable = this as IAuditData; 
     return string.Format(@"{0} {{ id: {1}, ETag: {2} }}", 
      identifiable.GetType().Name, 
      identifiable.id, 
      identifiable.ETag); 
    } 
    else return base.ToString(); 
} 

7行代碼 - 務實,簡單,有效。從你的問題來看,你完全反對繼承,而這種繼承必須由許多燒焦的手指承擔。我也是;-)但我仍然斷言在這種情況下它是更好的選擇。

+0

雖然我對最佳實踐有不同的看法,但我可以看到你的觀點,他們可能導致困境。但是,如果**大多數**,但是所有**域實體都不需要此跟蹤數據,您還會使用基類嗎? – 2013-02-22 16:03:13

+1

是的,但我只會繼承那些需要審計數據的類。 – qujck 2013-02-22 16:10:16

+0

你對所有實體使用基類(實體)嗎?這會讓你的查詢真的很慢,因爲它會連接繼承樹的所有表。 – Jehof 2013-02-26 09:36:38

0

我喜歡qujck在他的回答和他的評論中所說的話,但是如果跟蹤信息代表基礎設施概念,我會去把它們放在一些負責審計或記錄等的基礎設施層中。 。否則,如果它們是領域概念,我真的很想保留它們,因爲它們屬於領域模型層,在這種情況下,我會使用一個複雜的對象(值對象)來表示它們,並減輕我設置它們的負擔手動每當我創建一個跟蹤對象時,我會定義一個方法,當我請求跟蹤的對象時,我的工廠或IoC容器會調用這個方法,我的觀點是集中改變並且保留它們所屬的概念。

+1

我同意工廠/ IoC的觀點,但實體框架不支持使用您自己的容器。可以捕獲底層ObjectContext.ObjectMaterialized事件,但我更願意讓IoC容器注入一個裝飾器類來管理審計日誌記錄 - 我有一個UnitOfWork類,它調用context.SubmitChanges和一個用於管理審計日誌記錄的UnitOfWork的裝飾器。 – qujck 2013-02-24 16:02:04