本文章是由機器翻譯。

資料點

針對網域導向設計撰寫程式碼:進行資料型開發的秘訣,第 3 篇

Julie Lerman

下载代码示例

Julie Lerman這是我關於説明資料集中的開發人員,環繞其周圍的一些更具挑戰性的編碼概念使用與 Domain-Driven 設計 (DDD) 元首系列的最後一期。作為 Microsoft.NET 框架開發人員使用Entity Framework(EF) 和資料第一 (和甚至資料庫-第一次) 發展的歷史悠久,我掙扎著和爭辯說,哀我瞭解如何合併我與一些 DDD 的實現技術技能的方式。即使我不在專案中使用充分的 DDD 執行 (從一路到代碼的用戶端交互),我仍受益大大的許多 DDD 的工具。

在這最後一部分中,我將討論 DDD 編碼的兩個重要技術模式和它們適用于物件-關係映射 (ORM) 工具如何使用,EF。在較早的分期付款我談一對一關聯性。在這裡,我將探討單向關係 — — 與 DDD 首選 — — 以及它們是如何影響您的應用程式。這種選擇會導致一個困難的決定:認識到當你可能是沒有很好的關係"魔術"EF 執行的一些更好。我也會有點談平衡之間一個聚合根和一個存儲庫中的任務的重要性。

建立單向關係從根

從我開始建立模型與 EF 時間,雙向關係已經成為規範,和我這樣做是不想太在意它。它有意義,以便能夠在兩個方向中導航。如果您有訂單和客戶,很高興能夠看到客戶的訂單和給定一個訂單,很方便,訪問客戶資料。不加思索,我還建立了訂單和其行專案之間的雙向關係。從訂單的行專案的關係有意義。但如果你停止這考慮了一會兒的情況,有一個行專案,並且需要返回到它的順序是少之又少。一個我能想到的是你要彙報產品和想要做一些分析什麼常用訂購的產品在一起或涉及到客戶或航運資料分析。在這種情況下,您可能需要從產品定位到在其中所載的行專案,然後回順序。不過,我看這來了只有在那裡我不太可能需要與 DDD 為重點物件的工作報告所述情況下。

如果我只需要從順序導航到行專案,最有效的方式來描述這種關係在我的模型是什麼?

如上所述,DDD 更喜歡單向關係。Eric埃文斯勸告"它是重要的是要約束關係盡可能多"而"瞭解域可能揭示自然的定向偏向"。管理複雜的關係 — — 尤其當你依靠Entity Framework,以維護協會 — — 絕對是一個領域,可能會導致很多混亂。我已經寫下專門協會Entity Framework中的資料點列數了。任何可以被刪除程度是複雜的可能有益。

考慮了簡單的銷售模式,我用過的這一系列關於 DDD,它不會呈現一種偏見令其行專案的方向。我不能想像創建、 刪除或編輯一個行專案,而無需從訂單開始。

如果你回看我建造較早前在該系列中的順序聚合,該命令不會控制行專案。例如,您需要使用的訂單類的 CreateLineItem 方法來添加新行專案:

public void CreateLineItem(Product product, int quantity)
{
  var item = new LineItem
  {
    OrderQty = quantity,
    ProductId = product.ProductId,
    UnitPrice = product.ListPrice,
    UnitPriceDiscount = CustomerDiscount + PromoDiscount
  };
  LineItems.Add(item);
}

對類型都有一個訂單 id 屬性,但沒有順序屬性。 這意味著它是可以設置的值的訂單 id,但您不能從對導航到實際訂單實例。

在這種情況下,我有,埃文斯的話說,"施加遍歷方向"。我有,實際上,確保我可以遍歷從訂單到對,但不是在另一個方向。

