Microsoft PetShop 3.0 設計與實作 分析報告―――資料通路層 | ||||||||||||||||||||||||||
Microsoft PetShop 3.0 設計與實作--資料通路層 最近對多層設計實作和.Net産生了興趣,進而研究了一下比較著名的多層範例程式――PetShop,現在的版本是3.0,和以前的版本從設計上已有一定的差別,應該是和Java的Petshop設計相當。 關于一些Microsoft PetShop的來由、如何安裝,所表現業務流程,資料庫表結構等基本的資訊的資料請大家參考下面文章 http://msdn.microsoft.com/library/en-us/dnbda/html/bdasamppet.asp 另外建議先看一下這篇文章: http://msdn.microsoft.com/library/en-us/dnbda/html/petshop3x.asp (如有格式問題本文可到http://www.surfsky.com/bbs/myfiles/MSPetShop3.0%20Report.doc 下載下傳) 本文将以設計和實作緊密結合的方式來分析,這也是我們廣大實踐型的軟體開發人員的風格。先看一下設計圖和具體實作VS.NET工程的表格。 MSPetShop 3.0 系統結構圖: ![]() 從圖中可以看到系統大體分為Presentation,Business Logic,Data Access 三層,每層中又有子層。每層(也包括子層)各司其職,又互相協作,本文順序以此圖為準,從下到上分析。 對應上圖,具體的.NET Project實作清單(借用MS文章中的清單不用翻譯了吧)
另外我寫這篇文章時是一邊看源碼一邊寫,是以建意大家最好安裝一個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 業務實體模型 http://www.surfsky.com/bbs/myfiles/7.bmp 這個本來想在分析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之一來完成資料庫操作。 |