天天看點

.NET三層經典架構PetShop3.0分析之資料通路層---2

本文将以設計和實作緊密結合的方式來分析,這也是我們廣大實踐型的軟體開發人員的風格。先看一下設計圖和具體實作VS.NET工程的表格。

MSPetShop 3.0 系統結構圖:

從圖中可以看到系統大體分為Presentation,Business Logic,Data Access 三層,每層中又有子層。每層(也包括子層)各司其職,又互相協作,本文順序以此圖為準,從下到上分析。

對應上圖,具體的.NET Project實作清單(借用MS文章中的清單不用翻譯了吧)

Project

Purpose

BLL

Home for business logic components

ConfigTool

Administration application used to encrypt connection strings and create event log source

DALFactory

Classes used to determine which database access assembly to load

IDAL

Set of interfaces which need to be implemented by each DAL implementation

Model

Thin data classes or business entities

OracleDAL

Oracle specific implementation of the Pet Shop DAL which uses the IDAL interfaces

Post-Build

Project to run post compile actions such as adding assemblies to the GAC or COM+

Pre-Build

Project to remove assemblies from the GAC or unregister assemblies from COM+

SQLServerDAL

Microsoft SQL Server specific implementation of the Pet Shop DAL which uses the IDAL interfaces

Utility

Set of helper classes including a wrapper for the DPAPI

Web

Web pages and controls

Solution Items

Miscellaneous items used to build the application such as Pet Shop.snk key file used to sign application assemblies

另外我寫這篇文章時是一邊看源碼一邊寫,是以建意大家最好安裝一個Petshop3,因為時間倉促,水準有限,如我有不對之處請給我發Email更正。email:[email protected]      qq:364941

首先我們來看一下DAL層。

一:Data Access Layer:

1 PetShop.Utility如下圖:(上表中Utility為其實作工程)

正如上表所描述,這個名字空間有兩個類一個是ConnectionInfo用于加密解密資料庫連接配接資訊,另一個DataProtector調用了Crypt32.dll和kernel32.dll實作一些底層資料安全操作,這個類要在下面的PetShop.XXXDAL名字空間中調用,可見Petshop.Utility隻是起到的是資料通路輔助工具的作用。

2 PetShop.SQLServerDAL ――系統結構圖中DAL層中的SqlServer DAL子層實作

SqlHelper類實際上是封裝了關于此系統中資料庫操作通路的一些常用功能,其中它還會調用上面的PetShop.Utility中的ConectionInfo類方法加密解密連接配接字元串,如:ConnectionInfo.DecryptDBConnectionString方法。SqlHelper類是基于Microsoft Data Access Application Block for .NET。這個東西是用來幫助使用者更好的在.NET的通路資料。如MS一段話:Are you involved in the design and development of data access code for .NET-based applications? Have you ever felt that you write the same data access code again and again? Have you wrapped data access code in helper functions that let you call a stored procedure in one line? If so, the Microsoft® Data Access Application Block for .NET is for you。其實可以自已寫一個類似SqlHelper的東西,以實作一般化的對資料庫的操作,以在各項目中重用,當然也可以使用現在的MS為你做好的這個SqlHelper或是Microsoft® Data Access Application Block for .NET,避免不同項目中總是寫同樣的重複的資料庫通路程式。有時間最好還是看一下SqlHelper的具體程式實作思路以及所提到的那個Microsoft Data Access Application Block for .NET。不過這裡我們的SqlHelper應該隻是部分實作。更全面資訊請參看:http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp

Account類對使用者帳戶進行操作如Insert,Update,SignIn,其中這些對資料庫的操作,使用了上面的SqlHelper類來實作。另外Inventory和Order,Product,Profile和Account類的都是同樣對資料庫相關表進行操作,程式風格一緻,這些類中對資料庫的操作都是通過此名字空間下的SqlHelper類進行的,例如,下面語句:

private const string SQL_INSERT_SIGNON = "INSERT INTO SignOn VALUES (@UserId, @Password)";

private const string PARM_USER_ID = "@UserId";

private const string PARM_PASSWORD = "@Password";

來定義一個sql語句,以及聲明其中可變參數,然後像下面這樣用SqlHelper類的合适的方法執行:

SQLHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_SIGNON, signOnParms);

最後在SQLHelper.ExecuteNonQuery實作中,再調用ado.net中的相關類最終執行對資料庫的操作,可見SqlHelper在這裡又封裝了一下ado.net相關類以優化資料操作。正如SqlHelper.cs中注釋提示:The SqlHelper class is intended to encapsulate high performance,  scalable best practices for common uses of SqlClient.下面是SqlHelper. ExecuteNonQuery的實作内容:

public static int ExecuteNonQuery(string connString, CommandType cmdType, string cmdText, params SqlParameter[] cmdParms) {

              //注:運作時cmdText的實參就是SQL_INSERT_SIGNON

              SqlCommand cmd = new SqlCommand();

              using (SqlConnection conn = new SqlConnection(connString)) {

                   PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);

                   int val = cmd.ExecuteNonQuery();

                   cmd.Parameters.Clear();

                   return val;

              }

         }

