|
簡(jiǎn)介
本文為您提供了在 Microsoft ADO.NET 應(yīng)用程序中實(shí)現(xiàn)和獲得最佳性能、可伸縮性以及功能的最佳解決方案;同時(shí)也講述了使用 ADO.NET 中可用對(duì)象的最佳實(shí)踐;并提出一些有助于優(yōu)化 ADO.NET 應(yīng)用程序設(shè)計(jì)的建議。
本文包含:
• | |
• | DataSet 和 DataReader 之間的比較,以及這些對(duì)象中每個(gè)對(duì)象最佳用法的解釋。 |
• | 解釋如何使用 DataSet、Commands 和 Connections。 |
• | 有關(guān)與 XML 集成的信息。 |
• | 通用的技巧和問題。 |
使用 DataReader、DataSet、DataAdapter 和 DataView
ADO.NET 提供以下兩個(gè)對(duì)象,用于檢索關(guān)系數(shù)據(jù)并將其存儲(chǔ)在內(nèi)存中:DataSet 和 DataReader。DataSet 提供一個(gè)內(nèi)存中數(shù)據(jù)的關(guān)系表示形式,一整套包括一些表在內(nèi)的數(shù)據(jù)(這些表包含數(shù)據(jù)、對(duì)數(shù)據(jù)進(jìn)行排序并約束數(shù)據(jù)),以及表之間的關(guān)系。DataReader 提供一個(gè)來(lái)自數(shù)據(jù)庫(kù)的快速、只進(jìn)、只讀數(shù)據(jù)流。
當(dāng)使用 DataSet 時(shí),經(jīng)常會(huì)利用 DataAdapter(也可能是 CommandBuilder)與數(shù)據(jù)源進(jìn)行交互。當(dāng)使用 DataSet 時(shí),也可以利用 DataView 對(duì) DataSet 中的數(shù)據(jù)應(yīng)用排序和篩選。也可以從 DataSet 繼承,創(chuàng)建強(qiáng)類型 DataSet,用于將表、行和列作為強(qiáng)類型對(duì)象屬性公開。
下列主題包括的信息涉及:使用 DataSet 或 DataReader 的最佳時(shí)機(jī)、如何優(yōu)化訪問它們所包含數(shù)據(jù)、以及如何優(yōu)化使用 DataAdapter(包括 CommandBuilder)和 DataView 的技巧。
DataSet 與 DataReader
當(dāng)設(shè)計(jì)應(yīng)用程序時(shí),要考慮應(yīng)用程序所需功能的等級(jí),以確定使用 DataSet 或者是 DataReader。
要通過應(yīng)用程序執(zhí)行以下操作,就要使用 DataSet:
• | 在結(jié)果的多個(gè)離散表之間進(jìn)行導(dǎo)航。 |
• | 操作來(lái)自多個(gè)數(shù)據(jù)源(例如,來(lái)自多個(gè)數(shù)據(jù)庫(kù)、一個(gè) XML 文件和一個(gè)電子表格的混合數(shù)據(jù))的數(shù)據(jù)。 |
• | 在各層之間交換數(shù)據(jù)或使用 XML Web 服務(wù)。與 DataReader 不同的是,DataSet 能傳遞給遠(yuǎn)程客戶端。 |
• | 重用同樣的行組,以便通過緩存獲得性能改善(例如排序、搜索或篩選數(shù)據(jù))。 |
• | 每行執(zhí)行大量處理。對(duì)使用 DataReader 返回的每一行進(jìn)行擴(kuò)展處理會(huì)延長(zhǎng)服務(wù)于 DataReader 的連接的必要時(shí)間,這影響了性能。 |
• | 使用 XML 操作對(duì)數(shù)據(jù)進(jìn)行操作,例如可擴(kuò)展樣式表語(yǔ)言轉(zhuǎn)換(XSLT 轉(zhuǎn)換)或 XPath 查詢。 |
對(duì)于下列情況,要在應(yīng)用程序中使用 DataReader:
• | 不需要緩存數(shù)據(jù)。 |
• | 要處理的結(jié)果集太大,內(nèi)存中放不下。 |
• | 一旦需要以只進(jìn)、只讀方式快速訪問數(shù)據(jù)。 |
注填充 DataSet 時(shí),DataAdapter 使用 DataReader。因此,使用 DataAdapter 取代 DataSet 提升的性能表現(xiàn)為節(jié)省了 DataSet 占用內(nèi)存和填充 DataSet 需要的循環(huán)。一般來(lái)說(shuō),此性能提升只是象征性的,因此,設(shè)計(jì)決策應(yīng)以所需功能為基礎(chǔ)。
使用強(qiáng)類型 DataSet 的好處
DataSet 的另一個(gè)好處是可被繼承以創(chuàng)建一個(gè)強(qiáng)類型 DataSet。強(qiáng)類型 DataSet 的好處包括設(shè)計(jì)時(shí)類型檢查,以及 Microsoft Visual Studio .NET 用于強(qiáng)類型 DataSet 語(yǔ)句結(jié)束所帶來(lái)的好處。修改了 DataSet 的架構(gòu)或關(guān)系結(jié)構(gòu)后,就可以創(chuàng)建一個(gè)強(qiáng)類型 DataSet,把行和列作為對(duì)象的屬性公開,而不是作為集合中的項(xiàng)公開。例如,不公開客戶表中行的姓名列,而公開 Customer 對(duì)象的 Name 屬性。類型化 DataSet 從 DataSet 類派生,因此不會(huì)犧牲 DataSet 的任何功能。也就是說(shuō),類型化 DataSet 仍能遠(yuǎn)程訪問,并作為數(shù)據(jù)綁定控件(例如 DataGrid)的數(shù)據(jù)源提供。如果架構(gòu)事先不可知,仍能受益于通用 DataSet 的功能,但卻不能受益于強(qiáng)類型 DataSet 的附加功能。
處理強(qiáng)類型 DataSet 中的空引用
使用強(qiáng)類型 DataSet 時(shí),可以批注 DataSet 的 XML 架構(gòu)定義語(yǔ)言 (XSD) 架構(gòu),以確保強(qiáng)類型 DataSet 正確處理空引用。nullValue 批注使您可用一個(gè)指定的值 String.Empty 代替 DBNull、保留空引用或引發(fā)異常。選擇哪個(gè)選項(xiàng)取決于應(yīng)用程序的上下文。默認(rèn)情況下,如果遇到空引用,就會(huì)引發(fā)異常。
有關(guān)更多信息,請(qǐng)參閱 Working with a Typed DataSet。
刷新 DataSet 中的數(shù)據(jù)
如果想用服務(wù)器上的更新值刷新 DataSet 中的值,就使用 DataAdapter.Fill。如果有在 DataTable 上定義的主鍵,DataAdapter.Fill 會(huì)根據(jù)主鍵進(jìn)行新行匹配,并且當(dāng)更改到現(xiàn)有行時(shí)應(yīng)用服務(wù)器上的值。即使刷新之前修改了它們,刷新行的 RowState 仍被設(shè)置為 Unchanged。注意,如果沒有為 DataTable 定義主鍵,DataAdapter.Fill 就用可能重復(fù)的主鍵值添加新行。
如果想用來(lái)自服務(wù)器的當(dāng)前值刷新表,并同時(shí)保留對(duì)表中的行所做的任何更改,必須首先用 DataAdapter.Fill 填充表,并填充一個(gè)新的 DataTable,然后用 preserveChanges 值 true 把 DataTableMerge 到 DataSet 中。
在 DataSet 中搜索數(shù)據(jù)
在 DataSet 中查詢與特定條件相匹配的行時(shí),可以利用基于索引的查找提高搜索性能。當(dāng)把 PrimaryKey 值賦給 DataTable 時(shí),會(huì)創(chuàng)建一個(gè)索引。當(dāng)給 DataTable 創(chuàng)建 DataView 時(shí),也會(huì)創(chuàng)建一個(gè)索引。下面是一些利用基于索引進(jìn)行查找的技巧。
• | 如果對(duì)組成 DataTable 的 PrimaryKey的列進(jìn)行查詢,要使用 DataTable.Rows.Find 而不是 DataTable.Select。 |
• | 對(duì)于涉及到非主鍵列的查詢,可以使用 DataView 為數(shù)據(jù)的多個(gè)查詢提高性能。當(dāng)把排序順序應(yīng)用到 DataView 時(shí),就會(huì)建立一個(gè)搜索時(shí)使用的索引。DataView 公開 Find 和 FindRows 方法,以便查詢基礎(chǔ) DataTable 中的數(shù)據(jù)。 |
• | 如果不需要表的排序視圖,仍可以通過為 DataTable 創(chuàng)建 DataView 來(lái)利用基于索引的查找。注意,只有對(duì)數(shù)據(jù)執(zhí)行多個(gè)查詢操作時(shí),這樣才會(huì)帶來(lái)好處。如果只執(zhí)行單一查詢,創(chuàng)建索引所需要的處理就會(huì)降低使用索引所帶來(lái)的性能提升。 |
DataView 構(gòu)造
如果創(chuàng)建了 DataView,并且修改了 Sort、RowFilter 或 RowStateFilter 屬性,DataView 就會(huì)為基礎(chǔ) DataTable 中的數(shù)據(jù)建立索引。創(chuàng)建 DataView 對(duì)象時(shí),要使用 DataView 構(gòu)造函數(shù),它用 Sort、RowFilter 和 RowStateFilter 值作為構(gòu)造函數(shù)參數(shù)(與基礎(chǔ) DataTable 一起)。結(jié)果是創(chuàng)建了一次索引。創(chuàng)建一個(gè)“空”DataView 并隨后設(shè)置 Sort、RowFilter 或 RowStateFilter 屬性,會(huì)導(dǎo)致索引至少創(chuàng)建兩次。
分頁(yè)
ADO.NET 可以顯式控制從數(shù)據(jù)源中返回什么樣的數(shù)據(jù),以及在 DataSet 中本地緩存多少數(shù)據(jù)。對(duì)查詢結(jié)果的分頁(yè)沒有唯一的答案,但下面有一些設(shè)計(jì)應(yīng)用程序時(shí)應(yīng)該考慮的技巧。
• | 避免使用帶有 startRecord 和 maxRecords 值的 DataAdapter.Fill 重載。當(dāng)以這種方式填充 DataSet 時(shí),只有 maxRecords 參數(shù)(從 startRecord 參數(shù)標(biāo)識(shí)的記錄開始)指定的記錄數(shù)量用于填充 DataSet,但無(wú)論如何總是返回完整的查詢。這就會(huì)引起不必要的處理,用于讀取“不需要的”記錄;而且為了返回附加記錄,會(huì)耗盡不必要的服務(wù)器資源。 |
• | 用于每次只返回一頁(yè)記錄的技術(shù)是創(chuàng)建 SQL 語(yǔ)句,把 WHERE 子句以及 ORDER BY 子句和 TOP 謂詞組合起來(lái)。此技術(shù)取決于存在一種可唯一標(biāo)識(shí)每一行的辦法。當(dāng)瀏覽記錄時(shí),修改 WHERE 子句使之包含所有唯一標(biāo)識(shí)符大于當(dāng)前頁(yè)最后一個(gè)唯一標(biāo)識(shí)符的記錄。當(dāng)瀏覽記錄時(shí),修改 WHERE 子句使之返回所有唯一標(biāo)識(shí)符小于當(dāng)前頁(yè)第一個(gè)唯一標(biāo)識(shí)符的記錄。兩種查詢都只返回記錄的 TOP 頁(yè)。當(dāng)瀏覽時(shí),需要以降序?yàn)榻Y(jié)果排序。這將有效地返回查詢的最后一頁(yè)(如果需要,顯示之前也許要重新排序結(jié)果)。有關(guān)這個(gè)技術(shù)的一個(gè)示例,請(qǐng)參閱 Paging Through a Query Result。 |
• | 另一項(xiàng)每次只返回一頁(yè)記錄的技術(shù)是創(chuàng)建 SQL 語(yǔ)句,把 TOP 謂詞和嵌入式 SELECT 語(yǔ)句的使用結(jié)合在一起。此技術(shù)并不依賴于存在一種可唯一標(biāo)識(shí)每一行的辦法。使用這項(xiàng)技術(shù)的第一步是把所需頁(yè)的數(shù)量與頁(yè)大小相乘。然后將結(jié)果傳遞給 SQL Query 的 TOP 謂詞,該查詢以升序排列。再把此查詢嵌入到另一個(gè)查詢中,后者從降序排列的嵌入式查詢結(jié)果中選擇 TOP 頁(yè)大小。實(shí)質(zhì)上,返回的是嵌入式查詢的最后一頁(yè)。例如,要返回查詢結(jié)果的第三頁(yè)(頁(yè)大小是 10),應(yīng)該書寫如下所示的命令: SELECT TOP 10 * FROM (SELECT TOP 30 * FROM Customers ORDER BY Id ASC) AS Table1 ORDER BY Id DESC 注意,從查詢中返回的結(jié)果頁(yè)以降序顯示。如果需要,應(yīng)該重新排序。 |
• | 如果數(shù)據(jù)不經(jīng)常變動(dòng),可以在 DataSet 中本地維護(hù)一個(gè)記錄緩存,以此提高性能。例如,可以在本地 DataSet 中存儲(chǔ) 10 頁(yè)有用的數(shù)據(jù),并且只有當(dāng)用戶瀏覽超出緩存第一頁(yè)和最后一頁(yè)時(shí),才從數(shù)據(jù)源中查詢新數(shù)據(jù)。 |
有關(guān)更多信息,請(qǐng)參閱 .NET Data Access Architecture Guide。
用架構(gòu)填充 DataSet
當(dāng)用數(shù)據(jù)填充 DataSet 時(shí),DataAdapter.Fill 方法使用 DataSet 的現(xiàn)有架構(gòu),并使用從 SelectCommand 返回的數(shù)據(jù)填充它。如果在 DataSet 中沒有表名與要被填充的表名相匹配,Fill 方法就會(huì)創(chuàng)建一個(gè)表。默認(rèn)情況下,Fill 僅定義列和列類型。
通過設(shè)置 DataAdapter 的 MissingSchemaAction 屬性,可以重寫 Fill 的默認(rèn)行為。例如,要讓 Fill 創(chuàng)建一個(gè)表架構(gòu),并且還包括主鍵信息、唯一約束、列屬性、是否允許為空、最大列長(zhǎng)度、只讀列和自動(dòng)增量的列,就要把 DataAdapter.MissingSchemaAction 指定為 MissingSchemaAction.AddWithKey。或者,在調(diào)用 DataAdapter.Fill 前,可以調(diào)用 DataAdapter.FillSchema 來(lái)確保當(dāng)填充 DataSet 時(shí)架構(gòu)已到位。
對(duì) FillSchema 的調(diào)用會(huì)產(chǎn)生一個(gè)到服務(wù)器的額外行程,用于檢索附加架構(gòu)信息。為了獲得最佳性能,需要在調(diào)用 Fill 之前指定 DataSet 的架構(gòu),或者設(shè)置 DataAdapter 的 MissingSchemaAction。
使用 CommandBuilder 的最佳實(shí)踐
假設(shè) SelectCommand 執(zhí)行單一表 SELECT,CommandBuilder 就會(huì)以 DataAdapter 的 SelectCommand 屬性為基礎(chǔ)自動(dòng)生成 DataAdapter 的 InsertCommand、UpdateCommand、和 DeleteCommand 屬性。下面是為獲得最佳性能而使用 CommandBuilder 的一些技巧。
• | CommandBuilder 的使用應(yīng)該限制在設(shè)計(jì)時(shí)或即席方案中。生成 DataAdapter 命令屬性所必需的處理會(huì)影響性能。如果預(yù)先知道 INSERT/UPDATE/DELETE 語(yǔ)句的內(nèi)容,就顯式設(shè)置它們。一個(gè)比較好的設(shè)計(jì)技巧是,為 INSERT/UPDATE/DELETE 命令創(chuàng)建存儲(chǔ)過程并顯式配置 DataAdapter 命令屬性以使用它們。 |
• | CommandBuilder 使用 DataAdapter 的 SelectCommand 屬性確定其他命令屬性的值。如果 DataAdapter 的 SelectCommand 本身曾經(jīng)更改過,確保調(diào)用 RefreshSchema 以更新命令屬性。 |
• | 如果 DataAdapter 命令屬性為空(命令屬性默認(rèn)情況下為空),CommandBuilder 僅僅為它生成一條命令。如果顯式設(shè)置了命令屬性,CommandBuilder 不會(huì)重寫它。如果希望 CommandBuilder 為以前已經(jīng)設(shè)置過的命令屬性生成命令,就把命令屬性設(shè)置為空。 |
批處理 SQL 語(yǔ)句
很多數(shù)據(jù)庫(kù)支持把多條命令合并或批處理成一條單一命令執(zhí)行。例如,SQL Server 使您可以用分號(hào) (;) 分隔命令。把多條命令合并成單一命令,能減少到服務(wù)器的行程數(shù),并提高應(yīng)用程序的性能。例如,可以把所有預(yù)定的刪除在應(yīng)用程序中本地存儲(chǔ)起來(lái),然后再發(fā)出一條批處理命令調(diào)用,從數(shù)據(jù)源刪除它們。
雖然這樣做確實(shí)能提高性能,但是,當(dāng)對(duì) DataSet 中的數(shù)據(jù)更新進(jìn)行管理時(shí),可能會(huì)增加應(yīng)用程序的復(fù)雜性。要保持簡(jiǎn)單,可能要在 DataSet 中為每個(gè) DataTable 創(chuàng)建一個(gè) DataAdapter。
用多個(gè)表填充 DataSet
如果使用批處理 SQL 語(yǔ)句檢索多個(gè)表并填充 DataSet,第一個(gè)表用指定給 Fill 方法的表名命名。后面的表用指定給 Fill 方法的表名加上一個(gè)從 1 開始并且增量為 1 的數(shù)字命名。例如,如果運(yùn)行下面的代碼:
'Visual BasicDim da As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM Customers; SELECT * FROM Orders;", myConnection)Dim ds As DataSet = New DataSet()da.Fill(ds, "Customers")//C#SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Customers; SELECT * FROM Orders;", myConnection);DataSet ds = new DataSet();da.Fill(ds, "Customers");
來(lái)自 Customers 表的數(shù)據(jù)放在名為 "Customers" 的 DataTable 中。來(lái)自 Orders 表的數(shù)據(jù)放在名為 "Customers1" 的 DataTable 中。
填充完 DataSet 之后,可以很容易地把 "Customers1" 表的 TableName 屬性改為 "Orders"。但是,后面的填充會(huì)導(dǎo)致 "Customers" 表被重新填充,而 "Orders" 表會(huì)被忽略,并創(chuàng)建另外一個(gè) "Customers1" 表。為了對(duì)這種情況作出補(bǔ)救,創(chuàng)建一個(gè) DataTableMapping,把 "Customers1" 映射到 "Orders",并為其他后面的表創(chuàng)建其他的表映射。例如:
'Visual BasicDim da As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM Customers; SELECT * FROM Orders;", myConnection)da.TableMappings.Add("Customers1", "Orders")Dim ds As DataSet = New DataSet()da.Fill(ds, "Customers")//C#SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Customers; SELECT * FROM Orders;", myConnection);da.TableMappings.Add("Customers1", "Orders");DataSet ds = new DataSet();da.Fill(ds, "Customers");
使用 DataReader
下面是一些使用 DataReader 獲得最佳性能的技巧,同時(shí)還回答了一些關(guān)于使用 DataReader 的常見問題。
• | 在訪問相關(guān) Command 的任何輸出參數(shù)之前,必須關(guān)閉 DataReader。 |
• | 完成讀數(shù)據(jù)之后總是要關(guān)閉 DataReader。如果使用 Connection 只是用于返回 DataReader,那么關(guān)閉 DataReader 之后立刻關(guān)閉它。 另外一個(gè)顯式關(guān)閉 Connection 的方法是把 CommandBehavior.CloseConnection 傳遞給 ExecuteReader 方法,以確保相關(guān)的連接在關(guān)閉 DataReader 時(shí)被關(guān)閉。如果從一個(gè)方法返回 DataReader,而且不能控制 DataReader 或相關(guān)連接的關(guān)閉,則這樣做特別有用。 |
• | 不能在層之間遠(yuǎn)程訪問 DataReader。DataReader 是為已連接好的數(shù)據(jù)訪問設(shè)計(jì)的。 |
• | 當(dāng)訪問列數(shù)據(jù)時(shí),使用類型化訪問器,例如,GetString、GetInt32 等。這使您不用進(jìn)行將 GetValue 返回的 Object 強(qiáng)制轉(zhuǎn)換成特定類型所需的處理。 |
• | 一個(gè)單一連接每次只能打開一個(gè) DataReader。在 ADO 中,如果打開一個(gè)單一連接,并且請(qǐng)求兩個(gè)使用只進(jìn)、只讀游標(biāo)的記錄集,那么 ADO 會(huì)在游標(biāo)生存期內(nèi)隱式打開第二個(gè)、未池化的到數(shù)據(jù)存儲(chǔ)區(qū)的連接,然后再隱式關(guān)閉該連接。對(duì)于 ADO.NET,“秘密”完成的動(dòng)作很少。如果想在相同的數(shù)據(jù)存儲(chǔ)區(qū)上同時(shí)打開兩個(gè) DataReaders,就必須顯式創(chuàng)建兩個(gè)連接,每個(gè) DataReader 一個(gè)。這是 ADO.NET 為池化連接的使用提供更多控制的一種方法。 |
• | 默認(rèn)情況下,DataReader 每次 Read 時(shí)都要把整行加載到內(nèi)存。這允許在當(dāng)前行內(nèi)隨機(jī)訪問列。如果不需要這種隨機(jī)訪問,為了提高性能,就把 CommandBehavior.SequentialAccess 傳遞給 ExecuteReader 調(diào)用。這將 DataReader 的默認(rèn)行為更改為僅在請(qǐng)求時(shí)將數(shù)據(jù)加載到內(nèi)存。注意,CommandBehavior.SequentialAccess 要求順序訪問返回的列。也就是說(shuō),一旦讀過返回的列,就不能再讀它的值了。 |
• | 如果已經(jīng)完成讀取來(lái)自 DataReader 的數(shù)據(jù),但仍然有大量掛起的未讀結(jié)果,就在調(diào)用 DataReader 的 Close 之前先調(diào)用 Command 的 Cancel。調(diào)用 DataReader 的 Close 會(huì)導(dǎo)致在關(guān)閉游標(biāo)之前檢索掛起的結(jié)果并清空流。調(diào)用 Command 的 Cancel 會(huì)放棄服務(wù)器上的結(jié)果,這樣,DataReader 在關(guān)閉的時(shí)候就不必讀這些結(jié)果。如果要從 Command 返回輸出參數(shù),還要調(diào)用 Cancel 放棄它們。如果需要讀取任何輸出參數(shù),不要調(diào)用 Command 的 Cancel,只要調(diào)用 DataReader 的 Close 即可。 |
二進(jìn)制大對(duì)象 (BLOB)
用 DataReader 檢索二進(jìn)制大對(duì)象 (BLOB) 時(shí),應(yīng)該把 CommandBehavior.SequentialAccess 傳遞給 ExecuteReader 方法調(diào)用。因?yàn)?DataReader 的默認(rèn)行為是每次 Read 都把整行加載到內(nèi)存,又因?yàn)?BLOB 值可能非常大,所以結(jié)果可能由于單個(gè) BLOB 而使大量?jī)?nèi)存被用光。SequentialAccess 將 DataReader 的行為設(shè)置為只加載請(qǐng)求的數(shù)據(jù)。然后還可以使用 GetBytes 或 GetChars 控制每次加載多少數(shù)據(jù)。
記住,使用 SequentialAccess 時(shí),不能不按順序訪問 DataReader 返回的不同字段。也就是說(shuō),如果查詢返回三列,其中第三列是 BLOB,并且想訪問前兩列中的數(shù)據(jù),就必須在訪問 BLOB 數(shù)據(jù)之前先訪問第一列的值,然后訪問第二列的值。這是因?yàn)楝F(xiàn)在數(shù)據(jù)是順序返回的,并且 DataReader 一旦讀過該數(shù)據(jù),該數(shù)據(jù)就不再可用。
有關(guān)如何在 ADO.NET 中訪問 BLOB 的詳細(xì)描述,請(qǐng)參閱 Obtaining BLOB Values from a Database。

