ASP.NET MVC+EF框架+EasyUI實現權限管系列
(開篇) (1):框架搭建 (2):數據庫訪問層的設計Demo (3):面向接口編程 (4 ):業務邏輯層的封裝
前言:通過前面的五篇博客我們已經對權限系統的后台架構進行了詳細的說明,那么我再前面的博客中也說到了我們的后台架構還會再改的,我准備這段時間我們繼續完善我們的后台,順便能夠把前面的設計架構復習一下,下面我們就開始今天的博客系列,希望大家都能夠給予我支持,你們的支持才是我的動力,如果各位感覺寫的還可以,請不要吝嗇點擊一下支持。
1. 解說以前架構的實現
(1)當我們項目進行到這里的時候,我們很有必要解說一下我現在架構所實現的功能,先上張圖,然后我詳細的解說這些這些文件的含義。

(2)下面我們針對整個項目來解釋一下項目類庫的用法和作用
1) LYZJ.UserLimitMVC.UI.Portal項目,UI層,負責項目頁面的展示,使用EasyUI和MVC4.0來實現的頁面。
2) LYZJ.UserLimitMVC.Model類庫,用來存放數據表實體的模型,這里放置的時Entity FrameWork模型。也就是使用EF框架來操作數據庫。
3) LYZJ.UserLimitMVC.Common類庫,專門用來存放一些公用的信息,類。比如Md5加密算法類,文件上傳,格式轉換等等。
4) LYZJ.UserLimitMVC.DAL類庫,數據庫訪問層的實現,因為我們對數據庫的操作也就是增刪改查操作,所以我們封裝了一個基接口,用於實現對數據庫的操作,然后其他的數據庫訪問層的對象只需要繼承自基接口實現對數據庫的操作即可,在這里我們也添加了一個工廠(倉儲),使用簡單工廠管理實例的創建,這樣的話我們就把創建實例的這些模式封裝到簡單工廠里面去了。實現了高內聚,低耦合。
5) LYZJ.UserLimitMVC.IDAL類庫,數據庫訪問接口層,用來存放數據庫訪問層的接口信息,因為我們對數據庫的操作也就那些方法,所以我們封裝了一個基接口,然后讓其他的對象接口繼承自基接口。
6) LYZJ.UserLimitMVC.BLL類庫,這是我們對業務邏輯層的實現,和數據庫訪問層同樣的思路,不過我們實現的是對數據庫訪問層的抽象。
7) LYZJ.UserLimitMVC.IBLL類庫,業務邏輯接口層,用來存放的是業務邏輯的接口,實現思路和數據庫訪問層的思想一致。
(3)在前面我們已經使用到了依賴接口編程,簡單工廠模式,這里提醒博客,當我們在做項目的時候我們不要生搬硬套某些設計模式,只有當這些設計模式跟我們的應用場景非常匹配的時候我們在去想辦法使用這些設計模式,一般情況下,當我們在寫項目的時候,我們自己本來就在用某些設計模式,只是我們自己不知道而已。當我們寫完的時候我們發現我們已經使用了某種設計模式。
(4)下面我們繼續對數據庫訪問層進行封裝
2.對數據庫訪問層的在封裝
(1)下面我們來分析一下LYZJ.UserLimitMVC.DAL類庫下面的RepositoryFactory類,首先我把RepositoryFactory類的代碼帖上來,然后解釋:
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 public static class RepositoryFactory 6 7 { 8 9 public static IUserInfoRepository UserInfoRepository 10 11 { 12 13 get { return new UserInfoRepository(); } 14 15 } 16 17 public static IRoleRepository RoleRepository 18 19 { 20 21 get { return new RoleRepository(); } 22 23 } 24 25 } 26 27 }
(2)這個RepositoryFactory類我們是使用簡單工廠來操作的,我們在這里使用簡單工廠可以拿到所有的倉儲的實例(UserInfoRepository,RoleRepository),其實這個簡單工廠從另外一個角度來看的話,這個簡單工廠就是我們數據庫訪問層的一個統一的入口,這是為什么呢?因為從這個簡單工廠我們就能夠拿到所有的倉儲,既然我們能夠拿到所有的倉儲那么我們就能夠操作所有的表,所以說這個倉儲(RepositoryFactory)就是我們數據庫訪問層的統一入口。
(3)下面我們將這個簡單工廠在轉換成另外一種方式來實現。基於線程內唯一方針。
3.EFContextFactory,DbSession總括
(1)首先我們在LYZJ.UserLimitMVC.DAL類庫下面新建DbSession類,那么這個DbSession是干什么的呢?首先我們猜一猜,DB是代表數據庫的意思,而Session代表一次會話。
(2) 我們在ASP.NET中請求頁面的時候也用Session,Session就代表一次會話的開始,到我們所有的請求結束之后,瀏覽器關閉之后會話結束,也就是說在他一次請求當中的一個過程稱為一個會話,
(3)那么我們新建的這個DbSession是什么意思呢?意思就是我們跟數據庫進行一次會話的過程,當我們一個請求過來的時候,我們要跟數據庫進行很多次的交互,那我們把很多次交互的這個整體的過程我們稱之為一個會話。當我們對數據庫進行操作的時候會話開啟,當數據庫操作結束,請求也結束,那么我們對這個數據庫訪問的會話也就結束了。
(4)那么我們如果想操作數據庫的話,就要通過這個會話來進行操作,在DbSession中就可以拿到我們所有的倉儲,然后我們就可以對所有的表進行增刪查改操作。當我們對這個表所有的增刪查改完成之后,還可以通過DbSession一次性的提交會數據庫,因為是一次會話,所以將這次會話中的所有的變化都提交回數據庫,然后就實現了批量提交。
4. DbSession一次跟數據庫的會話
(1)首先我們要理解DbSession這個類的作用,代表了應用程序跟數據庫之間的一次會話,從另外一個角度來說,也是數據庫訪問層的統一入口。(整個數據庫訪問層的抽象)。
(2)然后我們書寫DbSession類的代碼,里面我寫了詳盡的注釋,大家可以看看:
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 //一次跟數據庫交互的會話,封裝了所有倉儲的屬性,根據DbSession可以拿到倉儲的屬性 6 7 public class DbSession //代表應用程序跟數據庫之間的一次會話,也是數據庫訪問層的統一入口 8 9 { 10 11 public IDAL.IRoleRepository RoleRepository 12 13 { 14 15 get { return new RoleRepository(); } 16 17 } 18 19 20 21 public IDAL.IUserInfoRepository UserInfoRepository 22 23 { 24 25 get { return new UserInfoRepository(); } 26 27 } 28 29 //代表:當前應用程序跟數據庫的繪畫內所有的實體的變化,更新會數據庫 30 31 public int SaveChanges() 32 33 { 34 35 //調用EF上下文的SaveChanges方法 36 37 return 0; 38 39 } 40 41 } 42 43 }
(3)根據上面的代碼注釋我們可以看到,調用上下文的SaveChanges方法,那么我們調用上下文中的那個SaveChanges方法呢?下面我們看一下我們之前的EF上下文的處理。
5.EF上下文的處理
(1)首先我們找到LYZJ.UserLimitMVC.DAL類庫下面的BaseRepository類里面我們能夠看到我們以前是直接使用new來實現的,那么我們整個項目里面上下文的實例會有很多個,我們又遇到了多次,當我們在編程的時候遇到多的時候,一般我們就要想想能不能解決多這個問題。
(2)這里我要說的是EF上下文怎么管理呢?很簡單啦,就是要保證線程內唯一,所以這里我們就要進行修改BaseRepository類了。
(3)在這里BaseRepository倉儲的職責是什么?他的職責就是幫我們實現了所有子倉儲的公共方法(增刪查改),他的職責不包含怎么去管理上下文的實例,所以我們不能把這種控制上下文實例的線程內唯一的代碼放在這個位置,這就是我們每個類的職責必須唯一,面向對象中的一點就是類的職責必須單一。
(4)下面看一下我們修改后的BaseRepository(倉儲),這里我只列出一小部分,因為下面的都沒有變化
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 /// <summary> 6 7 /// 實現對數據庫的操作(增刪改查)的基類 8 9 /// </summary> 10 11 /// <typeparam name="T">定義泛型,約束其是一個類</typeparam> 12 13 public class BaseRepository<T> where T : class 14 15 { 16 17 //創建EF框架的上下文 18 19 //EF上下文的實例保證線程內唯一 20 21 //private DataModelContainer db = new DataModelContainer(); 22 23 // 24 25 private DbContext db = EFContextFactory.GetCurrentDbContext(); 26 27 // 實現對數據庫的添加功能,添加實現EF框架的引用 28 29 public T AddEntity(T entity) 30 31 { 32 33 //EF4.0的寫法 添加實體 34 35 //db.CreateObjectSet<T>().AddObject(entity); 36 37 //EF5.0的寫法 38 39 db.Entry<T>(entity).State = EntityState.Added; 40 41 //下面的寫法統一 42 43 db.SaveChanges(); 44 45 return entity; 46 47 } 48 49 } 50 51 }
(5)那么我們怎么控制上下文的實例並且要求它是線程內唯一呢?這時候我們不能放到BaseRepository(倉儲)中去設置,這時候我們就想到了封裝,我們將控制上下文的實例並且要求它是線程內唯一的代碼封裝到一個公共的類中。這時候怎么做呢?請看下面的做法
(6) 這時候我們看到上面的代碼我們不能直接new來實現了(//private DataModelContainer db = new DataModelContainer();),那么我們怎么獲取這個實例呢?重點是在這里獲取實例的地方必須是公共的,而且還要能夠幫我們管理線程內唯一,這時候我們可以想到我們能夠通過工廠來實現這個實例,那么我們在這里創建一個EFContextFactory工廠,在這個工廠里面有GetCurrentDbContext()方法來返回實例( private DbContext db = EFContextFactory.GetCurrentDbContext();)。那么這時候我們就需要創建上面的類了。
6.EFContextFactory
(1)這時候我們在LYZJ.UserLimitMVC.DAL類庫下面再建立一個EFContextFactory類,在這個類里面含有GetCurrentDbContext方法,下面我解釋一下這些代碼的實現,我們實現這個方法的代碼是:
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 public class EFContextFactory 6 7 { 8 9 //幫我們返回當前線程內的數據庫上下文,如果當前線程內沒有上下文,那么創建一個上下文,並保證 10 11 //上線問實例在線程內部是唯一的 12 13 public static DbContext GetCurrentDbContext() 14 15 { 16 17 //CallContext:是線程內部唯一的獨用的數據槽(一塊內存空間) 18 19 //傳遞DbContext進去獲取實例的信息,在這里進行強制轉換。 20 21 DbContext dbContext = CallContext.GetData("DbContext") as DbContext; 22 23 if (dbContext==null) //線程在數據槽里面沒有此上下文 24 25 { 26 27 dbContext = new DataModelContainer(); //如果不存在上下文的話,創建一個EF上下文 28 29 //我們在創建一個,放到數據槽中去 30 31 CallContext.SetData("DbContext", dbContext); 32 33 } 34 35 return dbContext; 36 37 } 38 39 } 40 41 }
(2)首先我們看到我們在定義方法的時候返回的是DbContext,這里需要注意的就是在EF5.0我們使用DbContext,但是在EF4.0的話我們使用ObjectContext,那么DbContext是干什么的呢?他的作用就是幫助我們返回當前線程內的數據庫上下文,如果當前線程內沒有上下文,那么我們就直接創建一個,並且保證上下文實例在線程內部唯一。
(3)在這里我們用到了CallContext類,那么這個類是干什么的呢?MSDN上面給的說法是這樣的
1)資料地址:http://msdn.microsoft.com/zh-cn/library/system.runtime.remoting.messaging.callcontext(VS.80).aspx
2)然后我們找到備注:CallContext是類似於方法調用的線程本地存儲區的專用集合對象,並提供對每個邏輯執行線程都唯一的數據槽(數據存儲區域),數據槽不在其他邏輯線程上的調用上下文之間共享,當CallContext沿執行代碼路徑往返傳播並且由該路徑的各個對象檢查時,可以將其對象添加其中。
3)當對另一個AppDomain中的對象進行遠程方法調用時,CallContext類將生成一個與該遠程調用一起傳播的LogicalCallContext實例,只有公開ILogicalThreadAffinatice接口並且存儲在CallContext中的對象被在LogicalCallContext中傳播的AppDomain外部,不支持此接口的對象不在LogicalCallContext實例中與遠程方法調用一起傳輸。
(4)在CallContext中有兩個比較重要的靜態方法
1)void SetData(string name,object data),往集合中存入數據。
2) object GetData(string name) 代表取數據,傳遞一個Key過去取出對應的值。
(5)這時候我們解釋一下執行的流程,當線程第一次過來的時候,首先調用這個方法(GetCurrentDbContext)的代碼,去數據槽里面看看有沒有這個線程,當然了第一次過來肯定沒有,這時候我們創建一個,然后存放到dbContext中去了,這時候將new的對象直接返回,而當請求第二次過來的時候,當去數據槽中去取得時候就有信息了,然后不執行判斷,直接返回,以后就直接去取就行了。注意:只是當前線程內部。這樣就保證了線程內上下文唯一。
源碼下載
(1):完整源碼下載
Kencery返回本系列開篇
