概述
Ado.NET為我們提供了強大的數據庫開發能力,它內置的多個對 象為我們的數據庫編程提供了不同的選擇。但是在允許我們靈活選用的同時,許 多初學者也很迷惑,我到底是應該使用DataReader還是應該使用DataAdapter? 我只想讀取一小部分數據,難道我一定要Fill滿整個DataSet嗎?為什麼 DataReader不能和RecordSet一樣提供一個數據更新的方法?DataSet到底有什麼 好處?
在本文中,我將對.NET PetShop的數據庫編程模式和Duwamish的 數據庫編程模式進行一些簡單的分析和對比。如果您也有以上疑問的話,相信在 讀完本文之後,就可以根據具體的需要來制定一個最適合您應用的數據庫編程模式。
目錄
.NET PetShop和Duwamish簡單介紹
結構簡述
Duwamish數據訪問剖析
.NET PetShop數據訪問剖析
分析 總結
.NET PetShop和Duwamish簡單介紹
相信大家一定聽說過有名 的"寵物店大戰",沒錯,本文的主角之一就是獲勝方.NET PetShop, 微軟號稱以27倍的速度和1/4的代碼量遙遙領先於基於J2EE的PetStore寵物商店 。雖然SUN也曾對此抱怨過不滿,指責此"大戰"有水分,不過無論如 何,.NET PetShop絕對是一個經典的.NET實例教程,至少為我們提供了一條趕超 J2EE的“捷徑” :),它的下載地址是: http://www.gotdotnet.com/team/compare
.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 (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 ) && 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應用例程的數據訪 問機制做了一個簡單的追蹤分析,如果有希望能對此例進行其它方面的研究或者 就本話題繼續進行更加深入探討的朋友,請發Email到我的信箱: [email protected],謝謝。