ADO.NET 賦予開發者強大的資料庫操作能力,其內建的 DataReader、DataAdapter、DataSet 等物件提供了多樣化的程式設計路徑。然而,靈活性的背面往往伴隨著選擇的困擾:究竟該用 DataReader 還是 DataAdapter?僅需讀取少量資料,是否必須填滿整個 DataSet?為何 DataReader 不像經典 ADO 的 Recordset 那樣支援直接更新?DataSet 的核心優勢究竟在哪裡?
本文將透過對微軟兩大經典範例專案——.NET PetShop 與 Duwamish——的資料存取層實作進行深度剖析與對比,幫助您釐清觀念,依據實際業務需求制定最適合的資料庫程式設計模式。
.NET PetShop 是微軟在著名的「寵物店大戰」中擊敗 J2EE PetStore 的獲勝之作,號稱以 27 倍效能優勢與 1/4 程式碼量完勝。儘管 Sun 曾質疑其公平性,但 PetShop 無疑是學習 .NET 高效能架構的經典教材。專案採用標準三層架構(表示層、中間層、資料層),程式碼精簡輕快,展現了 .NET 平台的極致效能潛力。

Duwamish 是一個外表簡單但內部極度複雜的線上書店完整應用範例。作為 Visual Studio .NET 官方 Enterprise Sample,它提供 C# 與 VB.NET 雙版本及大量中文文件。架構採用四層設計(表示層、業務外觀層、業務規則層、資料層),強調嚴謹的資料驗證、交易處理與系統健壯性,是企業級開發的教科書級參考。

兩者皆採用 N 層架構,但分層粒度不同:
本文聚焦於資料存取模式,不展開架構優劣討論。
Duwamish 採用 DataAdapter 填充 DataSet 的模式,並透過繼承 DataSet 建立強型別資料容器(如 BookData),在建構函式中預先定義資料表結構:
public class BookData : DataSet
{
public BookData() { BuildDataTables(); }
private void BuildDataTables()
{
DataTable table = new DataTable(BOOKS_TABLE);
// 定義欄位:PKID, TYPE_ID, ISBN, TITLE, PRICE...
this.Tables.Add(table);
}
}
此設計將結構定義封裝於型別內,避免執行期的 Column Mapping 開銷,確保層間傳遞的型別安全與 IntelliSense 支援。
資料層類別(如 Books)封裝 SqlDataAdapter,提供細粒度查詢方法:
private SqlDataAdapter dsCommand;
public BookData GetBookById(int bookId)
{
return FillBookData("GetBookById", "@BookId", bookId.ToString());
}
private BookData FillBookData(string cmdText, string paramName, string paramValue)
{
BookData data = new BookData();
SqlCommand cmd = dsCommand.SelectCommand;
cmd.CommandText = cmdText;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter(paramName, paramValue));
dsCommand.Fill(data);
return data;
}
觀察可見,資料層直接暴露具體查詢方法(GetBookById),上層(業務規則層)則專注於嚴格的資料驗證與業務邏輯:
public CustomerData GetCustomerByEmail(string email, string password)
{
// 前置條件驗證
ApplicationAssert.CheckCondition(email != String.Empty, "Email address is required");
ApplicationAssert.CheckCondition(password != String.Empty, "Password is required");
// 呼叫資料層
CustomerData dataSet;
using (DataAccess.Customers da = new DataAccess.Customers())
dataSet = da.LoadCustomerByEmail(email);
// 密碼驗證邏輯
DataRowCollection rows = dataSet.Tables[CustomerData.CUSTOMERS_TABLE].Rows;
if (rows.Count == 1 && rows[0][CustomerData.PASSWORD_FIELD].Equals(password))
return dataSet;
return null;
}
這體現了企業級系統對健壯性的極致追求:資料層只管存取,驗證與邏輯上移至業務層。
PetShop 資料層僅有一個 Database 類,核心方法 RunProc 直接回傳 SqlDataReader:
public void RunProc(string procName, out SqlDataReader dataReader)
{
SqlCommand cmd = CreateCommand(procName, null);
dataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
此處 DataReader 扮演「資料指標」或「資料引用」角色,連線生命週期延長至讀取完成。
中間層(如 Product 類)接管 DataReader,手動實作分頁邏輯並映射至輕量 DTO 陣列:
public ProductResults[] GetList(string catId, int currentPage, int pageSize, ref int numResults)
{
SqlDataReader reader = GetList(catId);
ProductResults[] results = new ProductResults[pageSize];
int start = (currentPage - 1) * pageSize;
// 跳過前幾頁資料
for (int i = 0; i < start - 1; i++) if (reader.Read()) numResults++;
if (start > 1) reader.Read();
// 讀取當頁資料
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();
// 修剪陣列...
return results;
}
DTO 定義極簡:
public class ProductResults
{
public string productid { get; set; }
public string name { get; set; }
}
此模式避免了 DataSet 序列化開銷,僅傳輸必要欄位,並將分頁邏輯下推至中間層,大幅減少記憶體佔用與網路傳輸量。
| 維度 | Duwamish (DataSet/離線) | PetShop (DataReader/線上) |
|---|---|---|
| 核心物件 | DataSet (強型別衍生類) | DataReader + 自訂 DTO |
| 連線模式 | 離線:Fill 即關閉連線 | 線上:讀取期間保持連線 |
| 資料更新 | DataAdapter.Update() 批次離線更新 | Command.ExecuteNonQuery() 即時線上更新 |
| 架構風格 | 嚴謹、龐大、企業級健壯 | 輕量、敏捷、效能導向 |
| 適用場景 | 複雜業務邏輯、大量批次更新、高併發需快速釋放連線 | 以讀為主、需高效能分頁、簡單 CRUD、資料量大但顯示少 |
DataReader + 手動映射 (PetShop 模式),減少記憶體壓力,縮短連線佔用(配合 CommandBehavior.CloseConnection)。DataSet + DataAdapter (Duwamish 模式),利用離線特性批次提交,降低資料庫鎖定時間。DataReader/DTO,複雜表單編輯用 DataSet,關鍵在於依業務特性在恰當層級選擇恰當工具。.NET PetShop 與 Duwamish 代表了微軟內部不同團隊對 ADO.NET 最佳實踐的兩種詮釋:一極致效能與簡潔,一極致規範與健壯。沒有銀彈,只有權衡。理解兩種模式的核心機制——線上流式讀取 vs 離線快照操作、手動映射 vs 自動填充、即時命令 vs 批次更新——才能在真實專案中遊刃有餘。
若您對此議題有更深入探討意願,歡迎來信交流:nluyan@163.NET