使用命令
ADO.NET 提供了幾種命令執(zhí)行的不同方法以及優(yōu)化命令執(zhí)行的不同選項(xiàng)。下面包括一些技巧,它們是關(guān)于選擇最佳命令執(zhí)行以及如何提高執(zhí)行命令的性能。
使用 OleDbCommand 的最佳實(shí)踐
不同 .NET 框架數(shù)據(jù)提供程序之間的命令執(zhí)行被盡可能標(biāo)準(zhǔn)化了。但是,數(shù)據(jù)提供程序之間仍然存在差異。下面給出一些技巧,可微調(diào)用于 OLE DB 的 .NET 框架數(shù)據(jù)提供程序的命令執(zhí)行。
• | 按照 ODBC CALL 語(yǔ)法使用 CommandType.Text 調(diào)用存儲(chǔ)過程。使用 CommandType.StoredProcedure 只是秘密地生成 ODBC CALL 語(yǔ)法。 |
• | 一定要設(shè)置 OleDbParameter 的類型、大小(如果適用)、以及精度和范圍(如果參數(shù)類型是 numeric 或 decimal)。注意,如果不顯式提供參數(shù)信息,OleDbCommand 會(huì)為每個(gè)執(zhí)行命令重新創(chuàng)建 OLE DB 參數(shù)訪問器。 |
使用 SqlCommand 的最佳實(shí)踐
使用 SqlCommand 執(zhí)行存儲(chǔ)過程的快速提示:如果調(diào)用存儲(chǔ)過程,將 SqlCommand 的 CommandType 屬性指定為 StoredProcedure 的 CommandType。這樣通過將該命令顯式標(biāo)識(shí)為存儲(chǔ)過程,就不需要在執(zhí)行之前分析命令。
使用 Prepare 方法
對(duì)于重復(fù)作用于數(shù)據(jù)源的參數(shù)化命令,Command.Prepare 方法能提高性能。Prepare 指示數(shù)據(jù)源為多次調(diào)用優(yōu)化指定的命令。要想有效利用 Prepare,需要徹底理解數(shù)據(jù)源是如何響應(yīng) Prepare 調(diào)用的。對(duì)于一些數(shù)據(jù)源(例如 SQL Server 2000),命令是隱式優(yōu)化的,不必調(diào)用 Prepare。對(duì)于其他(例如 SQL Server 7.0)數(shù)據(jù)源,Prepare 會(huì)比較有效。
顯式指定架構(gòu)和元數(shù)據(jù)
只要用戶沒有指定元數(shù)據(jù)信息,ADO.NET 的許多對(duì)象就會(huì)推斷元數(shù)據(jù)信息。下面是一些示例:
• | DataAdapter.Fill 方法,如果 DataSet 中沒有表和列,DataAdapter.Fill 方法會(huì)在 DataSet 中創(chuàng)建表和列。 |
• | CommandBuilder,它會(huì)為單表 SELECT 命令生成 DataAdapter 命令屬性。 |
• | CommandBuilder.DeriveParameters,它會(huì)填充 Command 對(duì)象的 Parameters 集合。 |
但是,每次用到這些特性,都會(huì)有性能損失。建議將這些特性主要用于設(shè)計(jì)時(shí)和即席應(yīng)用程序中。在可能的情況下,顯式指定架構(gòu)和元數(shù)據(jù)。其中包括在 DataSet 中定義表和列、定義 DataAdapter 的 Command 屬性、以及為 Command 定義 Parameter 信息。
ExecuteScalar 和 ExecuteNonQuery
如果想返回像 Count(*)、Sum(Price) 或 Avg(Quantity) 的結(jié)果那樣的單值,可以使用 Command.ExecuteScalar。ExecuteScalar 返回第一行第一列的值,將結(jié)果集作為標(biāo)量值返回。因?yàn)閱为?dú)一步就能完成,所以 ExecuteScalar 不僅簡(jiǎn)化了代碼,還提高了性能;要是使用 DataReader 就需要兩步才能完成(即,ExecuteReader + 取值)。
使用不返回行的 SQL 語(yǔ)句時(shí),例如修改數(shù)據(jù)(例如INSERT、UPDATE 或 DELETE)或僅返回輸出參數(shù)或返回值,請(qǐng)使用 ExecuteNonQuery。這避免了用于創(chuàng)建空 DataReader 的任何不必要處理。
有關(guān)更多信息,請(qǐng)參閱 Executing a Command。
測(cè)試 Null
如果表(在數(shù)據(jù)庫(kù)中)中的列允許為空,就不能測(cè)試參數(shù)值是否“等于”空。相反,需要寫一個(gè) WHERE 子句,測(cè)試列和參數(shù)是否都為空。下面的 SQL 語(yǔ)句返回一些行,它們的 LastName 列等于賦給 @LastName 參數(shù)的值,或者 LastName 列和 @LastName 參數(shù)都為空。
SELECT * FROM CustomersWHERE ((LastName = @LastName) OR (LastName IS NULL AND @LastName IS NULL))
把 Null 作為參數(shù)值傳遞
對(duì)數(shù)據(jù)庫(kù)的命令中,當(dāng)把空值作為參數(shù)值發(fā)送時(shí),不能使用 null(Visual Basic廬 .NET 中為 Nothing)。而需要使用 DBNull.Value。例如:
'Visual BasicDim param As SqlParameter = New SqlParameter("@Name", SqlDbType.NVarChar, 20)param.Value = DBNull.Value//C#SqlParameter param = new SqlParameter("@Name", SqlDbType.NVarChar, 20);param.Value = DBNull.Value;
執(zhí)行事務(wù)
ADO.NET 的事務(wù)模型已經(jīng)更改。在 ADO 中,當(dāng)調(diào)用 StartTransaction 時(shí),調(diào)用之后的任何更新操作都被視為是事務(wù)的一部分。但是,在 ADO.NET 中,當(dāng)調(diào)用 Connection.BeginTransaction 時(shí),會(huì)返回一個(gè) Transaction 對(duì)象,需要把它與 Command 的 Transaction 屬性聯(lián)系起來(lái)。這種設(shè)計(jì)可以在一個(gè)單一連接上執(zhí)行多個(gè)根事務(wù)。如果未將 Command.Transaction 屬性設(shè)置為一個(gè)針對(duì)相關(guān)的 Connection 而啟動(dòng)的 Transaction,那么 Command 就會(huì)失敗并引發(fā)異常。
即將發(fā)布的 .NET 框架將使您可以在現(xiàn)有的分布式事務(wù)中手動(dòng)登記。這對(duì)于對(duì)象池方案來(lái)說(shuō)很理想;在該方案中,一個(gè)池對(duì)象打開一次連接,但是在多個(gè)獨(dú)立的事務(wù)中都涉及到該對(duì)象。.NET 框架 1.0 發(fā)行版中這一功能并不可用。
有關(guān)事務(wù)的更多信息,請(qǐng)參閱 Performing Transactions 以及 .NET Data Access Architecture Guide。

