一步一步學習開發BPM工作流系統--------(四)BPM數據庫訪問層


    設計一套適合自己的數據庫訪問層。   

    net領域提到數據訪問層,莫過於微軟的企業庫,我更喜歡微軟早期的sqlhelper輕量級的版本,企業庫是一個比較龐大的數據底層,但是並不一定實用,微軟的東西有個特點版本更新比較快,盡量不要跟的太緊,企業庫里面很多思想是可以借鑒的,模仿企業庫編寫一個數據訪問層,取其精華棄其糟粕,在項目中不斷完善,編寫適合自己訪問層一直是我的夢想,經過多年的積累終於完成了,並在幾個大型項目中實驗通過,效果還不錯。在眼花繚亂的眾多框架和平台中尋求一種適合自己的,無論是改造還是原創,讓它變成自己的核心技術,才具有戰斗力,切記這一點!

   下面介紹一下我是如何設計的,數據庫訪問層需要用到的知識點是數據庫的基本訪問方法:連接、增刪改查等。學會這個是數據庫開發的第一步,作為程序員必須掌握的技術。我們要設計的數據訪問層肯定要與眾不同,要有他的特色!下面先介紹需求和實現原理,重點部分貼出代碼,最后有一個完整的demo源碼。

先看一下要實現的功能:

  1、支持多數據庫,最起碼支持常見的Sql Server,Oracle,My Sql ,Access 等。實現支持多數據庫,這個不是很容易,盡管很多框架都支持多數據庫,但是仔細分析並不一定適合自己,一些數據庫的sql語法不一樣,很難達到統一。我們這里支持多數據庫 也是分別使用數據庫的驅動進行連接,每個數據庫都要建立自己的視圖和存儲過程。有很多軟件通過控制程序編寫來支持多數據庫,不使用任何視圖和存儲過程,這個方式在簡單的業務中使用還可以,要是復雜的業務就很難做到,最起碼性能會大打折扣。根據數據庫類型選擇使用數據庫驅動,這是最好的方案,例如System.Data.SqlClient,System.Data.OracleClient,這是真正的支持多數據庫,每種數據庫都有自己的存儲過程和視圖,分別去實現它,需要說明的是Access數據庫不支持視圖和存儲過程,暫時不考慮這種數據庫了。

  2、支持遠程訪問,通常我們常見的數據訪問層都是訪問本地數據庫的(雖然可以直接連接遠程數據庫,但這不是一個好的方式),我們編寫一個即支持本地訪問又支持遠程訪問的訪問層,這就是我們設計的特色。要做到這一點,需要在中間加一個代理層,這樣做的好處可以隨時更換代理層,其他層不受影響,這樣就可以實現網絡版和單機版的切換了。

我們先來看看實現原理:

數據庫訪問層設計如下圖,這個是一個綜合對比圖,我們的數據庫訪問層增加了代理層ClientDBAgent(右圖粉色部分),如果這個圖看不懂先看后面的文章,看完后面的介紹這個圖就自然而然的懂了:

imageimage

    數據庫訪問的核心是提交Sql語句,sql 語句包括insert update select 和存儲過程,業務層發送sql語句給數據庫訪問層,數據訪問層根據sql語句判斷調用什么類型的數據庫驅動,完成一次數據訪問,不同的數據庫類型創建不同的數據庫對象,例如創建sql server的數據庫對象,_dataBase = new SqlDatabase();創建Oracle的數據庫對象 _dataBase = new OracleDatabase(),在net類庫中分別對應System.Data.SqlClient和System.Data.OracleClient,net為每種數據庫訪問提供了驅動類庫,要正確使用對應的類庫。

    來看一個數據訪問的簡單的圖列,從業務層到數據訪問層:

  

    業務層如何告知訪問層訪問那個數據類型?

    需要在sql語句傳遞一個參數-數據庫連接參數,這個參數包含的信息不能太復雜,否則我們調用起來很麻煩,這個參數跟sql語句一起傳給數據訪問層,為此我們定義一個自定義類型:SqlDataItem,包含的屬性有{CommandText,DatabaseName,Parameters...}這三個參數分別對應sql語句,數據庫連接字符串名(對應配置文件),sql語句里面需要的參數和值。這樣我們在業務層調用數據庫的時候就可以這樣寫:

 

           ClientDBAgent agent = new ClientDBAgent();
           SqlDataItem sqlItem = new SqlDataItem();
           sqlItem.DatabaseName = "HFbpmDatabase";//數據庫連接字符串名,跟配置文件對應
           sqlItem.CommandText = "select * from VipCards";//sql 語句
           sqlItem.CommandType = CommandType.Text.ToString();
           DataSet ds = agent.ExecuteQuery(sqlItem);
           dataGridView1.DataSource = ds.Tables[0];

 

  這樣就可以把sql語句提交給數據訪問層了。

    數據訪問層如何處理這個Sql語句呢?

    看上面的代碼和圖例,sqlitem是用ClientDBAgent來處理的,agent是代理的意思,這種設計是可以隨時換掉這個中間層的agent類。我們可以設計2種代理類,一種是直接訪問數據庫的代理類,一種是可以訪問遠程服務的代理類。理論上可以任意編寫這個代理類,只要提供同名的方法即可,這就是代理的作用。用這種方式我們編寫了一個可以訪問遠程數據庫的代理類,這個在后面介紹。我們可以看出要想隨意切換代理類,代理類必須具備相同的名字和方法,否則沒法替換掉。我們先來看一下直接訪問數據庫的代理類。

    前面一再強調代理類需要使用統一的命名空間、類名以及方法名,為了准確無誤的做到這一點,我們為代理類定義了一個統一的接口類IDBAgent,代理類都要實現該接口。在IDBAgent接口中我們定義了所有數據庫訪問的方法。如下圖:

 

