博客園的大牛們,被你們害慘了,Entity Framework從來都不需要去寫Repository設計模式


本文將在技術層面挑戰園子里的權威大牛們,言語不敬之處敬請包涵。本文旨為技術交流,歡迎拍磚。

 

園子里面分享和推薦Entity Framework(以下簡稱EF)的Repository(倉儲)設計模式的文章真不少,其中還有很多大牛很詳細描述怎么去實現。但是這些文章真是害人不淺。我現在想問問這些大牛們,你們現在的項目真的還在這樣用嗎?

 

下面是在找找看里面隨便挑的幾篇,如果你從未了解過EF Repository,你可以看看:

 

我與EF的緣分

大約2年以前,我開始接觸Entity Framework,當時公司大牛推薦了幾篇EF的Repository設計模式的文章,公司內部也有一個使用EF Repository的框架,當時公司.NET項目基本都用那個框架,我們項目當然也不例外。

 

大約用了半年,做了2-3個項目,我越來越感覺EF倉儲模式用起來一點都方便,代碼寫起來很別扭。但那會還不知道為什么,因為那會我也沒有完全理解倉儲模式到底是個什么。

 

2012年7月,我開始獨立帶領一個中型項目開發團隊。數據庫訪問的架構仍然延續了公司一直推崇的EF+Repository模式。當開發進行到一半的時候,我感覺我們的數據庫訪問的代碼已經很亂了。service層在訪問數據庫,UI層也在訪問數據庫。由於同時使用了構造函數方式的依賴注入,循環依賴的問題也讓代碼變得難以維護。但是直到項目結束我才明白應該怎么使用EF。

 

在隨后的一個項目中,我完全拋棄了EF+Repository模式,使用一種全新的架構。在新架構上開發了一個多月,我意識到我們確實用對了,於是我跟公司推薦。但公司大牛們並不認可。幾個月后,有另一位項目經理也提出EF+Repository模式用起來太惡心。

 

在接下來大半年時間到現在,我帶的多個項目都采用了新的架構,用起來實在太爽了,我也多次建議在公司推廣。后來公司大牛基本也都否認了EF+Repository模式,但由於各種原因,其他項目組使用我們新架構的只占少數。

 

寫這么長一段經歷我只想說“大牛”們的權威真的影響好大,但只有真理才經得起時間的考驗。下面我將詳細闡述我對EF+Repository模式的理解。

 

EF+Repository模式錯在哪里

1. 沒有理解到什么是repository設計模式。

首先我想說repository設計模式的原理。repository即倉庫,通常我們會按照數據庫中的表來建repository。repository外一定有一個包裹器,因為所有對repository的增刪改都不會立即提交到數據庫,而需要調用包裹器的提交才會真正跟數據庫進行通訊。

 

當你看這篇文章(分享基於Entity Framework的Repository模式設計(附源碼))的時候,乍一看,確實是按照repository來設計的啊。IUnitOfWork即包裹器,數據的增刪改查都放到了這個包裹器里面。所以啊,很多人就這樣被欺騙了。

 

實際上這篇文章的中的repository框架性的代碼沒有一行有意義,因為EF本身即是按照repository來設計的。這個觀點有很多人知道,但更多的人不知道。請看下面這行代碼:

var db = new DemoDbContext();

這里的db即相當於是repository的包裹器。通過包裹器可以非常輕松的訪問到其下的每一個repository,這也絕對是最簡單的訪問方式了,如:

db.Users....
db.Products....

而這篇文章中,如果要訪問repository,還要通過IOC。不知道繞這么大個圈子干嘛,不是自己給自己找事嗎。

 

2. EF本身就是repository設計模式的

倉儲模式最大的優點就是所有的數據訪問首先是通過倉庫的,對倉庫的增刪改都不會立即提交到數據庫,而只有當調用了倉庫包裹器,這些增刪改的操作才會一次提交到數據庫。

 

在EF中,DbSet<TEntity>即是定義的倉庫,DbContext即是倉庫包裹器,相當於UnitOfWork。所有對DbSet<TEntity>的增刪改都只有在調用了DbContext的SaveChanges之后才會提交到數據庫。這樣看,EF是不是完全實現了Repository了呢?

 

另外,DbContext的SaveChanges本身包含了一個事務機制,再結合TransactionScope類,我認為EF真的是完美解決了所有事務問題。請看我之前的一篇博客中分享的基於EF事務機制的架構:http://www.cnblogs.com/leotsai/p/how-to-use-entity-framework-transaction-scope.html

 

我真不明白,這么多大牛們為什么要把EF本身的Repository再包裹一遍。關鍵是包裹一遍的代碼不但丑,不但難用,而且有BUG!

 

EF+Repository模式的三大缺點

1. 代碼丑