使用連接
高性能應(yīng)用程序與使用中的數(shù)據(jù)源保持最短時(shí)間的連接,并且利用性能增強(qiáng)技術(shù),例如連接池。下面的主題提供一些技巧,有助于在使用 ADO.NET 連接到數(shù)據(jù)源時(shí)獲得更好的性能。
連接池
用于 ODBC 的 SQL Server、OLE DB 和 .NET 框架數(shù)據(jù)提供程序隱式緩沖連接。通過在連接字符串中指定不同的屬性值,可以控制連接池的行為。有關(guān)如何控制連接池的行為的詳細(xì)信息,請(qǐng)參閱 Connection Pooling for the SQL Server .NET Data Provider 和 Connection Pooling for the OLE DB .NET Data Provider。
用 DataAdapter 優(yōu)化連接
DataAdapter 的 Fill 和 Update 方法在連接關(guān)閉的情況下自動(dòng)打開為相關(guān)命令屬性指定的連接。如果 Fill 或 Update 方法打開了連接,Fill 或 Update 將在操作完成的時(shí)候關(guān)閉它。為了獲得最佳性能,僅在需要時(shí)將與數(shù)據(jù)庫(kù)的連接保持為打開。同時(shí),減少打開和關(guān)閉多操作連接的次數(shù)。
如果只執(zhí)行單個(gè)的 Fill 或 Update 方法調(diào)用,建議允許 Fill 或 Update 方法隱式打開和關(guān)閉連接。如果對(duì) Fill 和/或 Update 調(diào)用有很多,建議顯式打開連接,調(diào)用 Fill 和/或 Update,然后顯式關(guān)閉連接。
另外,當(dāng)執(zhí)行事務(wù)時(shí),顯式地在開始事務(wù)之前打開連接,并在提交之后關(guān)閉連接。例如:
'Visual BasicPublic Sub RunSqlTransaction(da As SqlDataAdapter, myConnection As SqlConnection, ds As DataSet)myConnection.Open()Dim myTrans As SqlTransaction = myConnection.BeginTransaction()myCommand.Transaction = myTransTryda.Update(ds)myTrans.Commit()Console.WriteLine("Update successful.")Catch e As ExceptionTrymyTrans.Rollback()Catch ex As SqlExceptionIf Not myTrans.Connection Is Nothing ThenConsole.WriteLine("An exception of type " & ex.GetType().ToString() & _" was encountered while attempting to roll back the transaction.")End IfEnd TryConsole.WriteLine("An exception of type " & e.GetType().ToString() & " was encountered.")Console.WriteLine("Update failed.")End TrymyConnection.Close()End Sub//C#public void RunSqlTransaction(SqlDataAdapter da, SqlConnection myConnection, DataSet ds){myConnection.Open();SqlTransaction myTrans = myConnection.BeginTransaction();myCommand.Transaction = myTrans;try{da.Update(ds);myCommand.Transaction.Commit();Console.WriteLine("Update successful.");}catch(Exception e){try{myTrans.Rollback();}catch (SqlException ex){if (myTrans.Connection != null){Console.WriteLine("An exception of type " + ex.GetType() +" was encountered while attempting to roll back the transaction.");}}Console.WriteLine(e.ToString());Console.WriteLine("Update failed.");}myConnection.Close();}
始終關(guān)閉 Connection 和 DataReader
完成對(duì) Connection 或 DataReader 對(duì)象的使用后,總是顯式地關(guān)閉它們。盡管垃圾回收最終會(huì)清除對(duì)象并因此釋放連接和其他托管資源,但垃圾回收僅在需要時(shí)執(zhí)行。因此,確保任何寶貴的資源被顯式釋放仍然是您的責(zé)任。并且,沒有顯式關(guān)閉的 Connections 可能不會(huì)返回到池中。例如,一個(gè)超出作用范圍卻沒有顯式關(guān)閉的連接,只有當(dāng)池大小達(dá)到最大并且連接仍然有效時(shí),才會(huì)被返回到連接池中。
注 不要在類的 Finalize 方法中對(duì) Connection、DataReader 或任何其他托管對(duì)象調(diào)用 Close 或 Dispose。最后完成的時(shí)候,僅釋放類自己直接擁有的非托管資源。如果類沒有任何非托管資源,就不要在類定義中包含 Finalize 方法。
在 C# 中使用 "Using" 語(yǔ)句
對(duì)于 C# 程序員來(lái)說(shuō),確保始終關(guān)閉 Connection 和 DataReader 對(duì)象的一個(gè)方便的方法就是使用 using 語(yǔ)句。using 語(yǔ)句在離開自己的作用范圍時(shí),會(huì)自動(dòng)調(diào)用被“使用”的對(duì)象的 Dispose。例如:
//C#string connString = "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;";using (SqlConnection conn = new SqlConnection(connString)){SqlCommand cmd = conn.CreateCommand();cmd.CommandText = "SELECT CustomerId, CompanyName FROM Customers";conn.Open();using (SqlDataReader dr = cmd.ExecuteReader()){while (dr.Read())Console.WriteLine("{0}/t{1}", dr.GetString(0), dr.GetString(1));}}
Using 語(yǔ)句不能用于 Microsoft廬 Visual Basic廬 .NET。
避免訪問 OleDbConnection.State 屬性
如果連接已經(jīng)打開,OleDbConnection.State 屬性會(huì)對(duì) DBPROP_CONNECTIONSTATUS 屬性的 DATASOURCEINFO 屬性集執(zhí)行本地 OLE DB 調(diào)用 IDBProperties.GetProperties,這可能會(huì)導(dǎo)致對(duì)數(shù)據(jù)源的往返行程。也就是說(shuō),檢查 State 屬性的代價(jià)可能很高。所以僅在需要時(shí)檢查 State 屬性。如果需要經(jīng)常檢查該屬性,監(jiān)聽 OleDbConnection 的 StateChange 事件可能會(huì)使應(yīng)用程序的性能好一些。有關(guān) StateChange 事件的詳細(xì)信息,請(qǐng)參閱 Working with Connection Events。

