ADO.NET

**ADO.NET** 是微軟在.NET Framework 中負責資料存取的類別庫集，它是使用在 COM 時代奠基的 OLE DB 技術以及.NET Framework 的類別庫和程式語言來發展的，它可以讓.NET 上的任何程式語言能夠連接並存取關聯式資料庫與非資料庫型資料來源（例如 XML，Excel 或是文字檔資料），或是獨立出來作為處理應用程式資料的類別物件，其在.NET Framework 中的地位是舉足輕重，許多人將 ADO.NET 視為 ActiveX Data Objects (ADO) 的下一個版本，但其實它是一個全新的架構、產品與概念。

ADO.NET 類封裝在 System.Data.dll 中，並且與 System.Xml.dll 中的 XML 類整合。

## 歷史

### 發展緣起

在 1990 年代初期，微軟已經開發了許多的資料存取方式，像是 ODBC 架構、和 Microsoft Access 資料庫互動使用的 DAO 物件、可以跨越網路存取資料的 RDO 以及讓 DAO 元件可以存取 ODBC 資料來源的 ODBCDirect 技術等等，技術雖然多，但是卻又各自為政，而且每個技術的重疊性也很高（像是 ODBC 有 Microsoft Access 的驅動程式）；RDO 雖然可跨網路，但是 ODBC 的驅動程式中也有提供跨網路的功能（像是 SQL Server 和 Oracle 驅動程式），如此琳琅滿目重疊性又高的技術群，讓企業與開發人員在選擇、學習與應用上產生了很多的困難。

1996 年，適逢 COM 的出現，微軟將資料存取的核心開始改寫為以 COM 為基礎的 OLE DB，並且在它上面建立一個新的統一的資料存取的高層物件模型－ADO。ADO 推出後順利的取代了 DAO 和 RDO，成為在 Windows NT 4.0 和 Windows 2000 作業系統上開發資料庫應用程式的首選。它將 OLE DB 的物件模型進一步簡化；由資料庫廠商開發滿足 OLE DB 介面的資料提供者（data provider，這個模式在此時奠基），而 ADO 本身則是與資料來源無關（data source independent）的物件結構，讓它迅速的獲得了使用 ASP、Visual Basic、COM 的開發人員的青睞。它能夠順利取代 DAO 與 RDO，要關鍵在於 ADO 的與資料庫伺服器端 / 客戶端的特性無關，這使得 ADO 通用性極好。然而 ADO 本身的架構仍然有缺陷（尤其是在開發網路應用程式時，最好的例子就是 Recordset 無法離線），這也是微軟為何不在.NET Framework 中繼續使用 ADO 的主要原因。

1998 年時，微軟提出了一個下一代的應用程式開發框架（Application Framework）的計畫，計畫中包含了：

* **ASP+**：改良與重新設計 ASP 技術，強化它的 Web 應用程式發展能力。
* **Storage+**：發展新的資料庫與物件導向之檔案系統結構（用於 SQL Server 8.0（即後來的 SQL Server 2000）與 NTFS），以及發展新一代的資料存取元件，並改良 ADO 本身的缺陷，讓它更能夠成為應用程式資料存取的核心功能。
* **COM+**：改良 COM 和 MTS，成為企業級應用程式開發的基礎元件。

ADO + 即為 Storage + 的一支。

### ADO.NET 的前身：ADO+

1998 年起，因為 Web 應用程式的竄起，大大改變了許多應用程式的設計方式，傳統的資料庫連線儲存設計法無法適用於此類應用程式，這讓 ADO 應用程式遇到了很大的瓶頸，也讓微軟開始思考讓資料集（Resultset，在 ADO 中稱為 Recordset）能夠離線化的能力，以及能在使用者端建立一個小型資料庫的概念，這個概念就是 ADO.NET 中離線型資料模型（disconnected data model）的基礎，而在 ADO 的使用情形來看，資料庫連線以及資源耗用的情形較嚴重（像是 Server-side cursor 或是 Recordset.Open 會保持連線狀態），在 ADO.NET 中也改良了這些物件，構成了能夠減少資料庫連線和資源使用量的功能。XML 的使用也是這個版本的重要發展之一。

