天天看點

Repository 傳回 IQueryable?還是 IEnumerable?

這是一個很有意思的問題,我們一步一步來探讨,首先需要明确兩個概念(來自 MSDN):

IQueryable:提供對未指定資料類型的特定資料源的查詢進行計算的功能。

IEnumerable:公開枚舉數,該枚舉數支援在非泛型集合上進行簡單疊代。

IQueryable 繼承自 IEnumerable,它們倆最大的差別是,IQueryable 是表達式樹處理,可以延遲查詢,而 IEnumerable 隻能查詢在本地記憶體中,Repository 的概念就不多說了,在“僞 DDD”設計中,你可以把它看作是資料通路層。

下面我們先實作 Repository 傳回 IEnumerable:

上面是我們的一般接口設計,包含查詢、增加、删除操作,你發現并沒有修改,其實我們可以先通過 GetById 操作,然後取得 Book 對象,進行修改,最後執行 SaveChanges 就可以了,在持久化資料庫的時候,會判斷實體狀态值的概念,最後進行應用改變。

GetBy....() 代表了一類查詢方法,因為我們的業務比較複雜,對 Book 的查詢會千奇百怪,是以,沒有辦法,我們隻能增加各類查詢方法來滿足需求,最後可能導緻的結果是,一個 Where 對應一個查詢方法,IBookRepository 會充斥着各類查詢方法,并且這些查詢方法一般隻會被一個 Application 方法調用,如果你檢視下 GetBy....() 方法實作,會發現其實都大同小異,不同的隻是 Where 條件,這樣的結果就會導緻代碼變得非常的臃腫。

針對上面的問題,怎麼辦呢?因為 IEnumerable 是查詢在本地記憶體中,是以沒有辦法,我們隻能這樣處理,那如何使用 IQueryable 會是怎樣的呢?我們看下代碼:

隻有一個 GetBooks 查詢,那它能滿足各類查詢需求嗎?我們看下 Application 中調用的代碼:

因為 IQueryable 是延遲查詢,隻有在執行 AsEnumerable 的時候,才會真正去查詢,也可以這麼說,BookApplication 可以根據需求任意建構查詢表達式樹,就像我們在 SQL Server 中寫查詢 SQL,<code>SELECT * FORM Books</code> 在 BookRepository 中進行建構,<code>WHERE ...</code> 操作在 BookApplication 中進行建構,最後的 F5 執行也在 BookApplication 中。

關于 EntityFramework.Extended 的擴充,需要記錄下,因為這個東西确實非常好,改變了我們之前的很多寫法和問題,比如,在之前使用 EF 進行修改和删除實體,我們一般會這些寫:

上面的寫法有什麼問題呢?其實最大的問題就是,我們要進行修改和删除,必須先擷取這個實體,也就是先查詢再進行修改和删除,這個就有點多餘了,尤其是 UpdateNameByIds 中的批量修改,先擷取 Book 對象清單,然後再周遊修改,最後儲存,是不是有點 XXX 的感覺呢,仔細想想,還不如不用 EF 來的簡單,因為一個 Update SQL 就可以搞定,簡單并且性能又高,為什麼還要使用 EF 呢?這是一個坑?其實使用 EF 也可以執行 SQL,但這就像換了個馬甲,沒有什麼卵用。

針對上面的問題,該如何解決呢?很簡單,使用 EntityFramework.Extended 和 IQueryable 就可以,我們改造下上面的代碼:

有沒有發現什麼不同呢?原來 IQueryable 還可以這樣寫?這貨居然不隻是用于查詢,也可以用于删除和修改,另外,通過追蹤生成的 SQL 代碼,你會發現,沒有了 SELECT,和我們直接寫 SQL 是一樣的效果,在執行修改和删除之前,我們需要對查詢表達樹進行過濾,也就是說的,當我們最後應用修改的時候,會是在這個過濾的查詢表達樹基礎上的,比如上面的 Delete 操作,我們先通過 bookId 進行過濾,然後直接進行 Delete 就可以了,哇塞,原來是這樣的簡單。