有這種方法不僅在模型中,也在資料層中的影響。 我用Entity Framework作為我的 ORM 工具和它理解這種關係還不夠簡單地從訂單類的 LineItems 屬性。 並且因為我碰巧遵循的約定 EF,它理解 LineItem.OrderId 是我回的訂單類的外鍵屬性。 如果我使用一個不同的名稱為訂單 id,事情就更複雜的Entity Framework。

但在這種情況下,我可以添加新的對現有訂單像這樣:

order.CreateLineItem(aProductInstance, 2);
var repo = new SimpleOrderRepository();
repo.AddAndUpdateLineItemsForExistingOrder(order);
repo.Save();

順序變數現在表示與預先存在的秩序和單一的新對關係圖。 預先存在的訂單已經來從資料庫和已經有一個值,在訂單 id,但新對已只有它的訂單 id 屬性的預設值,即 0。

我的存儲庫方法採用該順序圖、 將它添加到我的 EF 上下文,然後應用正確的狀態,如中所示圖 1

圖 1 適用于順序圖的狀態

public void AddAndUpdateLineItemsForExistingOrder(Order order)
{
_context.Orders.Add(order);
_context.Entry(order).State = EntityState.Unchanged;
foreach (var item in order.LineItems)
{
  // Existing items from database have an Id & are being modified, not added
  if (item.LineItemId > 0)
  {
    _context.Entry(item).State = EntityState.Modified;
  }
}
}