與 XML 集成
ADO.NET 在 DataSet 中提供了廣泛的 XML 集成,并公開了 SQL Server 2000 及其更高版本提供的部分 XML 功能。還可以使用 SQLXML 3.0 廣泛地訪問 SQL Server 2000 及其更高版本中的 XML 功能。下面是使用 XML 和 ADO.NET 的技巧和信息。
DataSet 和 XML
DataSet 與 XML 緊密集成,并提供如下功能:
• | 從 XSD 架構(gòu)中加載 DataSet 的架構(gòu)或關(guān)系型結(jié)構(gòu)。 |
• | 從 XML 加載 DataSet 的內(nèi)容。 |
• | 如果沒有提供架構(gòu),可以從 XML 文檔的內(nèi)容推斷出 DataSet 的架構(gòu)。 |
• | 把 DataSet 的架構(gòu)寫成 XSD 架構(gòu)。 |
• | 把 DataSet 的內(nèi)容寫成 XML。 |
• | 同步訪問使用 DataSet 的數(shù)據(jù)的關(guān)系表示,以及使用 XmlDataDocument 的數(shù)據(jù)的層次表示。 |
注 可以使用這種同步把 XML 功能(例如,XPath 查詢和 XSLT 轉(zhuǎn)換)應(yīng)用到 DataSet 中的數(shù)據(jù),或者在保留原始 XML 保真度的前提下為 XML 文檔中數(shù)據(jù)的全部或其中一個(gè)子集提供關(guān)系視圖。
關(guān)于 DataSet 提供的 XML 功能的詳細(xì)信息,請(qǐng)參閱 XML and the DataSet。
架構(gòu)推斷
從 XML 文件加載 DataSet 時(shí),可以從 XSD 架構(gòu)加載 DataSet 架構(gòu),或者在加載數(shù)據(jù)前預(yù)定義表和列。如果沒有可用的 XSD 架構(gòu),而且不知道為 XML 文件的內(nèi)容定義哪些表和列,就可以在 XML 文檔結(jié)構(gòu)的基礎(chǔ)上對(duì)架構(gòu)進(jìn)行推斷。
架構(gòu)推斷作為遷移工具很有用,但應(yīng)只限于設(shè)計(jì)階段應(yīng)用程序,這是由于推斷處理有如下限制。
• | 對(duì)架構(gòu)的推斷會(huì)引入影響應(yīng)用程序性能的附加處理。 |
• | 所有推斷列的類型都是字符串。 |
• | 推斷處理不具有確定性。也就是說(shuō),它是基于 XML 文件內(nèi)容的,而不是預(yù)定的架構(gòu)。因此,對(duì)于兩個(gè)預(yù)定架構(gòu)相同的 XML 文件,由于它們的內(nèi)容不同,結(jié)果得到兩個(gè)完全不同的推斷架構(gòu)。 |
有關(guān)更多信息,請(qǐng)參閱 Inferring DataSet Relational Structure from XML。
用于 XML 查詢的 SQL Server
如果正從 SQL Server 2000 FOR XML 返回查詢結(jié)果,可以讓用于 SQL Server 的 .NET 框架數(shù)據(jù)提供程序使用 SqlCommand.ExecuteXmlReader 方法直接創(chuàng)建一個(gè) XmlReader。
SQLXML 托管類
.NET 框架中有一些類,公開用于 SQL Server 2000 的 XML 的功能。這些類可在 Microsoft.Data.SqlXml 命名空間中找到,它們添加了執(zhí)行 XPath 查詢和 XML 模板文件以及把 XSLT 轉(zhuǎn)換應(yīng)用到數(shù)據(jù)的能力。
SQLXML 托管類包含在用于 Microsoft SQL Server 2000 的 XML (SQLXML 2.0) 發(fā)行版中,可從 XML for Microsoft SQL Server 2000 Web Release 2 (SQLXML 2.0) ??μ?。

