天天看點

對比.NET PetShop和Duwamish來探讨Ado.NET的資料庫程式設計模式

對比.NET PetShop和Duwamish來探讨Ado.NET的資料庫程式設計模式

概述

Ado.NET為我們提供了強大的資料庫開發能力,它内置的多個對象為我們的資料庫程式設計提供了不同的選擇。但是在允許我們靈活選用的同時,許多初學者也很迷惑,我到底是應該使用DataReader還是應該使用DataAdapter?我隻想讀取一小部分資料,難道我一定要Fill滿整個DataSet嗎?為什麼DataReader不能和RecordSet一樣提供一個資料更新的方法?DataSet到底有什麼好處?

在本文中,我将對.NET PetShop的資料庫程式設計模式和Duwamish的資料庫程式設計模式進行一些簡單的分析和對比。如果您也有以上疑問的話,相信在讀完本文之後,就可以根據具體的需要來制定一個最适合您應用的資料庫程式設計模式。

目錄

<b></b>.NET PetShop和Duwamish簡單介紹

<b></b>結構簡述

<b></b>Duwamish資料通路剖析

<b></b>.NET PetShop資料通路剖析

<b></b>分析總結

.NET PetShop和Duwamish簡單介紹

相信大家一定聽說過有名的"寵物店大戰",沒錯,本文的主角之一就是獲勝方.NET PetShop,微軟号稱以27倍的速度和1/4的代碼量遙遙領先于基于J2EE的PetStore寵物商店。雖然SUN也曾對此抱怨過不滿,指責此"大戰"有水分,不過無論如何,.NET PetShop絕對是一個經典的.NET執行個體教程,至少為我們提供了一條趕超J2EE的“捷徑” :),它的下載下傳位址是:http://www.gotdotnet.com/team/compare

對比.NET PetShop和Duwamish來探讨Ado.NET的資料庫程式設計模式

.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。

對比.NET PetShop和Duwamish來探讨Ado.NET的資料庫程式設計模式

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 (DataAccess.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 ) &amp;&amp; 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 &lt;= 0) start = 1; // skip for (int i = 0; i &lt; start - 1; i++) { if (reader.Read()) numResults++; } if (start &gt; 1) reader.Read(); // read the data we are interested in while (reader.Read()) { if (index &lt; 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那種輕靈的風格 :)