2000 年，微軟的 Microsoft .NET 計畫開始成形，許多的微軟產品都冠上.NET 的標籤，ADO + 也不例外，改名為 ADO.NET，並包裝到.NET Framework 類別庫中，成為.NET 平台中唯一的資料存取元件。

## 架構

ADO.NET 由連線資料來源（connected data source）以及離線資料模型（disconnected data model）兩個部份構成，這兩個部份是相輔相成的。

### 連線資料來源

若沒辦法連線到資料庫，則無法被稱為資料存取元件。連線資料來源便是用來連接資料庫（或是具有 OLE DB 資料來源提供者）的物件類別，由下列介面構成：

* **IDbConnection**，負責與資料庫的連線管理，包含連線字串（connection string），連線的開關，資料庫交易的啟始與連線錯誤的處理，所有的 ADO.NET 資料提供者都要實作此介面。
  * `Open()/Close()`：開啟與關閉資料庫連線。
  * `BeginTransaction()`：啟動資料庫交易，並回傳一個 `IDbTransaction` 物件，以控制交易的結果。
* **IDbCommand**，負責執行資料庫指令（在大多數的案例中都是 SQL 指令），並傳回由資料庫中擷取的結果集，或是執行不回傳結果集的資料庫指令。
  * `ExecuteNonQuery()`：執行不回傳結果集的資料庫指令，像是 `INSERT`、`UPDATE` 與 `DELETE` 指令，返回值為該命令所影響的行數。 對於其他所有類型的語句，返回值 為 -1。
  * `ExecuteScalar()`：執行指令並回傳第一列第一行中的值（object 類型）。當沒有資料時，ExcuteScalar 方法返回 System.DBNull。
  * `ExecuteReader()`：執行指令並回傳 IDataReader 物件，以讀取資料集中的資料。
  * `BeginExecuteNonQuery`：開始執行非同步查詢
  * `EndExecuteNonQuery`: 結束執行非同步查詢
* **IDataParameter**，負責裝載資料庫指令所需要的參數資料，在使用參數化查詢時會經常使用。 對於不同的資料來源來說，預留位置不同。SQLServer 資料來源用 @parametername 格式來命名參數，OleDb 以及 Odbc 資料來源均用問號（?）來標識參數位置，而 Oracle 則以:parmname 格式使用命名參數。
* **IDbTransaction**，負責裝載資料庫交易所需的控制物件，以執行交易的認可（commit）或復原（rollback）的工作。
  * `Commit()`：認可資料庫交易。
  * `Rollback()`：復原資料庫交易。
* **IDbDataAdapter**，負責將來自於 IDbCommand 執行取得的結果集，裝載到離線型資料集（DataSet）或是離線型資料表（DataTable）中。
  * `Fill()`：將資料填入離線型資料物件。
  * `Update()`：將變更過的離線型資料物件中的資料寫回資料庫。
* **IDataReader**，建立一個只可向前讀取游標（forward-only）的資料讀取器工具，以逐列讀取方式存取資料，`IDbDataAdapter` 內部也是由它來讀取資料。
  * `Read()`：第一次呼叫 Read () 方法取得第一行資料，並將游標指向下一行資料。當再次呼叫該方法時候，將讀取下一行資料。當檢測到不再有資料行時，Read () 方法將返回 `false`。
* **IDataRecord**，在 `IDataReader` 讀取資料後實際裝載資料列的物件，提供方法來讀取資料行中的資料，以及轉換成.NET Framework 原生型別的工具。
  * `GetOrdinal()`：取得指定資料行的欄位索引值。
  * `IsDBNull()`：判斷指定欄位的資料是否為 NULL 值。

使用連線資料來源需要由開發人員自我管理連線，並且直接操作資料存取的相關細節，但它的優點是速度快，而且可以自訂整個資料存取流程的邏輯。

 

### ADO.NET 資料提供者 (Data Provider)

