下面的代碼顯示了使用數據適配器從數據源收集數據的典型方式。
SqlDataAdapter da; DataSet ds; da = new SqlDataAdapter(m_selectCommand, m_connectionString); ds = new DataSet(); da.Fill(ds);
必須承認,此代碼并沒有什么高深復雜的地方,我猜想您已經對它非常熟悉,并且可能不止一次地成功運行過。但是,您是否了解在上述代碼的背后究竟發生了什么?信不信由你,在代碼中運行著一個小有名氣的對象,它的性質和行為對最后的結果有很大的影響。 當您運行上述代碼時,對于 “選擇” 語句的執行可能生成的每個結果集,都有一個新的 “數據表” 對象添加到數據集(最初是空的)中。如果您將一個非空的數據集傳遞到 “填充” 方法,只要發現數據表的名稱與之相匹配,結果集和現有的 “數據表” 對象的內容就會進行合并。同樣,在開始將數據行從結果集復制到一個給定的數據表時,匹配列的內容將進行合并。相反,如果未發現列名相匹配,則會新建一個 DataColumn 對象(使用默認設置),并且會添加到內存中的數據表中。 我們要提出的問題是,適配器如何將結果集的內容映射到數據集構成項中?數據適配器怎樣才能知道哪些表和列的名稱相匹配呢?數據適配器的 TableMappings 屬性正是這個幕后的對象,它決定了結果集中的表如何映射到數據集中的對象。
映射機制
一旦 “選擇” 命令終止,且數據適配器返回一個或多個結果集之后,映射機制就開始起作用了。適配器獲取對內部數據讀取器對象的引用,然后開始處理提取的數據。默認情況下,數據讀取器定位在第一個結果集上。下面的偽代碼描述了這一過程:
int Fill(DataSet ds) { // Execute the SELECT command and gets a reader IDataReader dr = SelectCommand.ExecuteReader(); // Map the first result set to the DataSet and return the table bool bMoreToRead, bMoreResults; DataTable dt = MapCurrentResultSet(ds); // Copy rows from the result set to the specified DataTable while(true) { // Move to the next data row bMoreToRead = dr.Read(); if (!bMoreToRead) { // No more rows in this result set. More result sets? bMoreResults = dr.NextResult() if (!bMoreResults) break; else // Map this new result set and continue the loop dt = MapCurrentResultSet(ds); } else AddRowToDataTable(dt) } }
“填充” 方法將第一個結果集映射到給定數據集的 “數據表” 對象。接下來,它對結果集進行從頭到尾的循環,并將數據行添加到該數據表。到達結果集的結尾時,該方法就會尋找新的結果集,然后重復該操作。
將結果集映射到數據集的過程包括兩個階段:
• |
表映射 |
• |
列映射 |
在表映射過程中,數據適配器必須為將要包含所處理結果集中的行的數據表找到一個名稱。
每個結果集都有一個默認名稱,您可以隨意更改該名稱。結果集的默認名稱取決于已用于調用的 “填充” 方法的簽名。例如,我們來看下面的兩個重載:
Fill(ds); Fill(ds, "MyTable");
在前一種情況下,第一個結果集的名稱默認為 表。其他結果集的名稱分別為 Table1、Table2 等。在后一種情況下,第一個結果集名稱為 MyTable,其他結果集也隨之命名為 MyTable1、MyTable2 等。
適配器檢查其 TableMappings 集合,看是否存在匹配結果集默認名稱的條目。如果發現匹配的條目,適配器則會嘗試在數據集中找到具有映射中所指定名稱的 DataTable 對象。如果不存在這樣的 DataTable 對象,則創建該對象,然后進行填充。如果數據集中存在這樣的數據表,其內容則會與結果集的內容進行合并。

圖 1. 將結果集映射到 DataSet 對象
在圖 1 中,我假定查詢至少會產生三個結果集。TableMappings 集合包含三個默認名稱和相應的映射名稱。如果 “選擇” 命令創建了采用默認名稱 表 的結果集,其內容則會合并到一個名稱為 “雇員” 的新的或現有的 “數據表” 對象。如何從代碼的角度來控制這個過程呢?請看下面的代碼片斷:
SqlDataAdapter da = new SqlDataAdapter(...); DataSet ds = new DataSet(); DataTableMapping dtm1, dtm2, dtm3; dtm1 = da.TableMappings.Add("Table", "Employees"); dtm2 = da.TableMappings.Add("Table1", "Products"); dtm3 = da.TableMappings.Add("Table2", "Orders"); da.Fill(ds);
當然,您映射到自己名稱上的默認名稱必須與調用 “填充” 方法生成的默認名稱一致。也就是說,如果您將最后一行更改為 da.Fill(ds, "MyTable");,代碼則無法成功運行,因為默認名稱現在變成了 MyTable、MyTable1 和 MyTable2,而對于這些名稱,前面的 TableMappings 集合中并沒有相匹配的條目。
您可以有任意數量的表映射,不一定與預期的結果集數量相關。例如,您可以只映射命令返回的第二個結果集 Table1。這種情況下,目標結果集包含三個表,分別名為 Table、Products 和 Table2。
DataTableMapping 對象描述了結果集與數據集中的 “數據表” 對象之間的映射關系。SourceTable 屬性返回默認的結果集名稱,而 DataSetTable 則包含映射名稱。
如果您使用 Visual Studio® .NET,則可以通過運行 Data Adapter Configuration Wizard,用一種可視的方式配置表映射。
列映射
如果就這樣完成表映射,也沒有什么特別之處。實際上,如果您的目的是為數據集表起一個好記的名稱,則可以使用下面的代碼:
SqlDataAdapter da = new SqlDataAdapter(...); DataSet ds = new DataSet(); da.Fill(ds); ds.Tables["Table"].TableName = "Employees"; ds.Tables["Table1"].TableName = "Products";
最后的效果完全一樣。不過,這種映射機制的另一方面卻十分有趣,這就是列映射。下圖擴展了前面的圖,包括詳細的列映射過程。

圖 2. 表映射和列映射
DataTableMapping 對象有一個名為 ColumnMappings 的屬性,它其實就是一個 DataColumnMapping 對象集合。列映射表示結果集中的列的名稱與 “數據表” 對象中相應列名稱之間的映射;旧,DataColumnMapping 對象的最終目標是,使您在數據表中使用的列名能夠區別于數據源中的列名。
SqlDataAdapter da = new SqlDataAdapter(...); DataSet ds = new DataSet(); DataTableMapping dtm1; dtm1 = da.TableMappings.Add("Table", "Employees"); dtm1.ColumnMappings.Add("employeeid", "ID"); dtm1.ColumnMappings.Add("firstname", "Name"); dtm1.ColumnMappings.Add("lastname", "Surname"); da.Fill(ds);
在上面的代碼中,我假定提取的結果集包含名為 employeeid、firstname 和 lastname 的列。這些列必須復制到數據集的內存中數據表子集中。默認情況下,目標 DataColumn 的名稱與源列相同。不過,利用列映射機制,您可以更改內存中列的名稱。例如,將列 employeeid 復制到內存時,它被重命名為 ID,并放在名為 Employees 的數據表中。
該列的名稱是您在此級別唯一可以更改的參數。請記住,整個映射是在 “填充” 方法內自動進行的。當 Fill 終止時,源結果集中的每一列已轉換到為 DataColumn 對象中時,您可以加入和應用進一步的更改 - 關系、約束、主鍵、只讀、自動遞增種子和步長、對空值的支持等。
總之,“填充” 方法會完成兩個主要操作。首先,它將源結果集映射到內存中的表。其次,它使用從物理數據源提取的數據來填充表。在完成其中任何一個任務的時候,“填充” 方法都可能產生某些特殊的異常。從概念上來說,異常是需要從代碼角度專門進行解決的反常情況。當適配器沒有找到表映射或列映射時,并且在目標數據集中沒有找到所需的數據表或數據列時,適配器就會引發一種輕量型的異常。
與必須在代碼中解決的真正異常不同的是,這種特殊的適配器異常必須通過從為數不多的一組可行選項中選擇一個操作,用聲明的方式來解決。適配器會引發下面兩種輕量型異常:
• |
缺少映射 |
• |
缺少架構 |
缺少映射操作
當適配器正收集用來填充數據集的數據時,有兩種情況需要缺少映射操作。如果在 TableMappings 中沒有找到默認名稱,或者,如果表的 ColumnMappings 集合中沒有列名,則需要缺少映射操作。您必須自定義適配器的 MissingMappingAction 屬性的行為,以便處理這樣的異常。該屬性的可取值屬于 MissingMappingAction 枚舉類型,如下表所示。
Error |
只要檢測到缺少列或表,就生成 SystemException。 |
Ignore |
忽略未映射的列或表。 |
Passthrough |
默認選項;使用默認名稱添加缺少的表或列。 |
表 1. MissingMappingAction 枚舉
除非您在填充適配器之前明確設置了 MissingMappingAction 屬性,否則它會采用默認值 Passthrough。因此,會使用默認名稱將表或列添加到數據集中。例如,如果尚未為名稱為 Table 的結果集指定表映射,目標數據表則會采用與之相同的名稱。實際上,下面的語句最后會將新的數據表分別添加到名稱為 Table 和 MyTable 的數據集。
da.Fill(ds); da.Fill(ds, "MyTable");
如果將 MissingMappingAction 屬性設置為 Ignore,則只是忽略任何未映射的表或列。此時,不會檢測任何錯誤,但目標數據集中也不會存在有關所涉及結果集的任何內容(或它的一列)。
如果 MissingMappingAction 屬性設置為 Error,那么,適配器就會限制為每次檢測到缺少映射時都引發 SystemException 異常。
一旦適配器完成映射階段,就開始用所選結果集的內容填充目標數據集。如果目標數據集中不存在任何必需的 “數據表” 或 DataColumn 對象,就會觸發另外一個輕量型的異常,此時需要另一個聲明的操作:缺少架構操作。
缺少架構操作
如果數據集不包含其名稱已經在表映射步驟中確定的表,則需要缺少架構操作。同樣,如果數據集表不包含具有預期映射名稱的列,也需要同樣的操作。MissingSchemaAction 是為了在缺少表架構的情況下指示您希望執行操作而設置的屬性。該屬性的可取值屬于 MissingSchemaAction 枚舉類型,如下表所示。
Error |
只要檢測到缺少列或表,就生成 SystemException。 |
Ignore |
忽略未映射的列或表。 |
Add |
默認選項;使用默認名稱添加缺少的表或列。 |
AddWithKey |
添加主鍵和約束。 |
表 2. MissingSchemaAction 枚舉
默認情況下,MissingSchemaAction 屬性設置為 Add。因此,會通過添加任何缺少的構成項 - “數據表” 或 DataColumn 形成完整的數據集。不過,要記住,用這種方式添加的架構信息非常有限。其中只包括名稱和類型。如果您需要額外的信息 - 如主鍵、自動遞增、只讀和空設置 - 請使用 AddWithKey 選項。注意,即使使用 AddWithKey 選項,也并非關于列的所有可用信息都加載到 DataColumn 中。例如,AddWithKey 將某個列標記為自動遞增,但不設置相關的種子和步長屬性。而且,源列的默認值(如果有的話)也不會自動復制。主鍵會被導入,但并非您可能已經設置的任何額外索引都會導入。
另外兩個選項,Ignore 和 Error,其運行方式與處理 MissingMappingAction 屬性的方式完全一樣。
對代碼的影響
雖然我反復提到(輕量型)異常方面的操作,但您在缺少對象的情況下聲明要執行的操作不像真正的異常那么難以處理。另一方面,這并不意味著,您的代碼完全不受此類操作的影響。更具體地說,填充一個已包含所有所需架構信息的數據集是一種代碼優化形式。當代碼的構成方式是使用固定的架構重復填充空數據集的時候,尤其是這樣。這種情況下,使用預加載了架構信息的全局 “數據集” 對象有助于阻止所有那些對恢復操作的請求。
如何使用屬于一組結果集的架構信息填充數據集呢?您猜怎樣,原來,數據適配器對象有一個自定義的方法 - FillSchema。
DataTable[] FillSchema(DataSet ds, SchemaType mappingMode);
FillSchema 首先獲得一個數據集,然后通過與適配器相關聯的 SELECT 命令根據需要向其添加任意數量的表。該方法會在一個數組中返回所創建的各個 “數據表” 對象(只有架構,沒有數據)。映射模式參數可以是 SchemaType 枚舉中定義的一個值。
Mapped |
將任何現有的表映射應用于傳入的架構。用轉換的架構配置數據集。建議選項。 |
Source |
忽略數據適配器上的任何表映射。使用傳入的架構配置數據集,不應用任何轉換。 |
表 3. SchemaType 枚舉
可取的選項從字面上就能看出其含義。Mapped 說明定義了映射時的進行什么操作。而 Source 則有意忽略您可能已經設置的任何映射。數據集中的表會保留其默認名稱,所有列也都會保留它們在源表中具有的原始名稱。
管理用戶配置文件
為了圓滿完成這次關于表映射的討論,我們來看一個您可能要考慮使用表映射的真實情況。假設您必須管理不同的用戶配置文件。每個配置文件都需要您訪問相同的一些表,但返回不同的列集合。您可以用許多方式解決這個問題,但 ADO.NET 表映射機制可能是最好的方法。
其理念是,您始終使用一個查詢 - 針對權限最高的的配置文件的查詢 - 然后映射到只包含特定于當前用戶配置文件的列的結果數據集。下面的一些 Visual Basic® 代碼說明了這個要點:
Dim da As SqlDataAdapter da = New SqlDataAdapter(m_selectCommand, m_connectionString) Dim dtm As DataTableMapping dtm = da.TableMappings.Add(da.DefaultSourceTableName, "Employees") If bUserProfileAdmin Then dtm.ColumnMappings.Add("EmployeeID", "ID") dtm.ColumnMappings.Add("LastName", "Last Name") dtm.ColumnMappings.Add("FirstName", "Name") dtm.ColumnMappings.Add("Title", "Position") dtm.ColumnMappings.Add("HireDate", "Hired") Else dtm.ColumnMappings.Add("LastName", "Last Name") dtm.ColumnMappings.Add("FirstName", "Name") End If Dim ds As DataSet = New DataSet() da.MissingMappingAction = MissingMappingAction.Ignore da.MissingSchemaAction = MissingSchemaAction.Add da.Fill(ds)
在這個簡單的示例中,查詢只返回一個結果集,我決定使用其默認名稱 Table 識別該結果集。注意,為了實現通用性,您應該使用數據適配器對象的 DefaultSourceTableName 屬性,而不是文字的名稱 (Table)。表映射根據用戶的角色定義不同的列映射。如果用戶是管理員,數據集則會包括更多的列。當然,諸如角色和權限這些概念的真正實現完全取決于您。使整個過程按預期工作的關鍵語句是已設置為 Ignore 的 MissingMappingAction 屬性值。結果是,未映射的列只是被忽略。最后,您要牢記的是,對于列名來說,區分大小寫這一點非常重要,列映射名稱的大小寫必須與源列名的大小寫相匹配。
小結
在這篇專欄文章中,我討論了 ADO.NET 中提供的表映射機制。表映射是管理行從數據源到內存中數據集的傳遞過程的規則和行為集合。映射由兩個步驟構成 - 表映射和列映射 - 它只是一個范圍更廣的操作的第一個階段,該操作涉及到由數據適配器對象控制的數據集填充。第二個階段在目標數據集實際被填充后開始。映射階段和填充階段中的任何邏輯異常都可以得到控制,方法是:聲明當表或列沒有顯式綁定到數據集表時或數據集中沒有所需的表或列時要執行哪些操作。
對話欄:區別
@Register 和 @Import 之間有什么區別呢?什么地方最適合由 ASP.NET 應用程序使用的非系統程序集 DLL?
首先,ASP.NET 應用程序是 .NET 應用程序。因而,它們需要鏈接到包含計劃使用的對象的任何程序集。@Register 指令就是用于解決這個問題的。您在頁面注冊的任何程序集稍后將作為引用傳遞到所選擇的編譯器。@Import 指令的作用不是很重要,因為它的功能是簡化編碼。利用 @Import,您可以導入命名空間,而不是程序集。程序集可以包含更多命名空間。例如,程序集 system.data.dll 包含 System.Data、System.Data.OleDb、System.Data.SqlClient 等等。
通過導入命名空間,您可以編寫更簡單的代碼,因而無需指定到給定對象的完整路徑。通過導入 System.Data,您可以通過類 DataSet 而不是 System.Data.DataSet 使用數據集。要使用數據集,您不必使用 @Import 指令,但不能缺少對 system.data.dll 的引用。
具體地說,對于 ASP.NET 應用程序,您無需顯式注冊 Global Assembly Cache (GAC) 中提供的任何程序集。使用 @Register 只是為了引用已向系統 GAC 注冊的自定義程序集。
這些程序集駐留在哪里呢?它們必須放在應用程序的虛擬目錄下的 BIN 目錄中。如果此目錄不存在,則應該創建該目錄。如果您的 ASP.NET 應用程序不使用虛擬目錄,則會從 Web 服務器的根目錄隱式運行。因此,BIN 目錄在 Web 服務器的根目錄下。例如,c:\inetpub\wwwroot\bin。
文章來源于領測軟件測試網 http://www.kjueaiud.com/