首先整個+Repository這一層的代碼都是多余的,包括UnitOfWork類和繼承自IRepository的類。 在這個架構下寫代碼,你會寫大量重復的沒有意義的IRepository的實現。而在service層,由於各個repository是獨立的,但是實體類之間又是有關系的(導航屬性),有時你的代碼通過導航屬性在訪問另一個repository,有時又不是,有時為了統一全部從repository里面讀數據,你不得不先定義要用到的所有repository,然后再寫很多看不懂的inner join。當業務邏輯復雜的時候,這樣的代碼真的是要讓維護人員崩潰。

 

2. 難用

首先你要寫大量重復的代碼。但更惡心的還不是這個,更惡心的是,明明有導航屬性可以訪問到另一張表,但是為了不跨repository,你只能通過unitOfWork.GetRepo來獲取一個repository的實例,然后就是大量的inner join。並且這個規則實在很難執行,於是你發現有時候你也在用導航屬性,然后你就會懷疑了,我定義的repository有何意義?

 

再加上很多人根本不知道UnitOfWork是什么意思,於是到處都有UnitOfWork,比如UI層都在用。可曾想,UnitofWork原來是用來包裹數據庫訪問的repository的,UI層怎能訪問數據庫呢?

 

3. 有BUG

這里說的BUG不是指運行會報錯,而是指架構的BUG。說白一點,就是這個架構會勾引你犯錯寫BUG。

 

最最明顯的一個BUG就是,IReposoitory<TEntity>下面有個void Update(TEntity entity)方法。當然也不是所有的EF+Reposoitory模式都這樣寫,但大多數是有的。

 

這個方法真的是害死人了。我相信被這個方法害過的人大有人在,我曾經也是其中之一。舉個簡單的例子,看能不能勾起你那憂傷的回憶。請看代碼:

1 public ActionResult Update(Product product)
2 {
3     _productRepository.Update(product);
4     _unitOfWork.SaveChanges();
5     return View("UpdateSuccess");
6 }

這段代碼看上去沒有問題,但BUG是,每次更新一個product,createdTime就被設置為當前時間,因為在Product構造函數里設置了createdTime為now。哎,真是悲劇!作為一個項目經理,你可能無數次忠告你的程序員注意這個BUG,但這個BUG還是不斷重現,因為過兩天Status字段又被重置了,再過幾天另一個字段的值又丟失了。

 

這個設計實際上跟Repository沒有任何關系,但很多大牛這樣寫了,害了很多人,所以在此專門提出。

 

結論

EF本身即是按照Repository模式設計的,我們完全沒有任何必要再自己去寫一套repository把EF的repository再包裹一層;EF本身的事務機制本是完美的,額外包裹一層repository之后,讓事務變得模糊。

 

我把我們項目的架構提取出來放到了github上面,大家可以看看:https://github.com/leotsai/mvcsolution。注意Data和Services層的代碼。

 

下面的代碼是其中一個service,是不是代碼簡潔合理?

 1 using System;
 2 using System.Linq;
 3 using MvcSolution;
 4 using MvcSolution.Data.Entities;
 5 using MvcSolution.Data.Entities;
 6 using MvcSolution.Infrastructure.Security;
 7 
 8 namespace MVCSolution.Services.Users
 9 {
10     public class UserService : ServiceBase<User>, IUserService
11     {
12         #region Implementation of IUserService
13 
14         public User Get(string username)
15         {
16             using(var db = base.NewDB())
17             {
18                 return db.Users.Get(username);
19             }
20         }
21 
22         public string[] GetRoles(string username)
23         {
24             using (var db = base.NewDB())
25             {
26                 return db.Roles.WhereByUsername(username).Select(x => x.Name).Distinct().ToArray();
27             }
28         }
29 
30         public bool CanLogin(string username, string password)
31         {
32             using (var db = base.NewDB())
33             {
34                 var user = db.Users.Get(username);
35                 return user != null && user.IsDisabled == false
36                        && user.Password == CryptoService.MD5Encrypt(password);
37             }
38         }
39 
40         public void Register(User user)
41         {
42             using (var db = base.NewDB())
43             {
44                 if (db.Users.Get(user.Username) != null)
45                 {
46                     throw new Exception("username already registered.");
47                 }
48                 user.Password = CryptoService.MD5Encrypt(user.Password);
49                 db.Users.Add(user);
50                 db.SaveChanges();
51             }
52         }
53 
54         #endregion
55     }
56 }
View Code

 

關於如何更好的使用EF的架構,我認為應該有以下3點:

  1. 在service層要給訪問數據庫的代碼絕對的自由,就像sql一樣,導航屬性、跨表增刪改查隨便寫,而不是UnitOfWork.GetRepo;
  2. 在service層之后要終止對數據庫的訪問,也就是每一個service方法必須將DbContext dispose掉,不管這個service是做增刪改還是查詢;
  3. 在UI層可使用TransactionScope來包裹多個service,從而實現跨service的事務機制。

完。

 

MvcSolution框架 QQ討論群:539301714

如果遇到問題,可以進QQ群,群里的高手還有我會盡量盡快幫你解決問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM