.Net PetShop寵物網上商店首頁
而Duwamish則是一個外表簡單,內部卻極其復雜的一個網上書店的.Net完整應用范例,作為一個微軟官方的Sample,它同時提供了C#和VB.Net兩種語言版本,并且還附上了大量詳盡的中文資料,如果打印出來,實在是居家旅行,臨睡入廁必備之物。什么?您沒聽說過?呵呵,如果您裝了Visual Studio .Net的話,它就在您的硬盤上靜靜的躺著呢,不過還沒有被安裝,您可以在您的vs.net 的Enterprise Samples目錄下找到并安裝它,例如:C:\Program Files\Microsoft Visual Studio .NET\Enterprise Samples\Duwamish 7.0 CS。
Duwamish網上電子書店首頁
結構簡述
兩家商店都采用了n層應用結構(毫無疑問,n層結構的應用架構應該絕對是您開發.Net應用的首選,哪怕您只想做一個網頁計數器),不同的是,PetShop采用的是最常見的三層應用結構,分別為表示層,中間層和數據層。而Duwamish則采用的是一個四層應用結構,并使用不同的項目分隔開,分別為表示層,業務外觀層,業務規則層和數據層。至于這兩種結構分別有什么優點和缺點,以及為什么要這么分層,我們不進行詳細討論,因為本文的重點不在于此。我們主要分析的是他們的數據庫編程的模式。
Duwamish數據訪問剖析
首先,我們來看看Duwamish書店,它采用的是DataAdapter和DataSet配合的數據存儲模式,所不同的是,它對DataSet進行子類化擴展作為數據載體,也就是采用定制的DataSet來進行層間的數據傳輸,下面是一個定制的DataSet示例:
public class BookData : DataSet { public BookData() { // // Create the tables in the dataset // BuildDataTables(); } private void BuildDataTables() { // // Create the Books table // DataTable table = new DataTable(BOOKS_TABLE); DataColumnCollection columns = table.Columns; columns.Add(PKID_FIELD, typeof(System.Int32)); columns.Add(TYPE_ID_FIELD, typeof(System.Int32)); columns.Add(PUBLISHER_ID_FIELD, typeof(System.Int32)); columns.Add(PUBLICATION_YEAR_FIELD, typeof(System.Int16)); columns.Add(ISBN_FIELD, typeof(System.String)); columns.Add(IMAGE_FILE_SPEC_FIELD, typeof(System.String)); columns.Add(TITLE_FIELD, typeof(System.String)); columns.Add(DESCRIPTION_FIELD, typeof(System.String)); columns.Add(UNIT_PRICE_FIELD, typeof(System.Decimal)); columns.Add(UNIT_COST_FIELD, typeof(System.Decimal)); columns.Add(ITEM_TYPE_FIELD, typeof(System.String)); columns.Add(PUBLISHER_NAME_FIELD, typeof(System.String)); this.Tables.Add(table); } ……… } |
我們可以看到它有一個BuildDataTables方法,并且在構造函數中調用,這樣,定制的Books表就和這個DataSet捆綁在一起了,省得以后還要進行Column Mapping,這真是個好主意,我怎么就沒有想到呢? :)
解決了數據結構,接下來看看數據層的代碼實現,在Duwamish中,數據層中有5個類,分別是Books,Categories,Customers和Orders,每個類分別只負責有關數據的存取。下面是其中一個類的示例代碼:
private SqlDataAdapter dsCommand; public BookData GetBookById(int bookId) { return FillBookData("GetBookById", "@BookId", bookId.ToString()); } private BookData FillBookData(String commandText, String paramName, String paramValue) { if (dsCommand == null ) { throw new System.ObjectDisposedException( GetType().FullName ); } BookData data = new BookData(); SqlCommand command = dsCommand.SelectCommand; command.CommandText = commandText; command.CommandType = CommandType.StoredProcedure; // use stored proc for perf SqlParameter param = new SqlParameter(paramName, SqlDbType.NVarChar, 255); param.Value = paramValue; command.Parameters.Add(param); dsCommand.Fill(data); return data; } |
這里就是數據層的代碼了,我們在這里可以看到Duwamish采用了DataAdapter來將數據填充到定制的DataSet中,然后返回該DataSet。我感到很奇怪的是在數據存取層中竟然可以看到GetBookById這樣具體的數據存取方法,雖然最后還是有一個抽象出來的FillBookData方法,但是上面還有三層啊,底層都做到這份上了,那上層都做些什么呢?答案是數據檢查,上層基本上都在做一些很嚴密的數據合法性校驗(當然也會包括一些比較復雜的事務邏輯,但是并不多),示例代碼如下:
public CustomerData GetCustomerByEmail(String emailAddress, String password) { // // Check preconditions // ApplicationAssert.CheckCondition(emailAddress != String.Empty, "Email address is required", ApplicationAssert.LineNumber); ApplicationAssert.CheckCondition(password != String.Empty, "Password is required", ApplicationAssert.LineNumber); // // Get the customer dataSet // CustomerData dataSet; using (DataAclearcase/" target="_blank" >ccess.Customers customersDataAccess = new DataAccess.Customers()) { dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress); } // // Verify the customer's password // DataRowCollection rows = dataSet.Tables[CustomerData.CUSTOMERS_TABLE].Rows; if ( ( rows.Count == 1 ) && rows[0][CustomerData.PASSWORD_FIELD].Equals(password) ) { return dataSet; } else { return null; } } |
在這個方法中,真正進行數據存取的實際上只有
dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress);
這么一句,是直接調用的數據層。其它都是在進行合法性校驗,我們可以感悟到,進行一個真正的企業級開發需要考慮的系統健壯性有多么重要。
.Net PetShop數據訪問剖析
OK,Duwamish看完了,下面我們來看看PetShop的數據訪問機制。
PetShop只有一個項目,它采用的分層辦法是將中間層和數據層都寫成cs文件放在Components目錄里,其中數據層就是一個名為Database的類,它封裝了所有對數據庫的底層操作。下面是示例代碼段:
public void RunProc(string procName, out SqlDataReader dataReader) { SqlCommand cmd = CreateCommand(procName, null); dataReader = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection); } |
我們看到了一個跟Duwamish截然不同的另一種數據訪問方式,它將所有的數據訪問方法抽象出來做成一個RunProc方法,至于返回數據呢,呵呵,它有點偷懶,直接返回一個DataReader給你,你自己去讀吧。還記得Duwamish采用的層間數據傳輸載體是什么嗎?對了,是DataSet,它被數據層填充后返回給了中間層。但是這里,數據層和傳輸層的數據傳輸載體變成了DataReader,實際上,還不能稱它為數據載體,因為數據還沒開始讀呢,在這里,DataReader的作用和指針有點類似,也許我們應該稱它為“數據引用”:)
接著往下看,DataReader被怎么“處理”的:
public ProductResults[] GetList(string catid, int currentPage, int pageSize, ref int numResults) { numResults = 0; int index=0; SqlDataReader reader = GetList(catid); ProductResults[] results = new ProductResults[pageSize]; // now loop through the list and pull out items of the specified page int start = (int)((currentPage - 1) * pageSize); if (start <= 0) start = 1; // skip for (int i = 0; i < start - 1; i++) { if (reader.Read()) numResults++; } if (start > 1) reader.Read(); // read the data we are interested in while (reader.Read()) { if (index < pageSize) { results[index] = new ProductResults(); results[index].productid = reader.GetString(0); results[index].name = reader.GetString(1); index++; } numResults++; } reader.Close(); // see if need to redim array if (index == pageSize) return results; else { // not a full page, redim array ProductResults[] results2 = new ProductResults[index]; Array.Copy(results, results2, index); return results2; } } |
注意到currentPage和pageSize了嗎?原來在這里就進行了數據分頁,只返回滿足需要的最少的數據量,而不是象我們很多喜歡偷懶的人一樣,簡單的將整個DataTable一股腦的綁定到DataGrid,造成大量的數據冗余。
在這里,數據被真正的讀出來,并且被手動填充到一個自定義的對象數組中,我們來看看這個數組的定義:
public class ProductResults { private string m_productid; private string m_name; // product props public string productid { get { return m_productid; } set { m_productid = value; } } public string name { get { return m_name; } set { m_name = value; } } } |
非常之簡單,不過我有點奇怪為什么不使用struct呢?是不是.Net中struct和class的性能差距已經可以忽略不計了?
分析總結
通過觀察這兩個商店的具體實現,我們得到了兩個不同的數據訪問模式,Duwamish采用的是以DataSet為核心,因為DataSet提供了這方面大量的相關方法,所以整個應用的數據傳輸,數據格式定義,數據校驗都圍繞著DataSet來進行,整個架構定義非常清晰和嚴謹,但是卻顯得有些龐大。PetShop在整個程序中沒有采用一個DataSet,程序非常的簡潔,輕靈,但是沒有Duwamish那么強的健壯性。這兩個程序是Microsoft公司不同的小組寫出來的代碼,所以有著不同風格。不過都應該能代表.Net的標準模式??吹竭@里,你應該對文章開頭提出的那些疑問有一個比較形象的認識了吧。
另外,請再次注意,PetShop在打開數據連接之后,并沒有馬上讀取數據,而是將DataReader傳遞給另外的對象來執行數據讀的操作,然后才關閉連接。這樣,數據連接的時間加長了,而數據庫連接是一項非常寶貴的服務器資源,相比之下,Dawamish在連接數據庫之后馬上進行填充,然后迅速釋放掉數據庫連接的方式更加有利于大量用戶的并發訪問。
再一點,上文的程序中沒有提到更新操作,PetShop采用的是使用Command對象執行單個存儲過程的方式來進行更新操作,是屬于一種在線即時數據更新模式。而Dawamish采用的是DataAdapter的Update方法,將DataSet的改變一次性的提交到數據庫中,屬于離線數據更新模式。這種模式的好處是可以一次性更新大批量數據,減少數據庫的連接次數。缺點是如果數據庫在改動非常頻繁的情況下需要實時的跟蹤數據變化就不合適了。需要根據具體的情況采用具體的數據更新辦法。
總的來說,如果您只需要快速的讀取數據并顯示出來,推薦您采用DataReader,如果您需要對數據進行大量的修改,還有大量并發訪問的可能,而且不需要實時的跟蹤數據庫的變化,推薦您使用DataSet。當然,這兩種情況有點極端了,實際的應用環境也許有著很復雜的條件,具體需要您自己審時度勢,綜合采用,不過我個人還是比較喜歡PetShop那種輕靈的風格 :)
本文只嘗試對以上兩個典型的.Net應用例程的數據訪問機制做了一個簡單的追蹤分析。
原文轉自:http://www.anti-gravitydesign.com