接上一篇我們來對數據訪問層進行封裝與抽象。在上一篇我們知道,要解除BLL對DAL的依賴,我們就必須抽象出DAL層的接口,同時基於DAL的數據訪問技術很多,如EF,ADO.NET,LINQ TO SQL,因此,我們的數據訪問層必須對這些技術提供相應的支持。所以今天我們要做的事情有兩件,第一,定義我們的數據訪問層接口;第二,屏蔽各類數據庫訪問技術的差異,提供統一的數據庫訪問模型。舉個例子,我們只需要修改一下我們的配置文件,就能夠把ADO.NET的實現方式,改變成EF的實現方式。好下面搭建我們的三層構,如圖:
項目的框架與上一篇基本一致。項目的引用關系: StructUI->Common,Model,BLL; BLL -> Model,IDAL,Common,Factory;DAL-> IDAL,Model。再次提醒各位,我們在BLL層並沒有引用DAL,我們不能創建(new)DAL層的任何實體。
下面,我們來定義DAL層的接口。定義層次的接口其實是一件很復雜的事情,首先,我們必須抽象出該層內所有對象有的屬性與行為。對於數據訪問層的對象,很明顯增刪改查是肯定走不掉的。其次我們必須充分的考慮,數據訪問層各類技術對該接口的實現難度,技術上沒法實現的接口,肯定是沒有任何意義的。本文僅當示范作用,所以暫時我定義的接口會很簡單,在后續的章節,我會抽象出一套完備的數據接口。該層的任何實體,都具備增刪改查功能,所以我們首先定義該層的全局接口IDALBase<T>,即任何數據訪問實體,都應該實現的接口,代碼如下

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IDAL { public interface IDALBase<T> where T : class { /// <summary> /// 向T對應的數據表插入 /// 一條數據 /// </summary> /// <param name="entity"></param> /// <returns>true:成功,false:失敗</returns> bool Insert(T entity); /// <summary> /// 修改數據表中與entity /// 實體對應的記錄 /// </summary> /// <param name="entity"></param> /// <returns>true:成功,false:失敗</returns> bool Update(T entity); /// <summary> /// 刪除數據表中與entity /// 實體對應的記錄 /// </summary> /// <param name="entity"></param> /// <returns>true:成功,false:失敗</returns> bool Delete(T entity); /// <summary> /// 查詢數據庫表中指定的ID /// 的記錄 /// </summary> /// <param name="ID">標識ID</param> /// <returns>指定ID記錄對應的實體</returns> T Query(int ID); } }
因為,我們的接口是面向數據訪問層的所有對象,所以我們采用了范型。接着我們思考一下,除了我們全局接口所定義的方法,我們有些數據訪問類可能還有一些自己特有的公共方法,這些方法也必須用接口的形式暴露出來,因為我們BLL層並沒有引用DAL層,所以它是個只認接口的家伙,接口沒有定義的方法,它就不會去調用,綜上我們必須為每個DAL數據訪問實體提供相應的接口,並且該接口里面必須涵蓋數據訪問類型中的所有方法,例如我定義了一個IOrder接口,如下

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Model; namespace IDAL { public interface IOrderDAL : IDALBase<Order> { } }
它僅僅只是繼承了全局接口,假設我們的數據訪問類型Order有一個全局接口沒有定義的方法如GetAllOrder(),那么在IOrder接口中必須定義出GetAllOrder()。
至此,我們的接口已經定義完成了,下面我們就要考慮封裝我們的DAL了。很明顯,所有的數據訪問類型都繼承了IDALBase<T>接口,那么有一點可以肯定,就是所有的數據訪問類型都必須提供IDALBase<T>的實現代碼,這個時候我們就必須設想一下,我們能否抽象出一個公共的基類,來幫我們實現IDALBase<T>接口呢?如果能的話,將極大的降低我們的代碼冗余,使我們的程序變的優雅,下面我提供一個基類DALBase<T>

using System; using System.Collections.Generic; using System.Linq; using System.Text; using IDAL; namespace DAL { public class DALBase<T>:IDALBase<T> where T:class { protected IDALBase<T> dalActive; public bool Insert(T entity) { return dalActive.Insert(entity); } public bool Update(T entity) { return dalActive.Update(entity); } public bool Delete(T entity) { return dalActive.Delete(entity); } public T Query(int ID) { return dalActive.Query(ID); } } }
從上面可以看到,我並沒有直接在基類里面手寫實現接口的代碼,而是調用了基類中另一個IDALBase<T>類型的實體(dalActive)來幫助我們實現實現接口。這是為什么呢? 在前面我們說過,我們要讓我們的DAL層支持各類的數據訪問技術。大家想想,如果我們在基類里面,采用了一種技術如ADO.NET去把接口給實現了,那么很顯然,我們這個DAL將不再支持任何其他的數據訪問技術,因為我數據訪問層的所有類型都繼承了這個基類,基類的數據訪問技術定型了,子類將根本沒的選。所以我們在這里定義了一個IDALBase<T>類型的屬性,讓它來幫我們實現我們的接口。
在DAL層我提供了兩個實現類,一個是ADOBase<T>,采用了ADO.NET技術,該類不僅僅提供給接口的實現,還提供ADO.NET操作數據庫的基本API。由於該系列是我前不久才開始寫的,沒有現成的Doom,所以暫時只能拿出一個閹割版本了,但是該類要實現一些什么樣的功能,我已經用注釋說明了,在下一篇,我會提供它的具體實現。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using IDAL; namespace DAL { public class ADOBase<T>: IDALBase<T> where T : class,new() { #region ADO.net操作數據庫的基本API //在這一部分需要提供各類操作數據的API,作用主要有兩個: //第一,調用這些API實現IDALBase<T>接口 //第二,對於所有繼承自DALBase<T>的數據訪問層實體, //他會取得該dalActive實體,讓后強行轉換為ADOBase<T>對象 //調用此類API,實現其特有的功能 #endregion #region 實現接口 public virtual bool Insert(T entity) { //采用ADO實現 return true; } public virtual bool Update(T entity) { //采用ADO實現 return true; } public virtual bool Delete(T entity) { //采用ADO實現 return true; } public virtual T Query(int ID) { //采用ADO實現 return new T(); } #endregion } }
另一個則是EFBase<T>,采用是EF技術,並提供EF操作數據庫的API。供DALBase<T>子類去實現其專有的方法。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using IDAL; namespace DAL { public class EFBase<T> : IDALBase<T> where T : class,new() { #region EF操作數據庫的API //這里面需要提供EF操作數據庫的各類方法 //函數 #endregion #region 調用EF操作數據庫的API實現接口 public virtual bool Insert(T entity) { //采用EF return true; } public virtual bool Update(T entity) { //采用EF return true; } public virtual bool Delete(T entity) { //采用EF return true; } public virtual T Query(int ID) { //采用EF return new T(); } #endregion } }
好了現在基類我們有了,那么我們的子類則只需要在繼承基類的同時,實例化dalActive屬性,就自動的實現了IDALBase<T>接口,同時根據dalActive的類型的不同,我們的子類還具備不同的數據庫訪問方式(ADO.NET,EF),這是因為我們子類可以從父類的dalActive屬性實體拿到其對應數據訪問技術的API,我們只需強行轉換一下dalActive。例如:將dalActive強轉為EFBase,就可以得到EF的數據訪問API(當然dalActive必須是EFBase類型)。
為了使我們的程序變的靈活,支持各類數據訪問技術,我們利DalActiveProvider的靜態方法來實例化我們基類中的dalActive屬性,該方法通過讀取配置文件來判斷,給dalActive創建何種類型的實例,本文你只提供了兩種實例(EFBase<T>與ADOBase<T>)所以相對簡單,如下

using IDAL; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Text; namespace DAL { public class DalActiveProvider<T> where T:class,new() { private static string className = ConfigurationManager.AppSettings["dalActiveName"]; public static IDALBase<T> GetDalActive() { if (string.Equals(className, "EFBase")) { return new EFBase<T>(); } else { return new ADOBase<T>(); } } } }
下面我定義了一個OrderDAL數據訪類

using Common; using Factory; using IDAL; using Model; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DAL { public class OrderDAL : DALBase<Order>, IOrderDAL { public OrderDAL() { this.dalActive = DalActiveProvider<Order>.GetDalActive(); } #region 專有公共方法 //根據技術的選型 //將dalActive強行轉換 //為ADOBase<T>或者EFBase,在調用其 //數據訪問API來實現專用公共方法 #endregion } }
我們的配置文件,如圖
很顯然,目前我們會創建EFBase<T>實例給基類的dalActive屬性,整個DAL層將采用EF數據訪問技術,如果我們想采用ADO.NET技術的話,我們只需要將配置文件的dalActiveName的Value改為與字符創“EFBase”不等的任何字符串即可。
至於BLL層如何調用DAL層,在我的上一篇文章已經做了詳細的介紹,所以我不多說了,工廠代碼如下

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace Factory { public class InstancesFactory { /// <summary> /// 創建指定類型T的實例 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="assemblyPath">程序集路徑</param> /// <param name="className">類名稱(非全名)</param> /// <returns>T類型實例</returns> public static T CreateOrder<T>(string assemblyPath,string className) { className = string.Format("{0}.{1}", assemblyPath, className); return (T)Assembly.Load(assemblyPath).CreateInstance(className); } } }
我在BLL層創建了一個很簡單的基類BLLBase<T>,里面只有一個實現IDALBase<T>接口的屬性dalService,如下

using System; using System.Collections.Generic; using System.Linq; using System.Text; using IDAL; namespace BLL { public class BLLBase<T> where T:class { /// <summary> /// 數據庫接口 /// </summary> protected IDALBase<T> dalService; } }
在BLL的我創建一個業務子類OrderBLL繼承BLLBase<T>,在構造函數中采用工廠實例化dalService,然后OrderBLL針對數據庫的所有操作都是調用dalService的對應方法。當然,如果OrderBLL需要調用一些OrderDAL的專有方法,我還必須把dalService強行轉換為IOrderDAL接口類型

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Model; using Factory; using IDAL; using Common; namespace BLL { public class OrderBLL:BLLBase<Order> { public OrderBLL() { //初始化dal服務 this.dalService = InstancesFactory.CreateOrder<IOrderDAL> (FactoryConfig.Order.AssemblyPath, FactoryConfig.Order.ClassName); } public bool Inset(Order order) { //業務1 //業務2 return dalService.Insert(order); } #region IOrderDAL的專用方法 public void 專用方法Dom() { IOrderDAL orderbll = dalService as IOrderDAL; //調用IOrderDAL的專有方法 //orderbll.專有方法(); } #endregion } }

using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Text; namespace Common { public class ConfigEntity { /// <summary> /// 程序路徑 /// </summary> public string AssemblyPath { get; set; } /// <summary> /// 類命 /// </summary> public string ClassName { get; set; } } public class FactoryConfig { public static ConfigEntity Order { get { return new ConfigEntity() { AssemblyPath=ConfigurationManager.AppSettings["AssemblyPath"], ClassName = ConfigurationManager.AppSettings["ClassName"] }; } } } }
總結
本文對數據訪問層進行了概要上的封裝與抽象。當然在定義接口方面我們做的很簡單,在后面的章節我會完善並抽象出一個相對完備的接口,另外對於DAL的封裝我們還有兩大核心類(EFBase<tT>與ADOBase<T>)沒有去實現,對於這兩個類的實現將會是我隨后的幾篇博文的核心課題,最后感謝大家的觀看
本文的DOME 在這里