另外Inventory和Order,Product,Profile和Account類的聲明都是像public class Account : IAccount這樣實作某個相關的接口,像IAccount這樣的接口是在PetShop.IDAL中聲明的,見後面介紹。

3 PetShop.OracleDAL ―――系統結構圖中 DAL層的OracleDAL子層實作

個人認為結構應該同上面的PetShop. SQLServerDAL,另外SqlHelper變成了OraHelper,在OraHelper中當然具體實作了對特定的Oracle資料庫的聯接操作,看一下源程式很明顯原來的     SqlCommand cmd = new SqlCommand(); 變成了OracleCommand cmd = new OracleCommand();。

注意一下:在系統結構圖中的DAL層還有兩個XXX DAAB的子層,它們對應的實作在哪裡呢? 下面對應一下:

以下是左邊是圖中 DataAccessLayer的各部分,右邊是具體實作所在名字空間或類

SqlServer DAL――PetShop.SQLServerDAL名字空間

Sql DAAB――PetShop.SqlServerDal.SqlHelper類

Oracle DAL――PetShop.OracleDAL名字空間

Oracle DAAB――PetShop.OracleDAL.OraHelper類

4 PetShop.IDAL 資料通路接口――對應系統結構圖中DAL Interface

接口是一種系列‘功能’的聲明或名單,接口沒有實作細節,如下接口IAccount定義也可以看出IAccount隻有聲明:

using System;

using PetShop.Model;

namespace PetShop.IDAL

{

// Inteface for the Account DAL

     public interface IAccount

     {

   // Authenticate a user

         AccountInfo SignIn(string userId, string password);

         /// Get a user's address stored in the database

         AddressInfo GetAddress(string userId);

         /// Insert an account into the database

         void Insert(AccountInfo account);

         /// Update an account in the database

         void Update(AccountInfo Account);

     }

}

您隻需要調用接口,而不用管接口是如何實作的那麼接口沒有實作,調用它有什麼用?實際上接口的實作是由某個類來做的,那麼這裡的IAccount接口是由PetShop.SqlServerDAL.Account類或是PetShop.OracleDAL.Account類來實作的,從他們的定義可以看到:

public class Account : IAccount {…….}

為什麼是兩個類都實作同一接口又是‘或’呢?因為這裡使用接口的目的就是為了統一‘外觀’,當上層BLL層調用此接口方法時不用知道這個接口由哪個類實作的。那誰來确定使用哪個類的實作?請再看下面。

(PetShop.IDAL下的其它接口和IAccount一樣,故在此略過。)

5 PetShop.DALFactory 資料通路工廠

工廠模式是設計模式的一種,以我了解就像Factory這個詞一樣,對于使用者來說,工廠裡産品如何生産的你不用知道,你隻要去用工廠裡生産出來的東西就可以了。MSPetShop3.0用工廠模式來實作了對SqlServer和Oracle資料庫通路的操作,而使用者(business Logic Layer)不用知道也不用關心背景用的是哪一種資料庫,它隻要用接口就行了,接口中定義了要用的方法,當調用接口時會根據具體的情況再去調用底層資料通路操作。而現在這個DALFactory就是關鍵,當BLL層要操作資料庫時,DALFactory會根據具體情況再去使用本文上面介紹的SqlServerDAL和OracleDAL中的一個。這樣系統上層隻管調用,而下層來實作細節,上級隻管發号施令,下級去幹活。對于上層來說實作細節被隐藏了。

那麼DALFactory是如何決定應該用SqlServerDAL還是用OracleDAL的呢?我們接着分析。

   以下是PetShop.DALFactory.Account類的實作:

  namespace PetShop.DALFactory {

     /// <summary>

     /// Factory implementaion for the Account DAL object

     /// </summary>

     public class Account

         public static PetShop.IDAL.IAccount Create()     //<<<<ß----這裡傳回接口

         {            

              /// Look up the DAL implementation we should be using

              string path = System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];

              string className = path + ".Account";

              // Using the evidence given in the config file load the appropriate assembly and class

              return (PetShop.IDAL.IAccount) Assembly.Load(path).CreateInstance(className);

  以下則是web.config中<appSettings>節點中的一部分:

<add key="WebDAL" value="PetShop.SQLServerDAL" />

    <add key="OrdersDAL" value="PetShop.SQLServerDAL" /> 

<add key="Event Log Source" value=".NET Pet Shop" />

上面的Create()方法傳回IAccount接口,用System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];則可以得到Web.config的<appsettings>節點中的關于系統中應該使用哪個資料通路層(SqlserverDAL還是OracleDAL)的資訊。因為我在安裝PetShop3.0時選擇的是Sqlserver是以在此是:value="PetShop.SQLServerDAL",如果用的是Oracle那就是value="PetShop.OracleDAL" 了吧!而且這個檔案也應該是可以更改的。接下來className=path+”.Account”傳回的應該是PetShop.SQLServerDAL.Account,然後再用Assembly.Load加載PetShop.SQLServerDAL.dll,同時建立PetShop.SQLServerDAL.Account的執行個體,并以接口(PetShop.IDAL.IAccount)類型傳回。這樣BLL調用IAccount接口時就會用PetShop.SQLServerDAL.Account類的實作代碼。(回上面第4再看一下)