更多有用的技巧
下面是一些編寫 ADO.NET 代碼時(shí)的通用技巧。
避免自動(dòng)增量值沖突
就像大多數(shù)數(shù)據(jù)源一樣,DataSet 使您可標(biāo)識(shí)那些添加新行時(shí)自動(dòng)對(duì)其值進(jìn)行遞增的列。在 DataSet 中使用自動(dòng)增量的列時(shí),如果自動(dòng)增量的列來(lái)自數(shù)據(jù)源,可避免添加到 DataSet 的行和添加到數(shù)據(jù)源的行之間本地編號(hào)沖突。
例如,考慮一個(gè)表,它的主鍵列 CustomerID 是自動(dòng)增量的。兩個(gè)新的客戶信息行添加到表中,并接收到自動(dòng)增量的 CustomerID 值 1 和 2。然后,只有第二個(gè)客戶行被傳遞給 DataAdapter 的方法 Update,新添加的行在數(shù)據(jù)源接收到一個(gè)自動(dòng)增量的 CustomerID 值 1,與 DataSet 中的值 2 不匹配。當(dāng) DataAdapter 用返回值填充表中第二行時(shí),就會(huì)出現(xiàn)約束沖突,因?yàn)榈谝粋€(gè)客戶行已經(jīng)使用了 CustomerID 值 1。
要避免這種情況,建議在使用數(shù)據(jù)源上自動(dòng)增量的列以及 DataSet 上自動(dòng)增量的列時(shí),把 DataSet 中的列創(chuàng)建為 AutoIncrementStep 值等于 -1 并且 AutoIncrementSeed 值等于 0,另外,還要確保數(shù)據(jù)源生成的自動(dòng)增量標(biāo)識(shí)值從 1 開始,并且以正階值遞增。因此,DataSet 為自動(dòng)增量值生成負(fù)數(shù),與數(shù)據(jù)源生成的正自動(dòng)增量值不沖突。另外一個(gè)選擇是使用 Guid 類型的列,而不是自動(dòng)增量的列。生成 Guid 值的算法應(yīng)該永遠(yuǎn)不會(huì)使數(shù)據(jù)源中生成的 Guid 值與 DataSet 中生成的 Guid 值一樣。
如果自動(dòng)增量的列只是用作唯一值,而且沒有任何意義,就考慮使用 Guid 代替自動(dòng)增量的列。它們是唯一的,并且避免了使用自動(dòng)增量的列所必需的額外工作。
有關(guān)從數(shù)據(jù)源檢索自動(dòng)增量的列值的示例,請(qǐng)參閱 Retrieving Identity or AutoNumber Values。
檢查開放式并發(fā)沖突
按照設(shè)計(jì),由于 DataSet 是與數(shù)據(jù)源斷開的,所以,當(dāng)多個(gè)客戶端在數(shù)據(jù)源上按照開放式并發(fā)模型更新數(shù)據(jù)時(shí),需要確保應(yīng)用程序避免沖突。
在測(cè)試開放式并發(fā)沖突時(shí)有幾項(xiàng)技術(shù)。一項(xiàng)技術(shù)涉及在表中包含時(shí)間戳列。另外一項(xiàng)技術(shù)是,驗(yàn)證一行中所有列的原始值是否仍然與通過在 SQL 語(yǔ)句中使用 WHERE 子句進(jìn)行測(cè)試時(shí)在數(shù)據(jù)庫(kù)中找到的值相匹配。
有關(guān)包含代碼示例的該主題的詳細(xì)討論,請(qǐng)參閱 Optimistic Concurrency。
多線程編程
ADO.NET 對(duì)性能、吞吐量和可伸縮性進(jìn)行優(yōu)化。因此,ADO.NET 對(duì)象不鎖定資源,并且必須只用于單線程。一個(gè)例外是 DataSet,它對(duì)多個(gè)閱讀器是線程安全的。但是,在寫的時(shí)候需要把 DataSet 鎖定。
僅在需要的時(shí)候才用 COM Interop 訪問 ADO
ADO.NET 的設(shè)計(jì)目的是成為許多應(yīng)用程序的最佳解決方案。但是,有些應(yīng)用程序需要只有使用 ADO 對(duì)象才有的功能,例如,ADO 多維 (ADOMD)。在這些情況下,應(yīng)用程序可以用 COM Interop 訪問 ADO。注意使用 COM Interop 訪問具有 ADO 的數(shù)據(jù)會(huì)導(dǎo)致性能降低。在設(shè)計(jì)應(yīng)用程序時(shí),首先在實(shí)現(xiàn)用 COM Interop 訪問 ADO 的設(shè)計(jì)之前,先確定 ADO.NET 是否滿足設(shè)計(jì)需求。
AspNet技術(shù):ADO.NET 的最佳實(shí)踐技巧,轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。