下面我們來剖析ClientDBAgent兩種代理類的實現,一種是直接訪問數據庫的,一種是訪問遠程數據庫服務的。

 1、直接訪問數據庫的代理

     這種方式的數據庫一般是在本機或者局域網內,直接連接數據庫,首先確定訪問的數據庫類型,然后創建數據庫對象,舉一個數據庫查詢的方法。代碼如下:

    

           CreateProvider方法根據數據庫類型創建不同的數據庫對象。看一下這個方法的核心:

          

根據Sql語句的數據庫連接的參數判斷創建那種數據庫驅動。因為每種數據庫的訪問存在很多差異,所以每種數據庫類型必須單獨定義他的方法,如下圖:

 

如上圖我們定義了四種數據庫。如果需要支持其他數據庫需要擴展這個類。HF.DataAccess.DataClient就是數據庫訪問層,所有代理類的最終實現都在這里。本地訪問的類庫之間的關系如下圖:

2、遠程訪問數據庫的代理

    在實際應用中,數據庫可能在遠程的某台服務器上,這種情況如果使用直接連接的方式並不是最佳選擇,從分布式架構上來說,最理想的方法是做服務,每個功能提供一個服務,客戶端調用這些服務。這樣固然很好,但是比較繁瑣,一個功能提供一個方法一個系統下來會有很多這樣的方法,雖然理論上都這樣推進,但是並不適用。在跨系統數據整合的時候可以使用這樣的方式,畢竟跨系統整合針對性比較強,方法也相對固定。對應一個完整的系統,要提供遠程的訪問,我的建議是只把數據庫訪問層做成服務。數據訪問邏輯如下圖:

   

    這個訪問邏輯與本地訪問邏輯的不同是代理層不是直接訪問的數據庫DataClient而是訪問的數據庫服務。代理層的作用就發揮出來了,有興趣的可以編寫輕量級的socket的代理層。

    遠程服務的提供方式包括WCF、WebService、Remoting、Socket等,Wcf和webservice是最常用的,也是微軟大力推薦的,wcf是微軟新一代的數據通訊框架,無論從安全性還是執行效率上都是一流的。它既可以宿主與系統服務又可以宿主與iis還可以宿主winform,部署起來也很方便,建議使用系統服務的方式做宿主,穩定性好。我們就用這種方式來實現數據庫服務。

    開發WCF的數據庫服務需要用到兩方面的知識,一個是WCF,一個是系統服務WinServer,這兩方面的知識講起來都可以寫一本書,可見一個小東西都用到方方面面的知識,這里這說重點,詳細的可以參見源碼。業務層提交的sql語句都屬於文字信息一般不會很大,wcf默認的數據包是65536,如果數據超過這個值,那么提交的時候就會報錯,為了避免這種情況,我們把它設成最大值maxReceivedMessageSize="2147483647",客戶端也要做相應的設置。一個完整的wcf服務端的配置:

