.NET三層架構的改進以及通用數據庫訪問組件的實現


  1. 前言

微軟的三層架構示例項目PetShop是很多.NET開發人員學習項目開發的經典樣本代碼,很多人都是直接使用PetShop的架構來搭建項目,
甚至許多著名的代碼生成器,也是以PetShop架構作為模板生成項目。事實上我認為PetShop只是一個展示如何使用.NET技術的應用項
目范例,若直接照搬用來用作實際項目開發的框架,還有很多地方需要改進,尤其是在實現多數據庫兼容方面。下面以PetShop為樣本
代碼,闡述如何通過改造它實現一個更合理實用的三層架構。由於改良方法主要采用通用數據庫訪問組件簡化原架構的數據訪問層,因此
本文也談及了如何實現基於ADO.NET的通用數據訪問組件。

  1. 架構分析

  2. 架構主干

    為了便於剖析項目的,下面列舉網上一個"PetShop粉絲"的項目架構與PetShop的對照

刨去一些本文討論無關的一些子項目,可以看出兩者的主干項目大致相同。項目架構的各層核心模塊調用關系如下圖

 

項目cofcms在數據訪問層實現了對SQL Server和DataAccess兩種數據庫的支持,項目cofcms在數據訪問層實現了對SQL Server和
DataAccess兩種數據庫的支持,
而PetShop則實現了對SqlServer 和 Oracle的支持。

  1. 代碼分析

    下面我們以PetShop在UI層Product頁面的一行代碼作為切入點,分析其架構特點。

很顯然,代碼的含義是獲取用戶選擇的產品分類作為參數,然后調用product類的GetProductsByCategory方法,取得該分類的所有產
品並作顯示。我們進一步查看GetProductByCategory方法的定義,發現位於BLL層。

查看方法體的代碼,主要是作了對無效參數的處理,然后調用IDAL層IProduct接口的實現類的GetProductsByCategory方法。
IProduct的實現類實例通過抽象工廠方式創建,由配置文件指定。實現了IDAL層接口的項目有兩個,SQLServerDAL和OracleDAL,
在這兩個項目下找到實現了IProduct的兩個實現類。

通過比較,我們發現這兩個實現類的代碼幾乎一模一樣,不過是把SqlParmeter換成OracleParmeter,SqlDbType換成OracleType,
SqlDataReader換成OracleDataReader,SqlHelper換成OracleHelper。我們再比較SqlHelper和OracleHelper里的ExecuteReader方法。

, SqlHelper.cs

OracleHelper.cs

代碼結構一模一樣,同樣的只不過是把用於訪問SqlServer的ADO.NET對象,統統換成了相應的訪問Oracle的ADO.NET對象。
我們再看看這些不同數據庫的ADO.NET類,都繼承於System.Data.Common命名空間下的一組標准ADO.NET抽象類。

  1. 主要改進思路和實現

  2. 利用ADO.NET抽象基類實現通用數據庫訪問組件

通過對代碼的分析,我們可以看到,如果用抽象基類替換繼承子類就可以實現一個通用的數據庫訪問類DBHelper,還是以ExecuteReader為例,
代碼如下

  1. 使用通用數據庫訪問組件簡化項目架構

我們暫且不討論DbConnection這個抽象基類如何創建出來(后面 三. 4會提到)。先看看用DBHelper取代了SQLHelper和
OracleHelper后的變化。對於SQLServerDAL 和OracleDAL來說,不同之處主要就是分別使用SQLHelper和OracleHelper
來訪問數據庫,現在這兩個Helper已經統一抽象為DBhelper,那么這幾個DAL也順理成章的統一CommonDAL,無須定義
IDAL接口,針對不同數據庫作不同的實現(SQLServerDAL,OracleDAL)了。CommonDAL層GetProductsByCategory方法代碼如下:

至於BLL層的代碼,簡便許多,可以直接創建DAL對象,無須通過抽象工廠方式創建接口IDAL的實現實例了。而用戶界面層代碼則完全不變。

  1. 改進后的項目架構

經過重構,我們目前的項目架構各層核心模塊調用關系演變如下

形成的項目架構簡化如下:

當Web層需要GetProductsByCategory方法時,實現流程演變如下:

  1. CommonDAL層里的Orders類添加GetProductsByCategory,方法方法體內通過調用 DBHelper,
    以及指定的sql語句執行數據庫查詢返回數據。
  2. BLL層理的Orders類添加GetOrderById方法,方法體內對id參數進行有效驗證后調用CommonDAL
    層里的Orders類的GetOrderById方法返回數據。
  3. Web層直接調用BLL層Orders類的GetOrderById方法

你看,原來需要在IDAL 的IProduct接口添加一個方法定義,SqlServerDAL層和OracleDAL層的Product類都要實現該方法,
然后BLL層,Web才能相繼實現調用,現在是不是簡單省事多了呢?

  1. 實現關鍵點:DbProviderFacoty

    前面啰啰嗦嗦講了這么多,其實就是一句話,用ADO.NET的抽象基類取代原來的子類 ,抽象出通用數據庫訪問類,
    使得開發人員無須再針對n種數據庫實現n個DAL。然而,類似原來BLL調用IDAL接口時,需要加載配置文件指定的
    某種數據庫的實現層DAL模塊,通過抽象工廠方式創建接口的實現類一樣,現在當BLL調用CommonDAL里的
    DBHelper時,里面的ADO.NET抽象基類也需要由針對指定某種數據庫的某類ADO.NET組件子類來進行實例化,
    實際上,ADO.NET組件里已經定義了這樣的一個工廠類,專門負責創建ADO.NET組件。

