2011-05-18 67 views
5

我讀過DDD Evans,並且正在使用C#和Entity Framework 4.1 + LINQ試驗一個聚合根存儲庫設計。我應該在EF 4.1 + LINQ中使用DDD聚合根存儲庫嗎?

但是,我擔心發送到數據庫的實際查詢。我正在使用SQL 2008 R2,並運行SQL事件探查器來檢查數據庫響應LINQ代碼所做的事情。

考慮使用Person和EmailAddress的簡單2實體設計。一個人可以有零到多個EmailAddress,而一個EmailAddress只能有一個Person。 Person是聚合根,因此不應該有電子郵件地址的存儲庫。電子郵件地址應該從Person存儲庫中選出(根據DDD Evans)。

爲了比較,我確實爲電子郵件地址設置了一個臨時存儲庫。下面的代碼行:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1] 

我可以選擇電子郵件出人庫中,用下面的代碼:

var emailString = "[email protected]"; 
var emailEntity = _tempEmailRepository.All.SingleOrDefault(e => 
    e.Value.Equals(emailString, StringComparison.OrdinalIgnoreCase)); 

...根據探查執行一個乾淨的SQL查詢:

var emailEntity = _personRepository.All.SelectMany(p => p.Emails) 
    .SingleOrDefault(e => e.Value.Equals(emailString, 
     StringComparison.OrdinalIgnoreCase)) 

這讓我在運行相同的實體,但不同的命令顯示了在SQL事件探查器:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName], 
FROM [dbo].[Person] AS [Extent1] 

除了從人選擇上面的查詢,有許多的「RPC:已完成」事件,一個用於在DB每個EmailAddress的行:

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1] 
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1 

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1] 
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=2 

我的測試分貝有14行在dbo.EmailAddress中,並且有14個不同的RPC:已完成的調用,每個都有不同的@ EntityKeyValue1值。

我假設這對SQL性能不利,因爲dbo.EmailAddress表獲得更多行,更多的這些RPC將在db上調用。在EF 4.1 + LINQ中使用DDD聚合根存儲庫還有另一種更好的方法嗎?

更新:解決

的問題是,所有的財產返回一個IEnumerable<TEntity>。在這改爲IQueryable<TEntity>之後,LINQ一下子就進入並選擇了整個Person + Emails。但是,我必須鏈接.Include(p => p.Emails)才能從All返回IQueryable。

+2

你從'All'屬性返回什麼? – 2011-05-18 15:26:36

+0

好問題。當我發佈這個所有返回一個IEnumerable 。將其更改爲IQueryable並查看其差異。將發佈更新。 – danludwig 2011-05-18 19:23:17

回答

12

鑑於現代ORM已經給你的抽象級別,我個人建議不要在你和你的數據庫之間增加一個抽象層。除了重新發明輪子之外,您會發現在服務層中直接使用所選的ORM可以更好地控制查詢,獲取和緩存策略。

Ayende的系列Wages of Sin是反對使用規範/存儲庫與現代ORM的各種其他觀點的一個很好的資源,特別是考慮到LINQ已經爲您提供了幾乎所有您可能需要的東西。

我在過去的項目中走過了「DDD」的路線(在引號中,因爲它當時對DDD的理解是必然的)。事後看來,我認識到,在公開辯論中,DDD通常被縮減爲應用這些模式是一種恥辱。我陷入了陷阱,我希望我能幫助別人避免它。

存儲庫和規格是基礎設施模式。 基礎設施是爲了達到目的,而不是爲了自己的目的。談到基礎架構,我主張嚴格應用重用抽象原則。爲了快速總結,RAP表示,如果並且只有當它將被超過2個消費者使用並且額外的抽象層實際上實現某些行爲,則應該引入抽象。如果你只引入一個抽象來將你與某些東西(比如ORM)分離開來,那麼要非常小心,這很可能最終會導致漏洞的抽象。

DDD的重點在於讓您的域模型與您的基礎架構分開,並使您的域模型儘可能具有表現力。沒有證據表明,如果不使用存儲庫,這是無法實現的。存儲庫僅僅是爲了隱藏數據訪問的細節,ORM已經有了。 (在一個側面說明中,考慮到DDD書的年齡,我認爲ORM的常見用途不在當時)。現在,存儲庫對強制執行聚合根等是有用的。但是,我認爲應該通過在「讀取」操作(查詢)和「寫入」操作(命令)之間作出明確的區分來對待它。只有後者的領域模型應該是相關的,查詢往往更適合於量身定製(並且更靈活)的模型(比如DTO或者匿名對象)。

規格的情況類似。規格的預期目的是相似的。他們的力量在於構建用於查詢對象的領域特定語言的元素。隨着LINQ的出現,大部分提供用於組合這些元素的泛型規範模式的「粘合劑」已經過時。提示:看看Predicate Builder(C#的50行代碼),很可能你需要實現規範。

總結這個漫長的(希望不是太混亂,我將重新後來我希望)職位:

  1. 不要去狂愛的基礎設施,構建它,當您去。
  2. 將您的域模型用於域特定行爲,而不是用於支持您的視圖。
  3. 專注於DDD更重要的部分:使用聚合根,建立你無處不在的語言,確保與業務專家的良好溝通。
+0

上週我讀過這本書,感謝您提供乾淨的單詞/建議。我想我需要從另一個角度重讀這本書 – Khh 2011-05-18 21:07:40

+0

所有優秀的討論要點。在我們的案例中,我認爲存儲庫模式仍然是合理的:在某些實體中編輯數據時,原始數據將保存在數據庫中。因此,任何特定的實體都可以有多個修訂版,其中只有一個是當前版本。這將會違反SRP來爲服務層提供這些擔憂,所以我們應用了實體層超類型+存儲庫(+ UoW)來封裝它們。現在的目標是保持存儲庫儘可能薄,輕,少。去讀這些罪的工資現在... – danludwig 2011-05-20 13:22:39

+0

我不得不不同意。嘗試在ORM中建模一個簡單的「這個對象不應該將'name''屬性設置爲'John'」的案例。我還沒有找到EF或NHibernate或其他任何可行的解決方案。 – drozzy 2013-07-29 16:44:31