剖析 .NET 托管提供程序
剖析 .NET 托管提供程序
發布日期: 4/1/2004 | 更新日期: 4/1/2004Dino Esposito
Wintellect
2001 年 10 月 9 日
與成熟的 OLE DB 提供程序相比,Microsoft .NET 托管提供程序有許多優點。首先,它實現了簡化的數據訪問結構,這種結構常常可提高性能,同時又不影響功能方面的能力。此外,.NET 托管提供程序通過方法和屬性直接向使用者提供特定于提供程序的行為。它使用的接口集合也比 OLE DB 提供程序要少的多。最后但并非最不重要的是,.NET 托管提供程序工作在公共語言運行庫 (CLR) 的邊界內,無需 COM 交互。對于 SQL Server 7.0 和 SQL Server 2000 而言,托管提供程序直接掛接到線路級,獲得了顯著的性能優勢。
.NET 數據提供程序提供的功能可分為以下兩類:
| ? | 通過 IDataAdapter 接口的方法的實現,支持 DataSet 類 |
| ? | 支持連接的數據訪問,包括表示連接、命令和參數的類 |
數據提供程序最簡單的功能是,在讀取和寫入時只通過數據集與調用方交互。另一種情況是,您可以控制連接、事務處理以及執行直接命令,而不必考慮 SQL 語言。下圖顯示了 .NET 中兩個標準托管提供程序(OLE DB 提供程序和用于 SQL Server 的提供程序)的類層次結構。
圖 1. 托管提供程序連接、執行命令,并以特定于數據源的方式獲取數據。
包裝連接、命令和讀取器的對象是特定于提供程序的,并且可能產生一組稍有不同的屬性和方法。任何內部實現都嚴格地具備了數據庫識別能力。在此架構范圍之外的唯一的類是數據集。該類是所有提供程序所共有的,它作為斷開連接的數據的一般容器。數據集類屬于一種名為 System.Data 的超命名空間。特定于一個數據提供程序的類屬于特定命名空間。例如,System.Data.SqlClient 和 System.Data.OleDb 屬于一個特定命名空間。上圖顯示的架構雖然并不過分簡單,卻是一種相當基本的架構。這是一種簡化的架構,因為它不包括所涉及的所有類和接口。下圖更接近真實情況。
圖 2. 托管提供程序涉及的類
下表顯示了構成 .NET 提供程序的接口的列表。
IDbConnection | 表示一個與數據源建立的唯一會話 |
IDbTransaction | 表示一個本地非分布式事務處理 |
IDbCommand | 表示一個在連接到數據源時執行的命令 |
IDataParameter | 允許將參數實現為命令 |
IDataReader | 讀取在執行命令后創建的只向前只讀數據流 |
IDataAdapter | 填充數據集并將數據集中的更改解析回數據源 |
IDbDataAdapter | 提供對關系數據庫執行典型操作(插入、更新、選擇、刪除)的方法 |
在所有接口中,只有 IDataAdapter 是強制性的,必須存在于每個托管提供程序中。如果您不打算實現其中的一個接口或給定接口的方法之一,無論如何都要公開該接口,但這樣會引發 NotSupportedException 異常。只要有可能,應避免提供方法和接口的無操作實現,因為這種做法可能導致數據損壞,對于事務處理的提交/回滾尤其是這樣。例如,提供程序無需支持嵌套事務處理,即使 IDbTransaction 接口的設計考慮到這種情況。
在進一步解釋每個類在 .NET 提供程序的整體運行中所扮演的角色之前,我們先大概了解一下建議托管提供程序使用的命名約定。如果您打算自己編寫提供程序,這方面的知識就非常有用。第一條準則是考慮命名空間。確保給自己的托管提供程序分配一個唯一的命名空間。接下來,使用在任何內部代碼和客戶端代碼中標識提供程序的別名作為類的前綴。例如,使用 OdbcConnection、OdbcCommand、OdbcDataReader 等諸如此類的類名。在本文提到的情況中,別名為 Odbc。此外,盡量使用不同的文件來編譯不同的功能。
實現連接
提供程序連接類從 IDbConnection 繼承,并且必須公開 ConnectionString、“狀態”、“數據庫” 和 ConnectionTimeout 屬性。強制方法包括 “打開”、“關閉”、BeginTransaction、ChangeDatabase 和 CreateCommand。您不一定必須實現事務處理。下面的代碼片斷是一個用于實現連接的代碼示例。
namespace DotNetMyDataProvider {public class MyConnection : IDbConnection{private ConnectionState m_state;private String m_sConnString;public MyConnection () { m_state = ConnectionState.Closed; m_sConnString = "";}public MyConnection (String connString) { m_state = ConnectionState.Closed; m_sConnString = connString;}public IDbTransaction BeginTransaction() {throw new NotSupportedException();}public IDbTransaction BeginTransaction(IsolationLevel level) {throw new NotSupportedException();}} }您應該至少提供兩個構造函數,其中一個是不帶任何參數的默認構造函數。另一個建議的構造函數只接受連接字符串。通過 ConnectionString 屬性返回連接字符串時,請確保返回的字符串始終是用戶設置的那個字符串。唯一的異常可能是您也許希望刪除的任何安全敏感信息導致的。
您在連接字符串中識別和支持的項取決于您本身,但無論何時,只要它有意義,就應該使用標準名稱。“打開” 方法負責打開與數據源進行通信的物理信道。此操作不應該在調用 “打開” 方法之前進行。如果打開連接成為一個耗費內存的操作,可以考慮使用某種連接池。最后,如果期望提供程序在分布式事務處理中提供自動登記,登記應該在執行 “打開” 的期間進行。
使 ADO.NET 連接區別于其他連接(例如,ADO 連接)的一個要點是,您需要確保在可以執行任何命令之前創建和打開連接。客戶端必須顯式打開和關閉連接,沒有任何方法會為客戶端隱式打開和關閉連接。這種做法導致了某種程度的安全檢查集中化。采用這種方法時,只有在獲得連接后才執行檢查,但提供程序中正巧涉及連接對象的所有其他類會同時受益。
方法 “關閉” 用于關閉連接。通常,“關閉” 應該只斷開連接,并將對象返回池(如果有池)。您還可以實現 “處置” 方法來自定義對象的析構。連接的狀態通過 ConnectionState 枚舉數據類型來標識。當客戶端使用連接時,您應該確保連接的內部狀態與 “狀態” 屬性的內容相匹配。例如,當您提取數據時,將連接的 “狀態” 屬性設置為 ConnectionState.Fetching。
返回頁首ODBC 連接
現在我們來看一個具體的 .NET 托管提供程序如何在實踐中應用上述原則。為此,我們以一個最新托管提供程序為例,盡管它只出現在早期的測試版本中。這就是 ODBC 數據源的 .NET 提供程序。您可能已經注意到,OLE DB 的 .NET 提供程序在連接字符串中不支持 DSN 標記。這樣的名稱需要自動選擇 MSDASQL 提供程序并搜索 ODBC 源。下面的代碼顯示了 ODBC.NET 如何聲明其連接類:
public sealed class OdbcConnection : Component, ICloneable, IdbConnectionOdbcConnection 對象利用了 ODBC 的典型資源,例如,環境句柄和連接句柄。這些對象使用類私有成員在內部存儲。該類同時用于 “關閉” 和 “處置”。通常,您可以使用其中的任何一種方法來關閉連接,但必須在連接對象超出作用范圍之前使用。否則,內存的釋放(也就是 ODBC 句柄)將留給垃圾回收器,而您是無法控制其執行時間的。在連接池方面,OdbcConnection 類依靠 ODBC 驅動程序管理器的服務。
為了使用 ODBC.NET 提供程序(目前提供 Beta 1),您應該將 System.Data.Odbc 包括在內。這樣可以確保提供程序使用用于 JET、SQL Server 和 Oracle 的驅動程序。
返回頁首實現命令
命令對象為某些操作創建請求并將請求傳遞到數據源。如果返回結果,命令對象負責將結果作為定制的 DataReader 對象、標量值和/或通過輸出參數進行打包和返回。根據數據提供程序的特性,您可以安排結果采用其他格式。例如,如果命令文本包括 FOR XML 子句,用于 SQL Server 的托管提供程序允許以 XML 格式獲得結果。
類必須至少支持 CommandText 屬性,并且至少支持文本命令類型。命令的分析和執行取決于提供程序。這是使提供程序有可能接受作為命令的任何文本或信息的一個關鍵要素。支持命令行為不是強制的,如果需要,您可以支持更多完全自定義的行為。
在命令中,連接可以與一個事務處理相關聯。如果重置連接 — 并且用戶應該能夠在任何時候更改連接 — 那么首先禁用相應的事務處理對象。如果支持事務處理,則設置命令對象的 “事務” 屬性時,應考慮額外的步驟,以確保您使用的事務處理已經與命令使用的連接相關聯。
命令對象使用兩個表示參數的類。一個類是 xxxParameterCollection,通過 Parameters 屬性訪問;另一個類是 xxxParameter,它表示集合中存儲的單個命令參數。當然,xxx 代表特定于提供程序的別名。對于 ODBC.NET,這兩個類就是 OdbcParameterCollection 和 OdbcParameter。
您可以對參數類使用 “新的” 運算符或通過命令對象的 CreateParameter 方法來創建特定于提供程序的命令參數。新創建的參數通過 Parameters 集合的方法填充和添加到命令的集合中。然后,用于命令執行的模塊負責通過參數收集數據集。使用命名的參數(就像 SQL Server 提供程序那樣)還是使用 ? 占位符(類似于 OLE DB 提供程序)則取決于您。
必須有一個有效的并且已打開的連接才能執行命令。使用任何標準類型的命令(例如,ExecuteNonQuery、ExecuteReader 和 ExecuteScalar)來執行命令。另外,還要考慮為 “取消” 和 Prepare 方法提供實現。
返回頁首ODBC 命令
OdbcCommand 類不支持通過 SQL 命令和存儲過程傳遞命名的參數。您必須改用 ? 占位符。至少在這個早期版本中,它既不支持 “取消”,也不支持 Prepare。正如您預期的那樣,ODBC .NET 提供程序要求 Parameters 集合中命令參數的數目與命令文本中找到的占位符的數目要匹配。否則,就會引發異常。下面的代碼行顯示了如何將一個新的參數添加到 ODBC 命令,同時為該參數賦值。
cmd.Parameters.Add("@CustID", OdbcType.Integer).Value = 99注意,提供程序定義了自己的一組類型。枚舉 OdbcType 包括 ODBC 的低級 API 確實可以識別的所有類型(并且只包括這些類型)。原來的 ODBC 類型非常相似,例如,SQL_BINARY、SQL_BIGINT 或 SQL_CHAR 和 .NET 類型。尤其是,ODBC 類型 SQL_CHAR 映射到 .NET String 類型。
返回頁首實現數據讀取器
數據讀取器是提供程序為了使客戶端以只向前方式讀取數據而創建的一種已連接無緩存緩沖區。讀取器的實際實現取決于提供程序的編寫器。不過,應該注意遵循幾條準則。
首先,DataReader 對象被返回用戶時,應該始終處于打開狀態并位于第一個記錄之前。另外,用戶不能直接創建 DataReader 對象。必須由命令對象創建和返回讀取器。為此,您應該將構造函數標記為內部。采用 C# 時應使用關鍵字 internal
internal MyDataReader(object resultset) {...}采用 Visual Basic? .NET 時使用關鍵字 friend
Friend Sub New(ByRef resultset As object)MyBase.New... End Sub數據讀取器必須至少有兩個構造函數,一個接受查詢的結果集,另一個接受用于執行命令的連接對象。只有當命令必須以 CommandBehavior.CloseConnection 的形式執行時才需要連接。在這種情況下,當 DataReader 對象關閉時,連接必須自動關閉。在內部,結果集可以采用滿足您的需要的任何形式。例如,可以將結果集實現為數組或字典。
數據讀取器應該正確管理屬性 RecordsAffected。該屬性只應用于包括插入、更新或刪除命令的批處理語句。它通常不應用于查詢命令。當讀取器關閉時,您可能希望禁止某些操作和更改讀取器的內部狀態,以清理內部資源,如用于存儲數據的數組。
數據讀取器的 Read 方法始終前進到一個新的有效行(如果有任何新的有效行)。更重要的是,它應該只是使內部數據指針指向前,但不進行任何讀取。實際的讀取由其他特定于讀取器的方法來完成,例如,GetString 和 GetValues。最后,NextResult 移到下一個結果集。基本上,它是將一個新的內部結構復制到 GetValues 等方法從其中進行讀取的公用知識庫。
返回頁首ODBC 數據讀取器
像所有讀取器類一樣,OdbcDataReader 是密封的,并且是不可繼承的。訪問列值的類的方法自動強制它們返回的數據的類型采用最初從該列檢索到的數據的類型。從某個給定列第一次讀取一個單元格時使用的類型將用于同一列的所有其他單元格。也就是說,您不能從同一列中接連將數據作為字符串和長整型讀取。
當命令對象的 CommandType 屬性設置為 StoredProcedure 時,CommandText 屬性必須使用過程的標準 ODBC 轉義序列進行設置。與其他提供程序不同的是,對于 ODBC.NET 提供程序,僅僅使用過程的簡單名稱是不夠的。下面的模式說明了通過 ODBC 驅動程序調用存儲過程的典型方法。
{ call storedproc_name(?, ..., ?) }字符串必須用 {...} 括起來,并且將關鍵字調用放在實際名稱和參數列表之前。
返回頁首實現數據適配器
成熟的 .NET 數據提供程序提供繼承了 IDbDataAdapter 和 DbDataAdapter 的數據適配器類。類 DbDataAdapter 實現了用于關系數據庫的數據適配器。不過,在其他情況下,您需要的是實現 IDataAdapter 接口并將某些斷開連接的數據復制到內存中的可編程緩沖區(如數據集)的類。實際上,在大多數情況下,實現 IDataAdapter 接口的 Fill 方法對于通過數據集對象返回斷開連接的數據已經足夠了。
DataAdapter 對象的典型構造函數是:
XxxDataAdapter(SqlCommand selectCommand) XxxDataAdapter(String selectCommandText, String selectConnectionString) XxxDataAdapter(String selectCommandText, SqlConnection selectConnection)從 DbDataAdapter 繼承的類必須實現所有成員,如果使用特定于提供程序的功能,還必須定義額外的成員。最后,必須實現下面的方法:
Fill(DataSet ds) FillSchema(DataSet ds, SchemaType st) Update(DataSet ds) GetFillParameters()需要的屬性包括:
| ? | TableMappings(默認為空集合) |
| ? | MissingSchemaAction(默認為 Add) |
| ? | MissingMappingAction(默認為 Passthrough) |
您可以根據需要提供 “填充” 方法的任意數目的實現。
表映射控制著源表(即數據庫表)映射到父數據集中的數據表對象時采用的方式。映射考慮表名及列名和屬性。而架構映射則考慮開始將新數據添加到現有數據集時處理列和表的方式。缺少的映射屬性的默認值要求適配器創建像源表一樣的內存中的表。缺少的架構屬性的默認值處理實際填充數據表對象時可能產生的問題。如果目標數據集中缺少任何映射的元素(表和列),則 MissingSchemaAction 的值會建議采取什么措施。在某種意義上,這兩個 MissingXXX 屬性是一種異常處理程序。值 Add 強制適配器添加經證明缺少的任何表或列。除非為屬性分配另一個 (AddWithKey) 值,否則,不添加任何關鍵信息。
當應用程序調用 “更新” 方法時,該類檢查數據集中每一行的 RowState 屬性,然后執行所要求的 INSERT、UPDATE 或 DELETE 語句。如果該類不提供 UpdateCommand、InsertCommand 或 DeleteCommand 屬性,但實現 IDbDataAdapter,那么您可以嘗試即時生成命令或產生一個異常。您還可以提供自定義的命令生成器類來幫助進行命令生成。
ODBC 提供程序提供 OdbcCommandBuilder 類作為自動生成單表命令的方法。OLE DB 提供程序和 SQL Server 提供程序提供了相似的類。如果您需要更新交叉引用的表,那么您可能要使用存儲過程或即席 SQL 批處理。在這種情況下,只需覆蓋 InsertCommand、UpdateCommand 和 DeleteCommand 屬性,使它們運行您指定的命令對象。
返回頁首小結
.NET 數據提供程序提供的功能可分為兩個主要類別:
| ? | 支持斷開連接的數據集對象 |
| ? | 支持連接的數據訪問,包括連接、事務處理、命令和參數 |
.NET 中的數據提供程序通過 IDataAdapter 接口的實現來支持數據集對象。還可以通過實現 IDataParameter 接口支持參數化的查詢。如果您無法負擔斷開連接的數據,可通過 IDataReader 接口使用 .NET 數據讀取器。
返回頁首對話欄:為多個結果集命名
Visual Studio? .NET 有一個很好的功能,即,您可以為數據適配器即將生成的所有表分配一個一致的名稱。在任何 .NET 應用程序中配置了數據適配器對象之后,該對話框顯示要創建的表的標準名稱:Table、Table1、Table2 等等。對于其中的每個名稱,您都可以在后來一次性指定為更形象的名稱。我們是否可以通過某種方法以編程方式達到此目的呢?
Visual Studio .NET 是一個出色的產品,但它的使用需要一些技巧。上述問題的回答是 — 我們的確可以通過某種方法用編程方式來實現該目的,順便提一句,Visual Studio 在后臺也使用了同樣的代碼。
DataAdapter 對象有一個名為 TableMappings 的集合,其元素是 DataTableMapping 類型的對象。概括地說,表映射是什么?表映射是源表和適配器即將創建的相應數據表對象之間設置的動態關聯。如果尚未設置任何映射,則適配器使用與源表相同的結構創建數據表對象,名稱除外。名稱是通過調用 “填充” 方法或字 “表” 指定的字符串。從多個結果集產生的額外的表在第一個表之后命名。因此,默認情況下,它們的名稱分別是 Table1、Table2 等等。可是,如果數據適配器的填充如下面的代碼所示,則額外的表分別命名為 Employees1、Employees2 等等。
myDataAdapter.Fill(myDataSet, "Employees");在配置數據適配器時,Visual Studio 所做的是,為您以可視方式創建的每一個關聯都創建一個 DataTableMapping 對象。下面的代碼行顯示了如何以編程方式為如上所述填充的數據集的前兩個表分配有意義的具體名稱。
myDataAdapter.TableMappings.Add("Employees", "FirstTable"); myDataAdapter.TableMappings.Add("Employees1", "SecondTable");第三個表(如果有)可以通過 Table2 訪問。
雖然這是命名從多個結果集產生的數據表對象的最佳方法,但您也可以使用以下同樣有效的代碼:
myDataAdapter.Fill(myDataSet, "Employees"); myDataSet.Tables["Employees1"].TableName = "SecondTable";您還可以通過索引訪問該表:
myDataSet.Tables[1].TableName = "SecondTable";Dino Esposito 供職于 Wintellect,他承擔了 ADO.NET 和 ASP.NET 方面的培訓和咨詢工作。他是 VB-2-The-Max 的創始人之一,并向 MSDN Magazine 的 Cutting Edge 專欄投稿。如果希望與 Dino 聯系,可發送電子郵件至 dinoe@wintellect.com。
轉載于:https://www.cnblogs.com/duadu/archive/2005/04/21/6167270.html
總結
以上是生活随笔為你收集整理的剖析 .NET 托管提供程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android R制作OTA包时报错
- 下一篇: sftp java测试连通性_如何使用知