摘要:本文討論如何以 ADO.NET 方式實現基本數據庫操作,以及何時使用 ADO.NET 代替 ADO。
目錄 .NET 中的數據訪問 讀取數據 DataSet、DataTable 和 Recordset 轉換現有代碼 更新數據 XML 擴展支持 總結
自若干年前推出開放式數據庫連接 (ODBC) 應用程序編程接口 (API) 以來,出現了各種各樣的數據庫訪問技術,而 ADO.NET 是其中最新的一種。在這過程中,發生了許多有趣的事。例如,COM 闖入數據庫領域,開始培植 OLE DB 的殖民進程。然后,大致相當于 OLE DB 自動化版本的 ActiveX® Data Objects (ADO) 被選來統治 Windows® 數據庫開發者的 Visual Basic® 和 ASP 社區。
通過 .NET,Microsoft 正在提供通用框架(即 Framework Class Library),其中將包括所有現有的 Windows API 甚至更多的內容。特別值得一提的是,它包括大量常用的庫,而這些庫現在需要通過各個 COM 對象分別獲得。在這些庫中,您會發現 XML 和 ADO 對象模型,它們被集成到了叫做 ADO.NET 的類子樹中。
ADO.NET 事實上成為構建數據感知 .NET 應用程序的基礎。和 ADO 不同的是,ADO.NET 遵循更通用的原則,不那么專門面向數據庫。ADO.NET 集合了所有允許數據處理的類。這些類表示具有典型數據庫功能(如索引、排序和視圖)的數據容器對象。盡管 ADO.NET 是 .NET 數據庫應用程序的權威解決方案,但從總體設計上來看,它不象 ADO 模型那樣以數據庫為中心,這是 ADO.NET 的一大特點。
ADO.NET 與 ADO 有很大差異。ADO.NET 是新的數據訪問編程模型,需要開發人員的全面理解、投入和新思維。然而,一旦開始掌握 ADO.NET,您將意識到:原有的 ADO 技巧非常有助于您以不同、卻更巧妙和可靠的方式來創建有效的應用程序和解決各種老問題。
在這篇文章的其余部分,我將集中介紹如何以 ADO.NET 方式實現基本的數據庫操作。我想說明,在什么時候 ADO.NET 是比 ADO 更好的選擇,而您最好在什么時候應放棄 ADO。ADO.NET 并不是將 ADO 改良以符合 .NET 基礎結構而形成的。只要您看一下 ADO.NET 的語法、代碼設計和移植,就會明白這一點。
.NET 中的數據訪問 在 ADO.NET 中訪問數據源的方式由托管提供程序確定。從功能上講,托管提供程序與 OLE DB 的提供程序非常相似,但有兩個重要的不同之處。首先,管理提供程序在 .NET 環境中工作,通過 DataReader 和 DataTable 等 .NET 類檢索和公開數據。其次,因為它們的體系結構針對 .NET 進行了優化,所以比較簡單。
目前 ADO.NET 提供了兩種托管提供程序:一種用于 SQL Server™ 7.0 或更高版本,另一種用于其他所有您可能已經安裝的 OLE DB 提供程序。在這兩種情況下您分別使用不同的類,但遵循相似的命名規則。除前綴外,名稱都是相同的。前一種情況前綴為 SQL,后一種情況則是 ADO。
您應該使用 SQL 類訪問 SQL Server 表,因為它們直接進入數據庫服務器的內部 API,跳過了由 OLE DB 提供程序表示的中間層。ADO 類是 OLE DB 提供程序上的 .NET 接口,它們使用 COM Interop 橋進行工作。
ADO.NET 對象的初學者可參閱 Omri Gazitt 的文章介紹 ADO+:用于 Microsoft .NET 框架的數據訪問服務(英文)和我的 ADO+ 推動數據種類的演變(英文)一文。前者技術性較強,針對 ADO.NET 程序模型提供了高水平的評注性概述。后者主要介紹 ADO.NET 的目標和它與 XML、腳本以及其他技術之間的聯系。
讀取數據 需要從數據源中讀取數據的 ADO.NET 應用程序首先要創建連接對象。根據目標提供程序的不同,該連接對象可以是 SQLConnection 或 ADOConnection。請記住,您可以使用 ADO.NET 類來連接到 SQL Server 數據庫,但我們不建議這樣做。其唯一的缺點是,您的代碼要通過不必要的額外代碼層。它先將 ADO 的托管提供程序調入,然后托管提供程序再調用 SQL Server OLE DB 提供程序。而 SQL Server 托管提供程序和 OLE DB 提供程序一樣直接操作數據。
ADO 和 ADO.NET 連接對象之間的顯著差異是:ADO.NET 連接不支持 CursorLocation 屬性。請注意,這并不是一個文檔錯誤,而是一個有爭議的設計問題。為了突出以數據為中心的原則,ADO.NET 沒有游標的顯式實現。
在 ADO 中,您習慣了用游標從數據庫或其他任何 OLE DB 兼容的數據源中抽取記錄。您可以選擇客戶端或服務器游標,每種游標都有幾個預先設定的游標類型。ADO.NET 則設計為從數據源中抽取數據,并提供新的編程接口來讀取和分析數據。
在 ADO 中,您通過指定連接和命令文本來創建 Recordset 對象。對于游標的位置和類型,Recordset 有一定策略。您可以按下列方式之一讀取數據:
在內存中創建選定記錄的靜態副本,然后在從數據源斷開連接時根據需要處理這些記錄。ADO 稱之為靜態游標。
通過快速、僅向前的只讀游標來滾動數據,這種游標工作在記錄的靜態快照中。ADO 稱之為只讀游標。
通過服務器端的兩種游標來訪問數據,這些游標需要保持良好的連接,但您可以在各個不同層次上隨時檢測其他已連接的用戶的更改。ADO 稱它們為鍵集和動態游標。 前兩種方式都在斷開連接的記錄集內工作,并從客戶端緩存讀取信息,這是它們的相似之處。另外,在面向 Web 的環境中和對于新的 n 層系統,這兩種方式被證明是使用頻率最高的。
在 ADO 中,以上所有這些方式與不同類型的游標相對應。您將在本文后面發現,雖然 ADO.NET 有很大不同,但它能實現您用 ADO 可實現的任何功能。只不過您的代碼將從實際數據源及其物理存儲媒介和格式中抽取數據。
ADO.NET 提供兩個對象來處理從數據源中抽取的數據。它們是 DataSet 和 DataReader 對象。前者是記錄在內存中的緩存,您可以從任何方向隨意訪問和修改。后者是高度優化的對象,專為以僅向前方式滾動只讀記錄而設計。請注意 DataSet 看起來象靜態游標,但實際上,在 .NET 中與 ADO 只讀游標相對應的是 DataReader 對象。
在 ADO.NET 中,不支持服務器端游標。然而,這不意味著您不能使用游標。您需要做的是在 .NET 中導入 ADO 類型庫。在項目窗口的 References 節點上單擊右鍵就行了。導入之后,您便可以開始在應用程序中使用本地 ADO 對象了。
盡管我承認下決心轉向 .NET 是一件很難的事情,但我個人還是建議您考慮用 .NET 重寫現有應用程序。可以把完全導入 ADO 作為邁向 .NET 的第一步,這無須投入太多的時間和資源。然而,請記住這只是漫漫長路上的第一步。這絕不是您邁向 .NET 的唯一一步。.NET 具有超值價值的的真正原因在于統一和一致的編程接口以及對本地類的廣泛使用。您可以導入 COM 類型庫,但導入 COM 類型庫只能作為臨時解決方案或者中間步驟,我們并不鼓勵這樣做。
使用 ADO.NET 時,應當充分考慮到它統一了數據容器類編程接口這一事實。無論您打算編寫何種應用程序,Windows 窗體、Web 窗體還是 Web 服務,都可以通過同一組類來處理數據。不管在后端的數據源是 SQL Server 數據庫、OLE DB、XML 文件還是一個數組,您都可以通過相同的方法和屬性來滾動和處理它們的內容。
圖 1:Solution Explorer 菜單
如果您堅持在 .NET 中使用 ADO,請準備面對一些副作用。例如,您需要額外的代碼才能夠從數據綁定控件中使用記錄集。
DataSet、DataTable 和 Recordset 在 ADO.NET 中,沒有與 Recordset 對象直接對應的對象。最接近的是 DataTable 對象。盡管這兩個對象的功能幾乎一樣,但它們在各自的框架中起不同的作用。
Recordset 是一個大型對象,具有許多 ADO 功能,但還是有所欠缺。Recordset 在很多方面性能優良,例如可創建性、斷開連接時仍能工作、功能豐富等等。但是,在某些方面仍然有待提高。例如,由于 Recordset 固有的 COM 特性,通過網絡進行序列化的工作將非常繁重。又如它是二進制對象,所以在不同的平臺上運行的模塊很難共享它,而且它不能穿過防火墻。另外,Recordset 表示多個記錄的單個表。如果該表是由一個或多個 JOIN 產生的,更新原始數據源可能會很困難。如果您要使斷開連接的記錄集和原始數據源保持協調,數據源必須能夠識別 SQL。然而,您的記錄集很可能是通過非 SQL 提供程序創建的。
在 ADO.NET 中,ADO Recordset 的所有功能被拆分成幾個較簡單的對象,DataReader 就是其中之一。DataReader 模擬快速、僅向前的只讀游標的操作。
DataTable 是一個表示數據源的簡單對象。您可以手動構造 DataTable,也可以通過 DataSet 命令自動填充它。DataTable 不區分它所包含的數據的來源。該對象允許您在內存中處理數據,以及執行瀏覽、排序、編輯、應用篩選器、創建視圖等操作。
ADO 中沒有與 DataSet 相對應的對象。DataSet 對象是一個容器類,是實現 ADO.NET 數據抽取的關鍵對象。DataSet 將一個或多個 DataTable 對象分組。DataTable 通過象行和列這樣的通用集合公開它的內容。當您嘗試從數據表中讀取數據時,您可能會經過兩個不同的對象層:DataTableMapping 和 DataView。
DataTableMapping 對象描述了數據源中的數據列和 DataTable 對象之間的映射關系。當填充 DataSet 時,DataSetCommand 對象要使用這個類。它維護數據集中的抽象列和數據源中的物理列之間的鏈接。
表的視圖通過 DataView 對象實現。它表示 DataTable 的自定義視圖,可以綁定到特定控件(如 Windows 窗體和 Web 窗體中的數據網格)中。該對象相當于 SQL CREATE VIEW 語句在內存中的實現。
DataSet 中的所有表都可以通過一個公用域放入關系中。這個關系由 DataRelation 對象管理。這看起來很象 ADO 的數據形成,但有一點重要區別。您不需要使用數據形成語言,您最終會擁有一個非常靈活的結構體系。ADO.NET 導航模型使您可以輕而易舉地從某一張表內的主行移入它的所有子行。
DataRelation 對象相當于 JOIN 語句在內存中的實現,可用于建立數據類型相同的列的父/子關系。一旦建立了關系,就不允許出現任何會破壞這種關系的更改,如果出現就會導致運行時異常。視圖和關系是實現主表/明細表架構的兩種方式。要記住,視圖只是放在記錄上的掩碼,而關系是設置在兩個表的一個或多個列之間的動態鏈接。如果使用關系,您不能更改順序或設置條件。
如果您的代碼需要一對一外鍵關系,并且不更改數據,那么您最好不要使用無格式的 JOIN 命令。如果您需要額外的篩選功能,就應該使用 ADO.NET 自定義視圖。
轉換現有代碼 有許多 ASP 頁面使用 ADO 對象來抽取數據。讓我們來討論幾種典型的情況,您在不久的將來移植和改編代碼時可能會遇上這些情形。
如果您有從單個記錄集生成報表的 ASP 頁面,DataReader 對象將是您最好的伙伴。 您瀏覽 DataReader 對象時,它會將結果輸出到頁面。
String strConn, strCmd; strConn = "DATABASE=MyAgenda;SERVER=localhost;UID=sa;PWD=;"; strCmd = "Select * From Names where ID=" + contactID.Text; SQLConnection oCN = new SQLConnection(strConn); SQLCommand oCMD = new SQLCommand(strCmd, oCN); oCN.Open(); SQLDataReader dr; oCMD.Execute(out dr); while (dr.Read()) { // 使用 dr.GetString(index) 或 // dr["field name"] 的方法 Response.Write 來輸出數據 }
您還可以用 HasMoreRows 屬性快速檢查 DataReader 是否為空。如果您只需要快速瀏覽一系列記錄,沒有比 DataReader 更好更快的對象了。它同樣適用于查詢單個記錄。您不能編輯 DataReader 的內容,但您可以將其內容移入更易于管理的對象,例如 DataTable 或者一個或多個 DataRow 對象。
當您需要處理表和記錄之間的復雜關系時,DataReader 就不再是合適的工具了。在 ADO 中, 最終您需要處理記錄集。您的數據模型鏈接越多,SQL 命令就越復雜。導航模型仍然是順序的,最后放入緩存的數據往往多于你所需要的。DataSet 和 DataRelation 對象是這種表關系模型的基礎。
為了管理父/子關系,ADO 還封裝了數據形成引擎。從功能上講,數據形成和 ADO.NET 關系是一樣的。然而,從設計方面來看,它們幾乎沒有什么共同點。形成記錄集將所有信息嵌入單個列表對象。ADO.NET 關系是您可以隨時在兩個數據表之間建立的動態鏈接。為了在執行單個 ADO 命令的過程中創建一個層次結構記錄集,ADO 要依靠 Shaping OLE DB 服務提供程序,并且使用特定的類 SQL 語言。
在 ADO.NET 中,關系中涉及的每個對象總是被看成單獨的個體。關系本身作為對象被公開,并且具有一定的行為規則。例如,DataRelation 對象可以從父行到子行一層層進行更改。您可以通過將 ForeignKeyConstraint 對象添加到 DataTable 的 Constraints 集合中來進行此操作。ForeignKeyConstraint 對象表示當刪除或更新數值和行時,對通過外鍵關系相關聯的一組列的約束。如前面提到的,一旦設置好了關系,在它按程序預設終止之前,您不能進行可能破壞該關系的更改。
另外,關系是不可傳遞的。您可以建立兩組不同的關系,例如客戶和訂單、訂單和產品之間的關系。然而,當在訂單中導航以尋找某一位客戶時,您不能從一個訂單跳到與之相關的產品行。您必須另外打開訂單/產品關系,定位到您需要的訂單,然后才能獲取相關的行。這就是為什么有時候最好不要通過原來的無格式 SQL JOIN 語句實現一對一關系的原因。
需要在 ASP Session 對象中存儲記錄嗎?利用 ADO.NET 和 DataSet 對象,您可以相當安全的操作而不會導致在在 GIT 中存儲 ADO 記錄集可能會導致訪問沖突(英文)中所討論的問題,也不會有線程相似性的麻煩。
更新數據 更新數據時,Web 應用程序通常使用無格式 SQL 語句,或者使用更好的參數化存儲過程。然而,當需要使用未連接的數據時,您可能想使用內置服務來更新所有需要修訂的記錄。ADO 提供了批更新機制來實現這個功能。
UpdateBatch 方法用于把保存在副本緩沖中的 Recordset 更改發送到服務器,以更新數據源。它采用開放式鎖定,允許所有掛起的本地更改。它還在單個操作中把所有更改傳送到數據源。僅當更改提交后數據源鎖定要更改的記錄時,才會出現開放式鎖定。開放式鎖定使兩個用戶可以同時訪問同一個記錄,但一個用戶輸入的更改很快會被另一用戶所覆蓋。當然,這種方式要求數據源能夠檢測和防止數據沖突。還要求整個數據源比較穩定,不會發生頻繁的更改。否則,不難想象協調費用將很快超過替代嚴格鎖定所帶來的節約。事實上,使用 UpdateBatch 方法,在任何更改失敗時都會返回一個錯誤。然后,您可以通過 Errors 集合和 Error 對象來訪問該錯誤。
要理解 ADO.NET 模型為什么是更新數據的更強大的工具,理解 ADO 中開放式鎖定的工作原理是非常關鍵的。在 ADO 代碼中,您無法控制調用 UpdateBatch 之后所發生的一切。也就是說,更新是在服務器上通過滾動已更改的行,然后比較原始值和數據源中對應記錄中的當前值來進行的。當所有的值都一致了,才對表執行適當的 SQL 語句(INSERT、UPDATE 或 DELETE)。
問題在于您不能控制實際應用于更改的 SQL 語句。服務器端的更新代碼并不比您編寫的代碼好,如果您采用非 SQL 提供程序,它甚至無法運行。在本節的開頭,我曾講過 Web 應用程序通常通過參數化存儲過程來更新數據。然而,如果您使用批更新就不同了。
在 ADO.NET 中,這個模型已經有所擴展。現在它采用更通用的架構,允許您自己指定基本操作命令,例如插入、刪除、更新和選擇等。其用意很明顯:不論何種數據源,都可以從中抽取數據并提供同樣的支持。在 ADO.NET 中進行批更新,您需要創建 DataSetCommand 對象即 SQLDataSetCommand 或 ADODataSetCommand。
注意:在 Beta 2 中,DataSetCommand 對象將被稱為 DataAdapter 對象。
擁有 DataSetCommand 對象之后,您便可以調用它的 Update 方法。DataSetCommand 提供 InsertCommand、DeleteCommand、UpdateCommand 和 SelectCommand 等屬性。它們都是 Command 對象。但是,除非默認行為無法滿足需要,否則您不必設置它們。這與在 ADO 中一樣。在 Update 過程中,如果沒有設置任何 xxxCommand 屬性,但是存在主鍵信息,將自動生成 Command 對象。請注意,要使上述過程正確進行,必須為所涉及的數據表設置主鍵。
以下代碼顯示了如何為 DataSet 的 EmployeesList 表設置主鍵:
DataColumn[] keys = new DataColumn[1]; keys[0] = m_oDS.Tables["EmployeesList"].Columns["EmployeeID"]; m_oDS.Tables["EmployeesList"].PrimaryKey = keys;
主鍵基本上是 DataColumn 對象的一個數組。
如果您要使用存儲過程來更新表,或者采用專用非 SQL 數據提供程序,您會經常用到這些命令屬性。
XML 擴展支持 在 ADO 中,XML 只不過是輸入和輸出格式。然而在 ADO.NET 中,XML 是一種數據格式,提供了操作、組織、共享和傳遞數據的手段。任何帶入 DataSet 的數據,無論其來源,都能通過雙面編程模型進行處理。您可以順序交替訪問信息,或者按行訪問,也可以按照 XML 文檔對象模型驅動的非順序、層次結構路徑進行訪問。
DataSet 將數據和架構作為 XML 文檔進行讀寫。數據和架構都可以通過 HTTP 傳輸,并且能在所有支持 XML 的平臺上使用。相同的數據在不同的時候可以通過不同的架構來呈現,這是通過 XSLT 實現的。您可以使用 ReadXmlSchema 方法編寫架構。XML 架構包括數據集中的表的說明,以及表的關系和約束。在調用 ReadXmlData 方法填充 DataSet 之前,應該先完成這個步驟。
以下代碼示例是一個顯示可更新數據表的最簡單的 ASP.NET 頁面。
<%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.IO" %>
<script runat="server" language="C#"> void Page_Load(Object source, EventArgs e) { DataSet data = new DataSet(); // 加載 XML 數據和架構 StreamReader sr; sr = new StreamReader(Server.MapPath("data.xml")); data.ReadXml(sr); sr.Close(); // 添加通過 URL 傳遞的新記錄 if (Request.QueryString.Count >0) { DataTable dt = data.Tables[0]; DataRow dr = dt.NewRow(); dr["FirstName"] = Request.QueryString["First"]; dr["LastName"] = Request.QueryString["Last"]; dt.Rows.Add(dr); dt.AcceptChanges();
StreamWriter sw; sw = new StreamWriter(Server.MapPath("data.xml")); data.WriteXml(sw); sw.Close(); }
// 刷新 UI(由網格組成) grid.DataSource = data.Tables[0].DefaultView; grid.DataBind(); } </script>
如圖 2 所示,您可以將新的行添加到表中。然而,它不涉及 SQL Server 或 Access 表。它只是一個 XML 文件,在處理它的代碼中,沒有使用 XML 節點或 XMLDOM 方法。您可以用相同的直觀數據表接口來讀取和更新 XML 記錄。您的工作方式與在 ADO 中大致相同,但此處的模型更深入、更龐大,有更多的潛力供您去發掘。
圖 2:可更新表的示例
總結 Web 應用程序的成功改變了典型分布式系統的面貌。現在大多數分布式系統都是 n 層系統,這類系統對擴展性和互操作性的要求越來越高。因此,非連接數據處理和 XML 成為最佳實踐,并為業界廣為接受。
ADO.NET 嘗試將當今一些最好的實踐統一在 .NET 下。這種用于數據訪問的編程模型全面而又非常強大。但這個模型可能尚不能滿足每一個人的要求,在將來的模型設計中還需要邁出一大步。然而,請記住現在 ADO.NET 還只是 Beta 版,只有有限的文檔支持。
ADO 程序員從 Beta 版中獲益最多,因為他們熟悉了 ADO.NET 的許多方面,包括最高層次的抽象即啟發性模型。ADO.NET 代碼與現有的 ADO 代碼不兼容,但功能相似。要充分利用 ADO.NET,您應該花些功夫來理解概念本身,而不僅僅是找出移植代碼的最快方式。無論您選擇何種 .NET 編程模型,Windows 窗體、Web 窗體還是 Web 服務,ADO.NET 都會幫助您處理好數據訪問的問題。
|