看!這樣根據系統目前Web.config檔案的配置描述(這也應該是系統運作時實際的配置),BLL層隻要像下面這樣:

// Get an instance of the account DAL using the DALFactory

              IAccount dal = PetShop.DALFactory.Account.Create();

AccountInfo account = dal.SignIn(userId, password);//<<ß----看看上面第4點的IAccount接口

就可以直接調用接口方法通過下層DAL層操作資料庫了(在此具體為使用者賬号相關操作),而BLL層并不用知道應該通過SqlserverDAL還是OracleDAL通路資料庫,這由都DAL Factory決定,你用的是什麼資料庫以及底層細節,更不用BLL知道,這樣做的好處是對于BLL層以及更上層的程式不會或很少機率會因為底層程式變動影響,因為BLL層中調用接口就行了,隻要那個接口定義沒變,一切仍然OK.

6  PetShop.ConfigTool

首先在..\Microsoft\PetShop\ConfigTool\中有一個app.config檔案,看一下其中内容,分别定義了兩種資料庫的聯接字元串,在app.config中有一行  <add key="WebConfigFileLocation" value="Web\Web.config" /> 則辨別出給asp.net程式使用的web.config配置檔案的相對位置。然後看一下PetShopConnectionString的EncryptConnectionString方法的源碼,這個類中先是從目前目錄的app.config檔案中讀出web.config檔案的位置,如下:

public static readonly string CONFIGFILE = ConfigurationSettings.AppSettings["WebConfigFileLocation"];

然後語句

XmlDocument doc = new XmlDocument();

doc.Load(CONFIGFILE);

加載Web.config檔案,最後将加密的連接配接字元串寫入Web.config對應的XML節點中。以供Asp.net應用程式使用。其中的加密還是使用PetShop.Utility。

而ConfigConsole,調用PetShopConnectionString類EncryptConnectionString執行對聯接字元串進行加密。另外PetShopEventLog類也是在ConfigConsole中使用的。用于記錄程式日志。

是以在最後部署時Web.config的連接配接字元串是加密的。

7 PetShop.Model   業務實體模型

   這個本來想在分析BLL層時再說,但是在SqlServerDAL和OracleDAL中都使用了這些Model,無論怎麼樣,上層的程式執行最終結果都是要操作資料庫,而資料庫是關系型,不是面向對象的,那就得把平面的‘表’結合業務規則抽象成類,這樣想辦法讓上層(BLL及以上)以為自已在操作類而不是資料庫表,進而使‘它們’感覺沒有資料庫的存在,上層隻管面向對象程式設計就可以了。類似現在所說的O-R MAPPING,但O-R MAPPING比這種簡單的資料到對象的持久化要複雜。據說可以在表結構有變化的情況下,上層應用程式代碼不用更改,隻要改O-R MAPPING的相關設定就可以了。 

   上面第2節中已看到Petshop的SqlServerDal和OracleDal中定義的sql語句,然後根據上層的調用,把sql語句傳給SqlHelper執行,再來看看SqlServerDal的一段程式:

     private void SetAccountParameters(SqlParameter[] parms, AccountInfo acc) {

              parms[0].Value = acc.Email;

              parms[1].Value = acc.Address.FirstName;

              parms[2].Value = acc.Address.LastName;

              parms[3].Value = acc.Address.Address1;

              parms[4].Value = acc.Address.Address2;

              parms[5].Value = acc.Address.City;

              parms[6].Value = acc.Address.State;

              parms[7].Value = acc.Address.Zip;

              parms[8].Value = acc.Address.Country;

              parms[9].Value = acc.Address.Phone;

              parms[10].Value = acc.UserId;

parms[x]就是那些有聲明為常量的有參數的Sql語句中的參數,這裡用Model中的AccountInfo的各Field和這些參數Mapping。是以對于BLL層,隻知道這些Model就可以了,反正最後在SqlServerDAL或是OracleDAL中Model的成員們會Mapping到參數中以存取資料庫(為什麼不直接Mapping到資料集的字段呢?我想應該是因為這裡資料庫操作直接給SqlHeper的原因,不然就沒必要用SqlHelper了。于是才又多了圖中的xxx DAAB層,這樣Mapping給參數再傳給DAAB來處理)

Data Access Layer總結:

  DAL完成資料庫通路任務,上層(BLL)直需調用接口即可,不用知道具體通路細節,用Factory模式來實作,使用運作時讀取web.config的方法來得到連接配接配置資訊,最後選用SqlServerDAL或OracleDAL之一的相對具體子層,同時使用SqlHelper或OraHelper之一來完成資料庫操作。