當 BookApplication 操作變的簡單的時候,BookRepository 也會相應變的簡單:

一個 IQueryable 表達樹,一個 SaveChanges 操作,就可以滿足 BookApplication 中的所有操作。

既然 IQueryable 是這麼的強大,那用它就好了,為什麼還要讨論呢?如果你 Google 搜尋“Repository IQueryable”關鍵詞,會發現大量的相關文章,我先貼出幾個非常贊的讨論:

<a href="http://programmers.stackexchange.com/questions/192044/should-repositories-return-iqueryable">Should Repositories return IQueryable?</a>

<a href="https://duckduckgo.com/?q=repository+return+iqueryable&amp;ia=qa">Repository Return IQueryable</a>

<a href="http://codetunnel.io/should-you-return-iqueryablet-from-your-repositories/">Should you return IQueryable from Your Repositories?</a>

<a href="http://stackoverflow.com/questions/16848465/what-are-alternatives-to-using-iqueryablet-in-repository-pattern">What are alternatives to using IQueryable in Repository Pattern?</a>

<a href="https://www.paragon-inc.com/resources/blogs-posts/what-to-return">IQueryable vs List: What Should Your Repository Return?</a>

<a href="http://mikehadlow.blogspot.jp/2009/01/should-my-repository-expose-iqueryable.html">Should my repository expose IQueryable?</a>

<a href="http://blog.fire-development.com/2013/03/07/why-the-repository-pattern-is-still-valid/">Why the Repository Pattern Is Still Valid</a>

上面隻是部分,關于這類的文章,老外寫的非常多,而且評論中的讨論也非常激烈,因為英語實在差,我大概看了一些,出乎我意料之外的是,很多人都不贊成 Repository 傳回 IQueryable,但讨論的卻非常有意思,比如有個老外這樣感歎:I'm still not convinced that returning IQueryable is a bad idea, but at least I'm far more aware of the arguments against it. 大緻意思是:我仍然不相信傳回 IQueryable 是一個壞主意,但至少我更了解他們的反對理由,是不是很有意思呢?

關于 Repository 傳回 IQueryable 的讨論,我大緻總結下:

好處:

延遲執行。

減少 Repository 重複代碼(GetBy...)。

IQueryable 提供更好的靈活性。

...

壞處:

隔離單元測試。

資料通路在 Repository 之外完成。

資料通路異常在 Repository 之外抛出。

該領域層将充斥着這些相當詳細查詢。

好處就不多說了,因為我們上面已經實踐過了,關于壞處,“隔離單元測試”是什麼意思呢?也就是說我們不能很好的對 Repository 進行單元測試,一方面是因為 IRepository 是那麼的簡單(就兩個方法),另一方面 IQueryable 是查詢表達樹,它并不是完成時,隻有在具體調用的時候才會查詢完成,是以,對于 Repository 的單元測試,顯然是沒有任何意義的。

However the mistake is not the IQueryable itself, but its purpose.(不是 IQueryable 本身的錯誤,而是它的目的。)

The point is that using IQueryable, you're asking for a query builder and not for a model.(問題的關鍵是,使用 IQueryable 是一個查詢生成器,而不是一個模型。)

we want to specify what to get, not how to get it.(我們想通過規約得到它,而不是怎樣去得到。)

tell it the what, not the how.

看了上面,是不是有點豁然開朗的感覺呢,其實從 Repository 的模式概念方面考慮,使用 IQueryable 确實不是很恰當,但不可否認的是,IQueryable 又這麼強大和便利,怎麼辦呢?就像博文一開始強調的那樣:Repository 的概念就不多說了,在“僞 DDD”設計中,你可以把它看作是資料通路層。

是以呢,如果你的項目是“僞 DDD”,并且 Repository 是被你看作“資料通路層”,那麼使用 IQueryable 就沒啥問題了。

本文轉自田園裡的蟋蟀部落格園部落格,原文連結:http://www.cnblogs.com/xishuai/p/repository-return-iqueryable-or-ienumerable.html,如需轉載請自行聯系原作者