在.NET Framework 中，ADO.NET 預設提供了四種資料來源：

* SQL Server：由 System.Data.SqlClient 提供原生資料來源，是微軟官方建議存取 SQL Server 時建議使用的資料提供者。以 Sql 為字首的類別群
* OLE DB Data Source：由 System.Data.OleDb 提供支援，可適用於 OLE DB Provider for ODBC 以外的 OLE DB 資料提供者。以 OleDb 為字首的類別群。支援本機事務和分散式事務。 對於分散式事務，預設情況下，用於 OLE DB 的 .NET Framework 資料提供程式會自動登記在事務中，並自動從 Windows 2000 組件服務取得事務詳細資訊。
* Oracle：由 System.Data.OracleClient 提供支援，但使用者的電腦必須安裝 Oracle Client 8.1.7 或更新版本才行（.NET Framework 1.1 開始支援）。以 Oracle 為字首的類別群。必須同時參照 System.Data 和 System.Data.OracleClient。
* ODBC：補 OLE DB Provider for ODBC 的支援，由 System.Data.Odbc 提供支援（.NET Framework 1.1 開始支援）。以 Odbc 為字首的類別群
* EntityClient 實體資料模型 (EDM) 應用程式的資料存取。使用 System.Data.EntityClient 命名空間。

其他廠商亦為不同的資料庫提供資料來源：

* DataDirect Technologies（頁面存檔備份，存於網際網路檔案館）發行 100% 列管提供源，支援主流企業資料庫（Oracle, Sybase, DB2, SQL Server, Progress RDBMS）
* OpenLink Software 給大量的客戶指定資料庫發行提供源，包括到其他資料存取機構的橋接提供源，並可以在視窗下微軟自己或者 Mono 的 CLR 實做下支援。
* MySQL（頁面存檔備份，存於網際網路檔案館）為本身的 MySQL Database Server 提供了 ADO.NET 的原生資料提供者。
* Oracle 自行開發的.NET Data Provider。

對每種 Data Provider，ADO.NET 要實現下述物件結構：

* Connection 物件提供與資料來源的連接。
* Command 物件使您能夠存取用於返回資料、修改資料、執行儲存程序以及傳送或檢索參數資訊的資料庫命令。
* DataReader 物件從資料來源中提供快速的，唯讀的資料流。
* DataAdapter 物件提供連接 DataSet 物件和資料來源的橋梁。DataAdapter 使用 Command 物件在資料來源中執行 SQL 命令，以便將資料載入到 DataSet 中，並使對 DataSet 中資料的更改與資料來源保持一致。
* Parameter 物件用於參數化查詢。

例如，對於 SQL Server 資料來源：

* ConnectionStringBuilder：提供一種用於建立和管理由 Connection 物件使用的連接字串的內容的簡單方法。 所有 ConnectionStringBuilder 物件的基礎類別均為 DbConnectionStringBuilder 類。
* CommandBuilder ：自動生成 DataAdapter 的命令內容或從儲存程序中衍生參數資訊，並填充 Command 物件的 Parameters 集合。 所有 CommandBuilder 物件的基礎類別均為 DbCommandBuilder 類。

### 離線資料模型

離線資料模型是微軟為了改良 ADO 在網路應用程式中的缺陷所設計的，同時它也是 COM + 中，IMDB 技術的設計概念的實作品，但它並沒有完整的 IMDB 功能，像是交易處理（transaction processing），但它仍不失為一個能在離線狀態下處理資料的好幫手，它也可以透過連線資料來源物件，支援將離線資料存回資料庫的能力。離線資料模型由下列物件組成：