所以,通過該工廠類來創建ADO.NET組件,我們便已可以把指定ADO.NET組件抽象基類的創建,統一交由該工廠類來負責,
而用哪一類數據庫(MSSQL,Oracle,OleDB…)的ADO.NET組件子類來實例化其抽象基類,則決定於該工廠抽象基類用哪一類數據庫
的工廠子類來創建,換言之,支持哪種數據庫(MESQL,Oracle…)的決定權在於DbProviderFacoty用哪個數據庫的繼承子類
(SqlClientFacoty,OracleClientFacoty)來實例化,所以,DbProviderFacoty如何創建出來便是全局關鍵所在。

  1. DbProviderFacoty的創建

    事實上Visual Stduio里面通過圖形化界面建立數據庫連接便是使用DbProviderFacoty實現多數據庫訪問的實例。當我們打開
    Visual Sdudio在項目下點擊Properties
    àSettings.Settings,打開頁面,為當前應用程序配置數據庫連接時,Visual Sdudio
    會自動在項目配置文件生成數據連接的配置節點。以SQL Server為例,大致如下

<connectionStrings>

<add name="*******.Properties.Settings.MSSQL" connectionString="****** "

providerName="System.Data.SqlClient" />

</connectionStrings>

 

這里providerName節點有何含義呢?我們打開.NET的系統級配置文件C:\Windows\Microsoft.NET\Framework\v2.0.50727\
CONFIG\machine.config,其中便有關於DbProviderFactory的配置

<system.data>

        <DbProviderFactories>

...            

            <add name="OracleClient Data Provider" invariant="System.Data.OracleClient"

description=".Net Framework Data Provider for Oracle"

type="System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
"/>

            <add name="SqlClient Data Provider" invariant="System.Data.SqlClient"

description=".Net Framework Data Provider for SqlServer"

type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
"/>

...

        </DbProviderFactories>

</system.data>

很顯然,在DbProviderFactories 里是DbProviderFactory的多個代表不同數據庫的子類信息集合,拿invariant屬性的值與
數據庫連接串里providerName匹配,便可得到當前數據庫連接所對應的DbProviderFactory子類信息,再通過反射,便可
得到DbProviderFactory的子類實例了。當然,關於machine.config配置信息的讀取以及數據源的配置,這里面也有很多內
容,但與本文主題關系不大,不再贅述。有興趣的讀者可以上微軟官方網站查閱,我這里只是通過列舉微軟對DbProvider
Factory的應用,闡述如何創建DbProviderFactory。事實上,我認為也可以自定義一個配置文件存儲DbProviderFactories,
在連接串里指定providerName,使用抽象工廠模式實現DbProviderFactory的創建。

  1. 其他改進
  • 當然,要讓DAL通過配置而非修改代碼的方式適應不同的數據庫,還有其它產生差異的地方需要進行處理。比如不同的
    數據庫的sql語法是有所差異的,PetShop里DAL層定義的sql語句,我們可以把它們放置於外部的XML文件下,通過讀
    取xml節點獲取相應的sql語句。通過配置文件方式為不同的數據庫指定使用不同的xml文件,也可以只使用一個xml文件,
    切換不同的數據庫的時候用同名的xml文件去替換(如果應用系統只使用一種類型的數據庫),再如 字段的數據類型也有
    所不同,再進行數據庫設計時就要充分考慮使用標准化的,可轉換的數據類型,如Oracle中的Number類型會被ADO.NET
    處理為Decimal,而SQLServer中的int則會轉換為int,這樣就不妨用Number類型定義整數類型的字段。再有一些更大的差
    異,如大字段,二進制數據的處理等,可以考慮使用接口方式,初始化時注入針對當前數據庫的實現類。
  • DBUtility項目的數據庫連接串。DBUtility項目里的DBHelper為DAL層調用,本來可以看作是DAL的低層,作為引用庫文
    件封裝在DAL里,但偏偏這里讀取數據庫連接是直接從表現層的配置文件里讀取,既限造成了連接節點的名稱的硬編碼,
    也無法支持同時對多個數據庫的操作。從某種意義上來說造成了對界面層的依賴,使得各層之間的依賴形成了糟糕的閉環。
    為了實現各層之間的單向依賴,同時把與業務無關的DBHelper封裝成可復用的標准化數據庫訪問組件,成為DAL的引用dll,
    簡化框架結構,我認為數據庫連接串還是應該由表現層傳遞至數據訪問層,至於傳遞的方法,可以使用單例模式創建工廠對象,
    通過工廠對象創建下層服務對象,而在工廠對象創建時始化數據庫連接等一系列相關信息。這樣就避免了在調用下層服務
    對象時把連接參數傳遞進去。
  1. 后記

    三層架構是老生常談了,很多程序員都耳熟能詳,但是很多初學者甚至老鳥、大蝦,在搭建項目架構時,都是參考PetShop,
    依葫蘆畫瓢,搞個IADL接口層,再整N個DAL作為實現,將所有sql語句,邏輯代碼都放入一個DAL,然后再Copy一份到其他
    的DAL稍作修改, BLL就僅僅寫個轉發方法供應用層調用,空盪盪無任何實質內容。名為三層,實則兩層,我認為還是對ADO
    .NET缺乏深入了解導致的,而現在涌現出各種ORM框架(Entity Framewokr,SQL To Linq,Nhibernate等),以實體對象方式
    操作數據,雖然在一定程度上讓開發者隔離了底層ADO.NET,但ORM並非萬能,我認為目前甚至將來很長一段時間也不能完全
    取代ADO.NET,遇到復雜SQL或者對性能要求很高的地方,仍然需要直接調用ADO.NET,而且,這些ORM框架本身就是構建於
    ADO.NET之上,所以改進、擴展甚至自主開發ORM框架,都需要我們深入了解並掌握ADO.NET的用法。


免責聲明!

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



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