萬一您不熟悉 EF 行為,Add 方法將導致要開始跟蹤圖 (順序和單一的行專案) 中的所有內容的上下文。 同時,在圖中的每個物件被標記添加狀態。 但這種方法的重點是使用一個預先存在的秩序,因為我知道順序並不是新和,因此,該方法通過將它設置為未更改修復命令實例的狀態。 它還檢查有任何預先存在的 LineItems 並將其狀態設置為已修改,因此他們就會在資料庫中更新而不為新插入。 在更充實出應用程式中,我會用一種模式,更明確地知道每個物件的狀態,但我不想此示例以深陷泥潭的其他詳細資訊。 (你可以看到這種模式的早期版本在羅恩Miller的博客 bit.ly/1cLoo14,和在我們合著的書更新的示例"程式設計Entity Framework:DbCoNtext"[O'Reilly 媒體,2012年])

因為雖然上下文跟蹤物件,所有這些行動正在完成,Entity Framework也"神奇"地在我新的對實例中修復訂單 id 的值。 因此,我打電話給保存的時候,對知道的訂單 id 值為 1。

放手的 EF 關係管理魔法 — — 更新

這個好運氣出現的原因我對類型發生跟隨 EF 公約與外鍵名稱。 如果你它命名為訂單 id,如 OrderFK,別的你要對你的類型 (例如,引入不想要的順序導覽屬性) 進行一些更改,然後指定 EF 映射。 這不是可取的因為你會增加複雜性,只是為了滿足 ORM。 有時也許是必要的但當它不是我更喜歡能夠避免它。

它將更簡單,只是放手的 EF 關係魔法任何依賴和控制您的代碼中的外鍵的設置。

第一步是要告訴 EF 忽略這種關係 ; 否則,它將繼續尋找一個外鍵。

這裡的代碼我會在方法重寫,以便 EF 不會注意到這種關係的 DbCoNtext.OnModelBuilder 中使用:

modelBuilder.Entity<Order>().Ignore(o => o.LineItems);

現在,我也會控制的關係。 重構所以向對,需要訂單 id 和其他值,添加一個建構函式,它使對更多這意味著喜歡 DDD 實體,所以我很高興。 我還必須修改的 CreateLineItem 方法,以便使用該建構函式,而不是物件初始值設定項。

圖 2 顯示的存儲庫方法的更新的版本。

圖 2 存儲庫方法

public void UpdateLineItemsForExistingOrder(Order order)
{
  foreach (var item in order.LineItems)
  {
    if (item.LineItemId > 0)
    {
      _context.Entry(item).State = EntityState.Modified;
    }
    else
    {
      _context.Entry(item).State = EntityState.Added;
      item.SetOrderIdentity(order.OrderId);
    }
  }
}

我不再添加的順序關係圖,然後固定的順序狀態到未更改的通知。 事實上,因為 EF 是不知道這種關係,如果我調用上下文。Orders.Add(order),它會添加訂單實例,但不會添加相關的行專案以前, 一樣。

相反,我遍歷圖的行專案和將現有行項的狀態設置為已修改不僅設置添加到新的狀態。 我使用的 DbCoNtext.Entry 語法做兩件事。 它設置的狀態之前,它會檢查到看看上下文都已經意識到 (或"跟蹤") 該特定實體。 如果不是,然後內部它重視實體。 現在它是能夠回應代碼設置國家財產的事實。 所以在這一行代碼,我是附加,設置對的狀態。

我的代碼現在是另一個健康處方使用 EF 的 DDD,是符合:不要依靠 EF 來管理的關係。 EF 執行大量的魔法,在許多情況下的巨額獎金。 我已經愉快地受益這多年。 但 DDD 聚合,真的要為管理這些在你的模型中的關係並不依賴于資料層,來為您執行必要的操作。

因為我被困正在為我的鍵 (例如,Order.OrderId) 使用整數和取決於我的資料庫提供的這些鍵的值的時間,我需要做一些額外的工作,例如新訂單行專案的新聚合存儲庫中。 我需要嚴格控制持久性的所以我可以使用插入圖表的老式模式:插入順序獲取新資料庫生成的訂單 id 值應用到新的行專案,、 將它們保存到資料庫。 這是必要的因為我打破 EF 通常用來執行這個魔法的關係。 你可以看到在示例下載中,怎麼我實現了這個存儲庫中。

我準備好了許多年後,根據要創建我的識別碼的資料庫停止,並開始使用 Guid 對於我關鍵的值,可以生成並且在我的應用程式中分配。 這使我對進一步分離我的域從資料庫。

保持 EF 關係管理魔術 — — 查詢

放棄我的模型的 EF 的關係真被幫在前面的方案中執行更新。 但我不想失去所有的 EF 的關係特點。 載入相關的資料從資料庫查詢時是我不想放棄的一個功能。 是否預先載入、 延遲載入或顯式載入抱歉我愛從帶沿的相關的資料而無需表達和執行附加查詢的 EF 能力中受益。

這是哪裡的關注概念分離擴展的視圖來發揮。 以下為設計 DDD 戒律,時,不尋常有類似的類的不同表示形式。 例如,你可以這樣做與客戶類設計為客戶管理,而不是簡單地填充需要只有客戶的名稱和識別碼的選取清單的客戶類的上下文中使用。

它也有意義 DbCoNtext 的不同定義。 在那裡你要檢索的資料的情況下,您可能希望是意識的順序和 LineItems 之間的關係,所以你熱切地可以從資料庫中載入順序以及其行專案的上下文。 但然後,當早些時候的一樣,執行更新,您可能需要顯式忽略這種關係,所以你可以有更精細的控制,您的域的上下文。

一個極端的觀點,這為複雜的問題,你可能會解決與軟體的某一子集是調用命令查詢責任分離 (CQRS) 模式。 CQRS 指導您想作為單獨的系統,可能需要完全不同的模型和體系結構的資料檢索 (讀取) 和資料存儲 (寫入)。 我小的例子,突出顯示的資料檢索操作的好處擁抱比資料存儲操作,給你一個想法什麼 cqrs 體系可以説明您實現關係的不同的理解。 你可以瞭解更多關於 CQRS 從優秀資源,CQRS 的旅程,可在 msdn.microsoft.com/library/jj554200

資料訪問發生在存儲庫中,不聚合的根

我想要備份有點現在,解決最後一個問題,吞噬了我開始專注于單向關係。 (這是不是說我有沒有更多的疑問 DDD,但這是我會解決這一系列中的最後一個主題)。關於單向關係這個問題是一項共同為我們"資料庫為先"的思想家:在哪裡,完全 (與 DDD),資料訪問發生?

EF 第一次發佈時,它可以與資料庫一起工作的唯一方法是現有的資料庫進行反向工程。 因此,如前所述,我已經習慣被雙向每個關係。 如果資料庫中的客戶和訂單表主鍵主鍵/外約束描述一對多的關係,我看到模型中的那一對多關聯性。 客戶使用了導覽屬性對訂單的集合。 訂單了導覽屬性的實例的客戶。

隨著事情的發展演變至模型和代碼-第,哪裡可以描述模型,並生成一個資料庫,我繼續按照這種方式定義上的一種關係兩端的導覽屬性。 EF 是快樂、 映射是簡單和編碼是更自然。

所以,與 DDD,當我發現自己,認識到客戶 id 的順序聚合根,或者甚至是一個完整的客戶類型,但我不能從順序導航回客戶,我感到不安。 我問的第一個問題,"如果我想要找到所有的客戶的訂單嗎?"我一直以為我會需要,以便能夠這樣做,和我習慣于依靠在兩個方向都有對導航的訪問。

如果邏輯從開始我順序聚合根,如何將我過回答這問題? 我起初也曾誤以為你盡一切努力通過聚合的根,沒有説明。

該解決方案讓我打我的頭,覺得自己有點愚蠢。 我分享我的愚昧,以防別人卡住方式相同。 它不是聚合根、 作業或作業的順序,幫我回答這個問題。 然而,在訂單集中存儲庫,這是將用於執行我的查詢和持久性,是沒有理由我不能有一種方法來回答我的問題:

public List<Order>GetOrdersForCustomer(Customer customer)
  {
    return _context.Orders.
Where(o => o.CustomerId == customer.Id)
      .ToList();
  }

該方法返回的順序聚合根的清單。當然,如果我創造這做 DDD 的範圍內,我不會只困擾投入我知道它的存儲庫中的方法要需要在特定的上下文,不"只是在情況下"。很有可能我會需要它在本報告所述的應用程式或類似,但不是一定在為生成銷售訂單而設計的上下文中。

才剛開始我的追求

我學到關於 DDD 在過去幾年,我在本系列中所涉及的主題將是那些過最難理解或弄清楚如何實現當Entity Framework將我的資料層的一部分。一些我所遇到是思考的挫折的由於多年我視角的一切將會如何在我的資料庫的軟體。放手的這一觀點已被釋放因為它能讓我專注于手頭的問題 — — 我在為其設計軟體的域問題。同時,我需要找到健康的平衡,因為可能有資料層問題時遇到時它是時間,添加到我的解決方案。

雖然我專注的東西可能會如何工作時我我我類直接後映射到該資料庫的Entity Framework,它是重要的考慮可能有另一層 (或多個) 之間的域邏輯和資料庫。例如,您可能有一個與您的域邏輯進行交互的服務。此時,資料層是後果的的小 (或沒有),映射到從您的域邏輯 ; 這個問題現在屬於服務。

有許多方法來處理您的軟體解決方案。甚至當我不執行一個完整的端到端 DDD 方法 (東西,需要相當多的掌握),我的整個過程繼續受益的經驗教訓和技術我學習從 DDD。

Julie Lerman* 是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。你可以找到她提出關於資料訪問和使用者組和會議,世界各地其他 Microsoft.NET 框架主題。她是《Programming Entity Framework》(2010) 以及“代码优先”版 (2011) 和 DbContext 版 (2012)(均出自 O’Reilly Media)的作者,博客网址为 thedatafarm.com/blog。通过她的 Twitter(网址为 twitter.com/julielerman)关注她,并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。*

衷心感谢以下技术专家对本文的审阅:Stephen Bohlen (Microsoft)