<system.serviceModel>
    <services>
      <service behaviorConfiguration="HF.HFService.DatabaseServiceBehavior" name="HF.WCF.DBService.DatabaseService">
        <endpoint address="" binding="basicHttpBinding" contract="HF.WCF.DBService.IDataBaseService" bindingConfiguration="basicHttp">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8801/HFService/DatabaseService/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="HF.HFService.DatabaseServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
          <serviceThrottling maxConcurrentCalls="150"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="basicHttp" closeTimeout="00:00:10" openTimeout="00:00:10" receiveTimeout="00:00:10" sendTimeout="00:00:10" allowCookies="false" 
                 bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="2147483647" maxBufferPoolSize="524288"
                 maxReceivedMessageSize="2147483647" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
            <message clientCredentialType="UserName" algorithmSuite="Default"/>
          </security>

        </binding>
      </basicHttpBinding>
    </bindings>

  </system.serviceModel>

 

   系統服務的開發,VS也提供了模版,我們只要創建系統服務的項目即可這里不做講解,注冊系統服務的時候需要注意一下,如果程序有錯誤服務是無法啟動的,可以查看系統日志跟蹤錯誤;重新發布服務需要先把服務停止,再發布否則會沖突,無法替換。為了注冊方便提供注冊和注銷服務的代碼,注意服務名和路徑:

sc stop   HFDBWinServer
sc delete HFDBWinServer
sc create HFDBWinServer binpath= D:\work\Net\HF3.0\Release\DBWinServer發布版\HFDbWinService.exe start= auto
sc description HFDBWinServer "禾豐軟件數據庫服務"
sc start  HFDBWinServer
pause

注銷服務

sc stop HFDBWinServer
sc delete HFDBWinServer
pause

 數據庫服務做好了,下面開始設計代理層,這里的代理層要訪問遠程的數據庫服務,我們通過vs添加web引用找到服務,關於如何添加web引用,這是wcf的知識不在本文范疇。添加好web引用后,開始實現IDBAgent接口。HF.DataAccess.DBAgent如下圖,在做web服務的時候要注意,服務端的類不支持同名方法,構造函數不支持參數。

客戶端的wcf訪問配置代碼如下:

<system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="basicHttp" closeTimeout="00:00:10" openTimeout="00:00:10" receiveTimeout="00:00:10" sendTimeout="00:00:10" allowCookies="false"
                  bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="2147483647" maxBufferPoolSize="524288"
                  maxReceivedMessageSize="2147483647" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true"/>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:8801/HFService/DatabaseService/"
          binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IDataBaseService"
          contract="WCFDatabaseService.IDataBaseService" name="basicHttp" />
    </client>
  </system.serviceModel>

這里需要說明一個binging參數useDefaultWebProxy="true",默認是true,是需要web代理,一般我們是不需要web代理的,所以設置成false會提高wcf的訪問速度。

遠程訪問的代理層設計好以后,業務層是不需要做任何改變的。下面看看業務層是如何調用代理的。

  業務層

業務層直接調用代理類ClientDBAgent既可以。一個查詢數據庫表的操作,如下:

public DataTable GetLogTable(DateTime startTime, DateTime endTime)
       {
           string tmpSql = "select * from  HF_Logs where LogDatetime between @startTime and @endTime order by LogDatetime desc";
           try
           {
               SqlDataItem sqlItem = new SqlDataItem();
               sqlItem.CommandText = tmpSql;
               sqlItem.AppendParameter("@startTime", startTime,typeof(DateTime));
               sqlItem.AppendParameter("@endTime", endTime, typeof(DateTime));

               ClientDBAgent agent = new ClientDBAgent();
               return agent.ExecuteDataTable(sqlItem);
           }
           catch (Exception ex)
           {
               throw ex;
           }

一個更新數據表的操作,如下:

       public  bool SetPassword(string userId, string password)
       {
           try
           {
               string tmpMd5 = SysBlack.MD5Encrypt(password);
               string tmpSql = "update HF_User set UserPassword=@UserPassword where userid=@userid";
               SqlDataItem sqlItem = new SqlDataItem();
               sqlItem.CommandText = tmpSql;
               sqlItem.AppendParameter("@userId", userId);
               sqlItem.AppendParameter("@UserPassword", tmpMd5);
               ClientDBAgent agent = new ClientDBAgent();
              int i= agent.ExecuteNonQuery(sqlItem);
              return i == 1;

           }
           catch (Exception ex)
           {
               throw ex;
           }
       }

 

源碼可以到http://www.51aspx.com/Code/HFWorkFlow下載,這里一個比較完整BPM項目源碼,今天提前公布改源碼,希望結合本系列教程一步一步學習。



免責聲明!

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



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