* **DataSet**，離線型資料模型的核心之一，可將它當成一個離線型的資料庫，它可以內含許多個 DataTable，並且利用關聯與限制方式來設定資料的完整性，它本身也提供了可以和 XML 互動作業的支援。
  * `ReadXml()/WriteXml()`：以 DataSet 的結構讀寫 XML。
  * `ReadXmlSchema()/WriteXmlSchema()`：以 DataSet 的結構讀寫 XML Schema。
  * `GetXml()/GetXmlSchema()`：取得 DataSet 內容的 XML 或 XML Schema。
  * `Merge()`：合併兩個 DataSet。
  * `Load()`：自 `IDataReader` 載入資料到 DataSet。
  * `AcceptChanges()`：將修改過的資料列的修改旗標改為 `Unchanged`。
  * `GetChanges()`：將修改過的資料列以 DataRow 陣列方式傳回。
  * `RejectChanges()`：復原所有資料的修改。
* **DataTable**，離線型資料模型的核心之一，可將它當成一個離線型的資料表，是儲存資料的收納器。
  * `Copy()`：將 DataTable 複製出一個副本，包含結構與資料。
  * `Merge()`：將兩個 DataTable 合併。
  * `Select()`：以指定的特殊查詢語法，傳回符合條件的 DataRow 陣列。
  * `Compute()`：以指定的彙總語法，傳回彙總的結果。
  * `GetErrors()`：傳回有錯誤的 DataRow 陣列。
  * `HasErrors`：判斷 DataTable 中的 DataRow 有沒有含有錯誤的 DataRow。
* **DataRow**，表示表格中的資料列，與資料欄組合成資料儲存的單元。
  * `IsNull()`：判斷指定的欄位是否為 NULL 值。
  * `ItemArray`：將 DataRow 中的資料轉換成陣列。
* **DataColumn**，表示表格中的欄位。
* **DataView**，展示資料的輔助元件，類似於資料庫中的檢視表，並可設定過濾條件與排序條件。
  * `Filter`：設定 DataView 的過濾條件。
  * `Sort`：設定 DataView 的排序條件。
  * `ToTable()`：將套用過濾與排序後的內容轉換為 DataTable 物件。
* **DataRelation**，可在 DataTable 之間設定欄位間的關聯。
* **Constraint**，設定欄位的條件約束，例如 `ForeignKeyConstraint` 為外部鍵限制，而 `UniqueConstraint` 則確保了欄位中的值都是唯一的。

DataSet 和 DataTable 除了資料庫的處理以外，也經常被用來管理應用程式中的資料，並且由於它可以儲存在 XML 中的特性，也讓它可以用來儲存需要儲存的應用程式資訊。DataSet 中可包含 DataRelation 物件，用於建構表之間的約束關係。

## 工廠方法

在.NET Framework 1.x 的時代，ADO.NET 不同的來源有不同的類別搭配（前面已述及），但是若想要在不同的資料來源間搭配，那麼勢必要產生很多的變數來存放不同的資料物件，因此微軟在.NET Framework 2.0 中提供了一個 `System.Data.Common` 命名空間，其中有各種必要物件的共享方法（例如連線是 `DbConnection`，命令是 `DbCommand`，讀取器是 `DbDataReader`，資料配接器是 `DbDataAdapter` 等），以及 `DbProviderFactory` 物件，用來總管資料存取的物件。

`DbProviderFactories` 則是電腦中所有提供者的總管，開發人員可用 `DbProviderFactories.GetFactoryClasses()` 取得各個提供者的 Invariant Name，再於呼叫 `DbProviderFactories.GetFactory()` 時傳入指定提供者的 Invariant Name 即可取得 `DbProviderFactory`，再利用下列方法取得共享物件：

* `DbProviderFactory.CreateConnection()`
* `DbProviderFactory.CreateCommand()`
* `DbProviderFactory.CreateParameter()`
* `DbProviderFactory.CreateDataAdapter()`

## XML 的整合

XML 在 ADO.NET 中扮演了相當重要的地位，DataSet 和 DataTable 都可以轉換成 XML 或和 XML 之間交換資料，在 DataTable 的內部資料的變更記錄，可以被輸出到一個 XML 的格式，用來識別變更的情形，這個格式稱為 DiffGram，而且它可以直接讀入 DataTable 之中（使用 DataTable.ReadXml（）並用 XmlReadMode.DiffGram 當參數）。一個典型的 DiffGram 如下：

