徐照興
(江西服裝學院 大數據學院,江西南昌向塘經濟開發(fā)區(qū)麗湖中大道103號 330201)
決定一個系統(tǒng)優(yōu)劣最關鍵的是系統(tǒng)的架構,系統(tǒng)架構的優(yōu)劣決定著系統(tǒng)具有的延遲與吞吐量、可用性與一致性、可擴展性、穩(wěn)定性等[1]。系統(tǒng)經典架構主要有基于接口編程的三層架構,該架構可以很好地從整體上對系統(tǒng)解耦。經典三層通常分為數據訪問層、業(yè)務邏輯層、表現層,大型系統(tǒng)通常會在經典三層架構的基礎上對業(yè)務邏輯層進行再封裝,使之形成新的一層,通常稱為服務層,這樣系統(tǒng)的架構就變成了四層,也就成為分布式系統(tǒng)架構[2]。不論幾層架構,數據訪問層是必須的,其功能主要是負責對數據庫的訪問。文中經過對多套開源框架分析,結合實戰(zhàn)開發(fā)經驗,給出一種基于接口編程思想,用Entity Framework技術實現數據訪問層的方案,并給出核心代碼,整個實現思路如圖1所示。
圖1 數據訪問層實現思路總圖Fig.1 General diagram of data access layer implementation idea
在.Net開發(fā)平臺下,數據訪問層實現技術主要有ADO.Net,NHibernate,Entity Framework(以下簡稱“EF”)。ADO.Net是一種最基本的數據庫訪問技術,訪問性能高,需要比較熟練掌握SQL Server數據庫技術。EF是典型的一種實現了ORM(Object Relational Mapping,對象關系映射)框架的技術,它底層是對ADO.Net技術進行了封裝,通過實體、關系型數據庫表之間的映射,使開發(fā)人員可以通過操作表實體而間接地操作數據庫,大大地提高了開發(fā)效率。EF與原生ADO.NET技術相比缺點是訪問性能稍差,主要是因為在編譯運行時有一個生成sql腳本的過程[3]。NHibernate也是一種實現了ORM框架的技術,它使用數據庫和配置信息來為應用程序提供持久化服務,也即在使用時要進行更多的配置[4],總體來說與Entity Framework技術差不多,但是NHibernate與Visual Studio開發(fā)環(huán)境的集成不如EF,所以本文選擇主要利用EF技術來實現數據訪問層。
系統(tǒng)采用Model First方式設計數據庫,即先設計Model,然后根據Model生成數據庫。為了方便后面給出實現代碼及說明,假設系統(tǒng)有UserInfo(用戶)和OrderInfo(訂單)兩個實體,UserInfo實體擁有UName(用戶名)、Pwd(密碼)、ShowName(真實姓名)等屬性,OrderInfo實體擁有Content(訂單內容)、UserInfoId(訂單所屬的用戶Id)等屬性,它們之間的關系為1對多,實體數據模型如圖2所示。
圖2 數據實體模型Fig.2 Data entity model
數據訪問層最基本職責就是封裝對實體的增刪改查方法,假設要對實體UserInfo進行封裝,即創(chuàng)建UserInfoDal類,此類中用EF對實體進行增加、修改、刪除方法都非常簡單。下面重點分析闡述如何根據用戶輸入的任意條件高效查詢數據。在ADO.Net數據庫訪問技術時往往會采用where拼接條件,這樣寫起來很繁瑣,性能比較差,也不利于擴展。采用EF來實現,要考慮參數類型和返回值類型2個問題。
(1)方法參數類型
要能接受用戶輸入的任意條件是一個條件,它要么為真要么為假,其返回值是bool類型,因此用委托Func
(2)方法返回值類型
返回值是用戶,但返回的用戶個數是不確定的,因此不能用UserInfo,可以用List
該方法參數Func
用Expression
利用EF技術實現根據用戶輸入任意條件高效查詢數據的代碼如下:
Public IQueryable
{
Return
db.UserInfo.Where(whereLambda).AsQueryable();
}
Lambda分頁查詢需要的參數有pageSize(每頁多少條記錄)、pageIndex(查找的頁碼索引)、查詢條件、排序條件、升序(asc)還是降序(desc),此外,一般還會輸出查找到的記錄數total。最關鍵的就是查詢條件參數及排序條件參數如何表示。
查詢條件與3.1節(jié)中的是一樣的,重點分析排序條件。
排序條件是由用戶傳入,因此跟查詢條件參數類似。但Func的返回值類型不是固定的bool,如果根據Id排序就是int,如果根據用戶名排序就是string等,具體實現如下:
把分頁查詢方法寫成泛型形式,也即泛型方法,這樣在調用方法時由用戶傳入,也即排序條件參數為:Expression
分頁查詢返回值類型同樣為延遲加載類型,因此返回值類型設置為IQueryable
為了提高代碼的復用性及系統(tǒng)的可擴展性,需要封裝數據訪問層的基類,取名為BaseDal,其功能就是封裝所有Dal層類公共的增刪改查方法。然后Dal層各實體類(比如UserInfoDal,OrderInfoDal等)只要繼承基類BaseDal就擁有了增刪改查方法。那么在基類下面所有方法就不能指明具體的類型,那如何處理呢?
子類UserInfoDal繼承基類BaseDal時,可以通過子類傳入類型給基類,因此,基類就要能接收子類傳入的類型,也即基類要采用泛型類,同時約定傳入的類型要為引用類型和具有無參構造方法。
通常采用new實例化對象,會導致BLL層與DAL層緊密耦合在一起,即DAL層發(fā)生變化BLL層就必須跟著變。而項目設計原則為模塊內高內聚、模塊間低耦合[6]。即當DAL層發(fā)生變化,BLL層不需要變化或者變化達到最小。
可以通過接口隔離BLL層對DAL的依賴,即讓BLL層依賴接口,不要依賴于DAL層,因為DAL層是具體的實現(即不依賴于具體實現),而依賴于接口,接口是抽象的(里面的方法等都只有一個定義而已),可以用不同的方法來實現接口,因此,需要建立接口層。然而有多少個實體,就需要建立多少個實體對應的接口,且每個接口里都是定義類似的抽象的增刪改查方法,因此,可以抽象出基類接口IBaseDal,同BaseDal一樣,它也需要設置為泛型,以便接收子類傳入的類型。
有了上面的基類接口后,具體的子類接口(如IUserInfoDal)只需要去繼承基類接口IBaseDal,并傳入對應的實體類型即可。
為了讓實例化對象返回值類型為接口類型,還需要讓對應數據訪問層子類(如UserInfoDal)去實現對應的接口。具體數據訪問層子類(UserInfoDal)的代碼結構升級改為如下形式:
Public class UserInfoDal:BaseDal
{
}
然后,實例化具體數據訪問層子類就可以用接口類型去接收(即返回值類型為接口)。
3.5通過抽象工廠實現BLL層與DAL層徹底解耦
通過接口隔離BLL對DAL的依賴,代碼得到較大優(yōu)化,但是還有不足之處。
因為業(yè)務邏輯層有很多BLL類,比如UserInfoBll,OrderInfoBll等,有多少個實體類就需要有多少個BLL類,而且UserInfoDal會用得非常頻繁,因為很多業(yè)務BLL都需要與用戶發(fā)生數據交互,如UserInfoDal()名稱發(fā)生改變(比如數據訪問驅動層實現技術發(fā)生改變,名稱由UserInfoDal改為了NhUserInfoDal),那么所有用到UserInfoDal的BLL類都要做相應的更改,這樣就非常麻煩,那么有沒有辦法改一個配置就可以呢?
這就可以用抽象工廠,其本質是使用反射方式來實現[7]。要使用反射就要獲得當前程序集。數據訪問驅動層的不同實現方法其實就是程序集名稱不同,不過前提是不同數據訪問驅動層封裝同一個實體的DAL名稱要相同,這實際也就是約定大于配置思想。在項目中新建一個靜態(tài)的StaticDalFactory類,在該類下面創(chuàng)建GetUserInfoDal方法,返回值類型設置為接口類型IUserInfoDal。
由于變化點只有一個程序集名稱,因此,可以把程序集的名稱放到配置文件(web.config)中去。
對于靜態(tài)工廠層(StaticDalFactory),首先添加對配置文件的引用,即添加對System.Configuratuion程序集的引用。
那么以后實現數據訪問驅動層的技術發(fā)生改變,只要修改配置文件web.Config中的
前面數據訪問層BaseDal中上下文實例[8]是通過new產生的,只要執(zhí)行到new所在代碼就會產生一個上下文實例??梢杂靡粋€單獨類來產生上下文實例,再新建一個DbContentFactory類,功能用來保證線程內共享一個上下文實例。
在此類下創(chuàng)建一個方法GetCurrentDbContent(),通過該方法用來創(chuàng)建上下文對象,返回值類型設置為DbContext,因為Model層中如果還有其他的上下文對象,那么只要把new后面的具體上下文對象名稱更改就可以切換上下文對象。
在數據訪問層繼續(xù)封裝一個DbSession類,讓它擁有所有DAL的實例和更新到數據庫的方法,也即是讓DbSession類為整個數據訪問層與數據庫的會話類,像EF上下文封裝了所有表對應實體集合DbSet
接下來把數據訪問層基類BaseDal.cs中所有Db.SaveChanges()代碼全部刪除,也即是不要在數據訪問層每一個操作都去與數據庫交互。這樣做優(yōu)點是數據提交的權利從數據庫訪問層提到了業(yè)務邏輯層。如果在數據庫訪問層每個方法都有SaveChanges()方法,那么每操作一次都會與數據庫發(fā)生交互。而到了業(yè)務層來了就非常靈活,多個操作可以“積攢”一起提交,當然需要的話也可以一個操作結束了就提交,只要加上DbSession.SaveChanges()語句即可。
業(yè)務邏輯層中語句:DbSession dbsession=new DbSession();即DbSession所在層與BLL層之間緊密依賴,因此需定義一個IDbSession類,用來隔離DbSession所在層與BLL層之間的依賴,然后讓DbSession去實現IDbSession。
但是這里還有問題,就是通過new實例化對象,只要執(zhí)行,就會產生新的實例。因此需要封裝成一次請求共用一個實例(比如封裝為GetCurrentDbSession方法)。然后回到業(yè)務邏輯層改造獲取dbsession實例代碼,采用上面封裝好的方法,這樣系統(tǒng)就又進一步得到了優(yōu)化。后續(xù)要得到userInfoDal等各個實例的DAL,可以通過統(tǒng)一的數據庫訪問入口DbSession來獲取。
至此,數據訪問層從可擴展性、可復用性及可維護性等方面均得到了非常大的優(yōu)化。
系統(tǒng)具有可擴展性、可復用性及可維護性是一個基本要求,文中基于接口編程的思想,選擇.Net平臺,采用EF技術,一步步分析闡述了如何對數據訪問層進行優(yōu)化,并給出核心代碼,從而加深加速軟件技術開發(fā)人員更好地理解數據訪問層常用的技術思想,為同行提供一種數據訪問層的實現技術參考。數據訪問層實現技術不是一成不變的,沒有最好,只有更好,比如數據訪問層實例對象可以通過依賴注入方式(Spring.Net)注入到業(yè)務邏輯層,讀者可以在理解的基礎上根據系統(tǒng)的實際業(yè)務需求作出選擇或進一步優(yōu)化。