EntityFramework之領域驅動設計實踐(三)

案例:一個簡易的銷售系統

從現在開始,我們將以一個簡易的銷售系統爲例,探討EntityFramework在領域驅動設計上的應用。爲了方便討論,我們的銷售系統非常簡單,不會涉及客戶存在多個收貨地址的情況,也不會包含任何庫存管理的內容。假設我們的系統只需要維護產品類型、產品以及客戶信息,並能夠幫客戶下訂單、跟蹤訂單狀態,以及接受客戶退貨。從簡單的分析我們大致可以瞭解到,這個系統將會有如下實體:客戶、單據、產品及其類型。單據分爲銷售訂單和退貨單兩種,每個單據可以有多個單據行(比如銷售訂單行和退貨單行)。不僅如此,系統允許每個客戶有多張信用卡,以便在結賬的時候,選擇一張信用卡進行支付。在使用EF的Entity Data Model Designer進行設計後,我們得到下面的模型:

71531210423

上面的模型表述了領域模型中各個實體及其之間的關係。我們先不去討論整個系統的業務會是什麼樣的,我們先看看EF是如何支持實體和值對象概念的。

 

實體

首先看看實體這個概念。在領域驅動設計的理論中,實體是模型中需要區分個體的對象,也就是說,針對某種類型,我們既要知道它是什麼,還需要知道它是哪個。我在前面的博文中有介紹過實體這個概念。實體都有一個標識符,以便跟同類型的其它實體進行區分。EF Entity Data Model Designer上能夠畫出的都是實體,你可以看到每個實體都有個Id成員,其Entity Key屬性被設置爲True,同時被分配了一種標識符的生成方式,如下圖所示:

71541799262

在從領域模型映射到數據模型的過程中,這個標識符通常都是被映射爲關係數據庫某個數據表的主鍵,這個應該是很容易理解的。

其次,EF不支持實體行爲,因此,整個模型只能被稱爲Entity Data Model,而不是Entity Model,因爲它只支持對實體數據的描述。幸虧從.NET 2.0開始,託管語言開始支持partial特性,同一個類可以以部分類(partial class)的特性寫入多個代碼文件中。因此,如果需要向上述模型中的實體加入行爲,我們可以在工程中加入幾個代碼文件,然後使用部分類的特點,爲實體添加必要的行爲。比如,下面的部分類向訂單行中加入了一個只讀屬性,該屬性用於計算某一單據行所擁有的總金額:

71555271864

有朋友會問,爲什麼我們要另外使用部分類,而不是直接在模型文件 edmx的源代碼上直接修改?因爲這個源代碼文件是框架動態生成的,如果在上面修改,等下次模型被更新的時候,你所做的更改便會丟失。

對於實體的行爲,EF支持從數據庫存儲過程生成實體對象行爲的過程。對此,我持批判態度:EF把數據模型與實體模型混爲一談了,這種做法只能讓軟件人員感到更加困惑。我在下一篇文章將重點表述我對這個問題的看法。我也相信微軟在下一代實體框架中能夠處理好這個問題。

再次,EF對實體對象關係的支持主要有關聯和繼承。根據Multiplicity的設置,關聯又可以支持到組合關聯與聚合關聯。我覺得EF中對繼承關係的支持是一個亮點。繼承表述了“什麼是一種什麼”的觀點,比如在我們的案例中,“銷售訂單”和“退貨單”都是一種“單據”。如果從傳統的數據庫驅動的設計方案,我們很自然地會使用“Orders”數據表中的整型字段“OrderType”來保存當前單據的類型(比如0表示銷售訂單,1表示退貨單),那麼,在獲取系統中所有銷售訂單的時候,我們會使用下面的代碼:

隱藏行號 複製代碼 獲取所有銷售訂單的僞代碼
  1. List<Order> GetSalesOrders(IDbConnection connection)
    
  2. {
    
  3.     IDbCommand command = new SqlCommand("SELECT * FROM [Orders] WHERE [OrderType]=0",
    
  4.         (SqlConnection)connection);
    
  5.     List<Order> orders = new List<Order>();
    
  6.     using (IDataReader dr = command.ExecuteReader())
    
  7.     {
    
  8.         while (dr.Read())
    
  9.         {
    
  10.             Order order = new Order();
    
  11.             order.Id = Convert.ToInt32(dr["Id"]);
    
  12.             // ...
    
  13.             orders.Add(order);
    
  14.         }
    
  15.         dr.Close();
    
  16.     }
    
  17.     return orders;
    
  18. }
    
  19. 
    

從技術角度講,上面的代碼沒什麼問題,運行的也很好,能夠獲得系統中所有銷售訂單的列表。但是, [OrderType]=0這種寫法並不包含任何領域語義,如果讓另一個開發人員來跟進這段代碼,他不得不先去查閱其它項目文檔,以瞭解這個 [OrderType]=0的具體涵義。在引入了繼承關係的EF中,我們只需要下面的Linq to Entities,即可既方便、又明瞭地獲得所有銷售訂單的列表了:

隱藏行號 複製代碼 使用LINQ to Entities後的代碼
  1. List<Order> GetSalesOrders()
    
  2. {
    
  3.     using (EntitiesContainer container = new EntitiesContainer())
    
  4.     {
    
  5.         return (from order in container.Orders
    
  6.                 where order is SalesOrder
    
  7.                 select order).ToList();
    
  8.     }
    
  9. }
    
  10. 
    

簡單明瞭吧?EF帶給我們的不僅僅是一個技術框架,也不僅僅是一個數據存取的解決方案。

 

值對象

EF支持值對象,這很好!在EF中可以定義Complex Types,而一個Complex Type下可以定義多個Primitive Type和多個Complex Type。與LINQ to SQL相比,這是一大進步。

91434422541

 

 

對於值對象的兩點問題我在第一篇文章中已經講過了,在此就不重複了。

綜上所述,EF基本上能夠支持領域驅動設計的思想(即使有些方面不完善,但目前也可以找到替代的方案)。我想,只要能夠對領域驅動有清晰的認識,就能夠很好地將實體框架應用於領域驅動的實踐中。

自出:http://www.cnblogs.com/daxnet/archive/2010/07/07/1772593.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章