DataSet 與 DataTable 也支援直接讀入 XML Schema 建立結構的能力，以及自行依 XML 的內容推斷（inference）其結構的能力，下列程式碼為由 XML 推斷結構的程式：

DataSet 和 DataTable 可以使用 XmlDataDocument 類別和 XML DOM 整合在一起，XmlDataDocument 的角色就像一個橋接介面，並且作為 DataSet 和 DataTable 可使用 XPath 與 XML DOM 方式存取的方法。下列程式碼即為使用 XmlDataDocument 和資料庫中資料轉換為 XSLT 輸出的範例：

在.NET Framework 中，DataSet 被分為兩類，一種是不會強制使用特別型態的 DataSet，稱為 Untyped DataSet，使用上較方便，但沒有強制的型別限制，另一種則是 Typed DataSet，會強制型別，並且是由自訂的 XML Schema 所產生，Untyped DataSet 則沒有 XML Schema，由建立時的結構來決定，Typed DataSet 可以用 Visual Studio，或者是 SDK 工具中的 xsd.exe 來產生。

產生出來的 Typed DataSet 會自動將欄位設定成屬性，讓開發人員的存取更方便（這個功能在 TableAdapter 相當常見）。

## 指令產生器

ADO.NET 中有專門用來產生資料處理指令的指令產生器（Command Builder），它可以利用開發人員所指定的 `SELECT` 指令，自動產生對應的 `INSERT`、`UPDATE` 與 `DELETE` 指令，但一開始它並不會自動產生，而是要靠呼叫方法來取得：

* `DbCommandBuilder.GetInsertCommand()`
* `DbCommandBuilder.GetUpdateCommand()`
* `DbCommandBuilder.GetDeleteCommand()`

最常使用到的地方是和 DataAdapter 並用時，但它要求傳入的 `SELECT` 語句所選擇的列集合中必須要有主鍵或者唯一鍵，否則無法產生，同時自動產生的指令因為判斷條件很多，對效能可能會有些影響。

## Visual Studio 的支援

ADO.NET 和 Visual Studio 開發工具幾乎已經是無縫的整合了，開發人員可以利用 Visual Studio 來建立強型別（strong-typed）的 DataSet，到了 Visual Studio 2005 時更能夠在 Windows Forms 應用程式中使用 TableAdapter（Typed DataSet 和 DataAdapter 整合的產物）來開發應用程式（不會再看到 DataAdapter，但使用上差不多）。Visual Studio 在建立 Typed DataSet 時有提供視覺化介面的支援，以及資料庫組態精靈（Database Configuration Wizard）來讓開發人員以簡單的設定方式來建立 DataSet，部份開發人員也將 TableAdapter 和 ASP.NET 應用程式的 ObjectDataSource 控制項並用，亦得到不錯的效果。

在.NET Framework 3.5 中，微軟特別為了 DataSet 和 DataTable 建立了 LINQ Provider（稱為 LINQ to DataSet 或 LINQ to ADO.NET），讓 LINQ 可以在 DataSet 或 DataTable 上使用，可以讓原本在 DataSet 上的投資（程式碼）得以繼續使用並享有 LINQ 的便利性。

## ADO.NET 和 ADO 的差異

對於 ADO 的開發人員來說，最明顯的變化在於以往 ADO 中的 Recordset 消失了，並且明確的分開為連線型的 DataReader 以及離線型的 DataSet 與 DataTable，並且發展支援離線型資料來源的瀏覽工具 DataView，這樣的改變，讓習慣使用 ADO 的 VB/ASP 開發人員會有某種程度的不習慣，同時讓 ADO.NET 的學習會較 ADO 有較些許的複雜性，因此有部份新入門或是 VB 6.0/ASP 開發人員會在學習.NET Framework 或是使用 VB.NET 開發應用程式時，在.NET Framework 中使用 ADO 來連接資料來源。但在.NET Framework 應用程式使用 ADO 的話，.NET Framework 會因為要多一層 ADO 的 COM 與 VB.NET 使用的.NET 之間的資料轉換，會讓應用程式效能有少部份的損耗。

