對比.NET PetShop和Duwamish來探討Ado.NET的數據庫編程模式

.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兩種語言版本,並且還附上了大量詳盡的中文資料,如果打印出來,實在是居家旅行,臨睡入廁必備之物.

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那種輕靈的風格 :) 

 
發佈了27 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章