下述範例，用 ADO 讀取一個資料庫中的表格，然後轉為 ADO.Net 的資料，最後繫結到 DataGridView 控制項中去顯示：

ADO 的 RecordSet，相當於在客戶機或伺服器的一個邏輯上的資料表格，使用者可以通過 cursor 來定位 / 讀寫當前行，但不能重新對這些行重新排序，也不能對客戶機上的多個 RecordSet 資料執行跨表的連接（join）查詢。ADO.Net 針對上述缺點，在客戶機上實現了 DataTable 與 DataSet，這是客戶機記憶體中的一套（多個）資料表格，可以對其執行各種單表或多表的查詢、修改、刪除操作，既可以使用 SQL 語句，也可以使用 LINQ 子語言。

ADO 與 ADO.Net 的共同點，在其內部都是使用 SQL 語句來對後台資料庫操作。

內在實現上，ADO 是基於 COM 技術，因此變體類型（Variant）無處不在，這是一種通用、萬能類型；而 ADO.Net 是強型別的，並很好地支援資料庫元資料與 XML 功能。

## ADO.NET 的進化

隨著網路應用程式的進化，ADO.NET 也隨之做了許多的改變，但不變的是，ADO.NET 的基礎提供了強固的發展支援，這些進化的技術都是植基於 ADO.NET 的核心元件而來。

長久以來，程式設計師和資料庫總是保持著一種微妙的關係，在商用應用程式中，資料庫一定是不可或缺的元件，這讓程式設計師一定要為了連接與存取資料庫而去學習 SQL 指令，因此在資訊業中有很多人都在研究如何將程式設計模型和資料庫整合在一起，物件關聯對應（Object-Relational Mapping）的技術就是由此而生，像 Hibernate 或 NHibernate 都是這個技術下的產物，而微軟雖然有了 ADO.NET 這個資料存取的利器，但卻沒有像 NHibernate 這樣的物件對應工具，因此微軟在.NET Framework 2.0 發展時期，就提出了一個 ObjectSpace 的概念，ObjectSpace 可以讓應用程式可以用完全物件化的方法連接與存取資料庫，其技術概念與 NHibernate 相當類似，然而 ObjectSpace 專案相當大，在.NET Framework 2.0 完成時仍無法全部完成，因此微軟將 ObjectSpace 納入下一版本的.NET Framework 中，並且再加上一個設計的工具（Designer），構成了現在的 ADO.NET Entity Framework。

Entity Framework 利用了抽象化資料結構的方式，將每個資料庫物件都轉換成應用程式物件（entity），而資料欄位都轉換為屬性（property），關聯則轉換為結合屬性（association），讓資料庫的 E/R 模型完全的轉成物件模型，如此讓程式設計師能用最熟悉的程式語言來呼叫存取。而在抽象化的結構之下，則是高度整合與對應結構的概念層、對應層和儲存層，以及支援 Entity Framework 的資料提供者（provider），讓資料存取的工作得以順利與完整的進行。

以往在發展像是 AJAX 應用程式時，伺服端總是需要設計一個 HTTP 介面埠（end point），通常都會使用 Web Service 來實作，但是隨著 Mashup 應用程式的成長，若每次都要為一份（或一組）資料撰寫 Web Service 或 HTTP end point 的話，對開發人員也是不小的負擔，而且 Web Service 只支援 XML/SOAP 的資料格式，無法相容於 Mashup 應用程式常用的 JSON 資料格式，微軟也發現未來的 Silverlight 應用程式也是會面臨到相同問題。

當時剛好微軟的 ADO.NET Entity Framework 也正在開發中，它的 EDM 能力剛好可以提供給 WCF 資料存取的能力，因此微軟特別以 ADO.NET Entity Framework 為基礎，開發一個專門提供 HTTP 端點資料服務的資料供應層，即為 WCF Data Services。

## 注釋

## 參考文獻

## 參見

* ActiveX Data Objects（ADO）
* Open Database Connectivity（ODBC）
* OLE DB