.NET 數據訪問架構指南(一)

.NET 數據訪問架構指南
Alex Mackman, Chris Brooks, Steve Busby, 和 Ed Jezierski
微軟公司
2001年10月

概述:本文提供了在多層.NET應用程序中實施基於ADO.NET的數據訪問層的指導原則。其重點是一組通用數據訪問任務和方案,並指導你選擇最合適的途徑和技術(68張打印頁)。

目錄

簡介

如果你在爲.NET應用程序設計數據訪問層,那麼就應該把 Microsoft ADO.NET用作數據訪問模型。ADO.NET擴展豐富,並且支持結合鬆散的數據訪問需求、多層Web應用程序及Web服務。通常,它利用許多擴展豐富的對象模型, ADO.NET提供了多種方法用於解決一個特定問題。

本文將指導你選擇最合適的數據訪問方法,其做法是詳細列出大範圍的通用數據訪問方案,提供運用技巧,並且建議最優實踐。本文還回答了其它經常問到的問題:何處最適合存放數據庫鏈接字符串?應如何實現鏈接存儲池?如何處理事務?如何實現分頁以允許用戶在許多記錄中滾動?

注意本文的重點是ADO.NET的使用:利用SQL Server .NETData Provider--隨ADO.NET一起提供的兩個供應器之一--訪問Microsoft SQL Server 2000。本文在合適的地方,將突出顯示在你使用OLE DB .NET數據供應器訪問其它OLE DB敏感數據源時需要注意的所有差別。

對於利用本文所討論的指導原則和最優實踐所開發的數據訪問組件的具體實現,見(Data Access Application Block)數據訪問應用程序塊。注意,本實現的源代碼是可以獲得的,並且能直接用於你的.NET應用程序中。

誰應當閱讀本文?

本文爲希望構建.NET應用程序的應用程序設計師和企業開發人員提供了指導原則。如果你負責設計並開發多層.NET應用程序的數據層,那麼請閱讀本文。

你首先需要知道什麼?

要利用本指南構建.NET應用程序,你必須有利用ActiveX數據對象(ADO)和/或 OLE DB開發數據訪問代碼的實際經驗,及SQL Server經驗。你也必須明白如何爲.NET平臺開發管理代碼,並且也必須清楚ADO.NET數據訪問模型引入的基本變化。有關.NET開發的更多信息,見http://msdn.microsoft.com/net

ADO.NET簡介

ADO.NET是.NET應用程序的數據訪問模型。它能用於訪問關係型數據庫系統,如SQL Server 2000,及很多其它已經配備了OLE DB供應器的數據源。在某種程度上,ADO.NET代表了最新版本的ADO技術。然而,ADO.NET引入了一些重大變化和革新,它們專門用於結構鬆散的、本質非鏈接的Web應用程序。關於ADO 與 ADO.NET的比較,見MSDN中的“用於ADO程序員的ADO.NET”一文。

ADO.NET引入的一個重要變化是,用DataTable, DataSet, DataAdapter, 和 DataReader對象的組合代替了ADO Recordset對象。DataTable表示來自一個表的行集合,在這方面它與Recordset類似。DataSet表示DataTable對象的集合,及與其它表綁定在一起的關係和限制。實際上,DataSet是具有內置的擴展標記語言(XML)支持的內存中的關聯結構。

DataSet的一個主要特點是,它對底層的數據源一無所知,而這些數據源可能用於對其進行填充。這是一個分離的用於表示數據集合的獨立實體,並且它可通過多層應用程序的不同層由一個組件傳遞到另一組件。它也可作爲XML 數據流被序列化,因而非常適合於不同類型平臺間的數據傳輸。ADO.NET使用DataAdapter對象爲發送到和來自DataSet及底層數據源的數據建立通道。DataAdapter對象還支持增強的批更新特性,以前這是Recorder的相關功能。

圖1顯示了完整的DataSet對象模型。

adonet1.gif
圖1 DataSet 對象模型

.NET 數據供應器

ADO.NET 依靠.NET 數據供應器的服務。 它們提供了對底層數據源的訪問,包括四個主要對象(Connection, Command, DataReader,及DataAdapter),目前,ADO.NET只發行了兩個供應器:

  • SQL Server .NET 數據供應器。這是用於Microsoft SQL Server 7.0及其以後版本數據庫的供應器,它優化了對SQL Server的訪問,並利用 SQL Server內置的數據轉換協議直接與SQL Server通信。
  • 當鏈接到SQL Server 7.0 或 SQL Server 2000時,總是要使用此供應器。
  • OLE DB .NET 數據供應器。. 這是一個用於管理OLE DB 數據源的供應器。它的效率稍低於SQL Server .NET Data Provider,因爲在與數據庫通信時,它需通過OLE DB層進行呼叫。注意,此供應器不支持用於開放數據庫鏈接(ODBC),MSDASQL的OLE DB供應器。對於ODBC數據源,應使用ODBC .NET數據供應器。有關與ADO.NET兼容的OLE DB供應器列表,見。

目前測試版中的其它.NET數據供應器包括:

  • ODBC .NET 數據供應器。目前Beta 1.0版可供下載。它提供了對ODBC驅動器的內置訪問,其方式與OLE DB .NET數據供應器提供的對本地OLE DB供應器的訪問方式相同。關於ODBC .NET及Beta版下載的更多信息見.
  • 用於從SQL Server 2000中得到XML的管理供應器。用於SQL Server Web升級2版的XML還包括了專用於從SQL Server 2000中得到XML的管理供應器。關於此升級版本的更多信息,見 .

名稱空間組織

與每個.NET數據供應器相關的類型(類,結構,枚舉,等等)位於它們各自的名稱空間中:

  • System.Data.SqlClient. 包含了 SQL Server .NET 數據供應器類型。
  • System.Data.OleDb. 包含了 OLE DB .NET數據供應器類型。
  • System.Data.Odbc. 包含了ODBC .NET數據供應器類型。
  • System.Data. 包含了獨立於供應器的類型,如DataSet及DataTable。

在各自關聯的名稱空間中,每個供應器都提供了Connection, Command, DataReader, 及 DataAdapter對象的實現。SqlClient實現都有前綴"Sql";而OleDb實現前面都有前綴"OleDb"。例如,Connection對象的 SqlClient實現是SqlConnection。而OleDb實現是OleDbConnection。類似的,DataAdapter對象的兩種實現是SqlDataAdapter 和OleDbDataAdapter。

通用編程

如果你很有可能以不同的數據源爲目標,並希望將代碼從一種數據源移植到另一數據源,那麼可以考慮對System.Data名稱空間中的IDbConnection, IDbCommand, IDataReader,和IDbDataAdapter接口進行編程。Connection, Command, DataReader, 及 DataAdapter對象的所有實現都必須支持這些接口。

關於實現.NET數據供應器的更多信息,見http://msdn.microsoft.com/library/en-us/cpguide/html/cpconimplementingnetdataprovider.asp.

圖2顯示了數據訪問堆棧及ADO.NET如何與其它數據訪問技術,包括ADO和OLE DB,聯繫起來。該圖還顯示了ADO.NET模型中的兩個管理供應器和主要對象。

adonet2.gif
圖2 數據訪問堆棧

關於ADO到ADO.NET的演化,見MSDN雜誌2000年11月期的文章“ADO+簡介:用於微軟.NET框架的數據訪問服務”。

存儲過程與直接SQL的比較

在本文剩餘部分的大部分代碼片段中,都使用了SqlCommand對象調用存儲過程去執行數據庫操作。在一些例子中,你見不到SqlCommand對象,因爲存儲過程名直接傳遞給了SqlDataAdapter對象,但這仍將導致SqlCommand對象的創建。

使用存儲過程而非SQL語句的原因是:

  • 存儲過程通常會使性能增加,因爲數據庫可以優化過程使用的數據訪問計劃,並對其進行緩存以備將來重用。
  • 在數據庫中,存儲過程可分別得到保護。客戶可以被給予執行某個存儲過程的權限,但無權處理底層的表。
  • 存儲過程將導致維護簡單,因爲在一個已部署組件內,修改存儲過程通常要比修改硬編碼的SQL語句簡單。
  • 存儲過程增加了一個從底層的數據庫結構中提取出的層。存儲過程的客戶與存儲過程的實現細節及底層結構被隔離開了。
  • 存儲過程可以降低網絡流量,因爲SQL語句可以以批處理的方式執行,而不是從客戶端發送多個請求。

屬性與構造函數的比較

可以通過構造函數參數或直接設置屬性來爲ADO.NET對象設置具體的屬性值。例如,下面的代碼片段在功能上是等同的。

// Use constructor arguments to configure command object
SqlCommand cmd = new SqlCommand( "SELECT * FROM PRODUCTS", conn );

// The above line is functionally equivalent to the following
// three lines which set properties explicitly
sqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT * FROM PRODUCTS";

從性能角度來說,兩種方法的差別可以忽略,因爲設置或獲得.NET對象的屬性比對COM對象執行類似操作要有效得多。

所作出的選擇只是個人愛好和編碼風格而已。然而,明確地設置屬性的確使代碼易於理解(特別是當你不熟悉ADO.NET對象模型時),便於調試。

注意 過去,VB開發人員被建議避免使用"Dim x As New…"結構創建對象。在COM環境中,這些代碼將導致COM對象創建過程的“短路”,產生一些奇妙的和不怎麼奇妙的錯誤。然而,在.NET環境中,這已不再是一個問題。

管理數據庫鏈接

數據庫鏈接是一種危險的、昂貴的、有限的資源,特別是在多層Web應用程序中。你必須正確管理你的鏈接,因爲你的方法將極大的影響應用程序的整體升級性。還有,必須仔細考慮在哪兒存放鏈接字符串。你需要一個可配置的、安全的位置。

在管理數據庫鏈接和鏈接字符串時,你應當努力:

  • 通過跨多個客戶多路複用一池數據庫鏈接來幫助實現應用程序的擴展性。
  • 採用可配置的、高性能的鏈接池戰略。
  • 在訪問SQL Server時使用微軟Windows操作系統認證。
  • 避免中間層的冒充。
  • 安全地存儲鏈接字符串。
  • 較晚地打開數據庫鏈接,而較早地關閉它們。

本節討論鏈接池,並幫你選擇合適的鏈接池戰略。其它可選方法也是存在的。本節也將考慮如何管理、存儲、控制數據庫鏈接字符串。最後,本節還提供了兩個編碼方案,使用它們將有助於確保鏈接已可靠關閉,並返回到鏈接池中。

鏈接池

數據庫鏈接池使應用程序能夠重用池中的現有鏈接,而不是重複地建立對數據庫的鏈接。這種技術將極大地增加應用程序的可擴展性,因爲有限的數據庫鏈接可以爲很多的客戶提供服務。此技術也將提高性能,因爲能夠避免用於建立新鏈接的巨大時間。

數據訪問技術,如ODBC和OLE DB,提供了多種形式的鏈接池,它們可配置到不同級別上。這兩種方式對數據庫客戶端應用程序來說都是透明的。OLE DB鏈接池經常被稱爲會話或資源池。

關於微軟數據訪問組件(MDAC)中池的一般討論,見http://msdn.microsoft.com/library/en-us/dnmdac/html/pooling2.asp

ADO.NET數據供應器提供了透明的鏈接池,每種鏈接池的確切機制對每種供應器來說是不同的。本節討論的鏈接池是關於:

用SQL Server .NET 數據供應器池化

如果正在使用SQL Server .NET數據供應器,那麼就可使用該供應器提供的鏈接池化支持特性。它是由供應器在管理代碼內內置實現的對事務敏感的高效機制。每個過程都將創建池,並且直到過程結束,池才被取消。

你可以透明地使用此種鏈接池,但應當清楚池是如何被管理的,並要知道可以用哪些選項來調整鏈接池。

如何配置SQL Server .NET數據供應器鏈接池

可以使用一組名稱-值對以鏈接字符串的形式配置鏈接池。例如,可以配置池是否有效(默認是有效的),池的最大、最小容量,用於打開鏈接的排隊請示被阻斷的時間。下面的示例字符串配置了池的最大和最小容量。

"Server=(local); Integrated Security=SSPI; Database=Northwind; 
Max Pool Size=75; Min Pool Size=5"

當鏈接打開,池被創建時,多個鏈接增加到池中以使鏈接數滿足所配置的最小值。此後,鏈接就能增加到池中,直到配置的最大池計數。當達到最大計數時,打開新鏈接的請求將排隊一段可配置的時間。

選擇池容量

能建立最大極限對於管理幾千用戶同時發出請求的大型系統來說是非常重要的。你需要監視鏈接池及應用程序的性能,以確定系統的最優池容量。最優容量還要依賴於運行SQL Server的硬件。

在開發期間,也許需要減小默認的最大池容量(目前是100)以幫助查找鏈接泄漏。

如果設立了最小池容量,那麼當池最初被填充以達到該值時,會導致一些性能損失,儘管最初鏈接的幾個客戶會從中受益。注意,創建新鏈接的過程被序列化了,這就意味着當池最初被填充時,服務器無法處理同時發生的請求。

關於監視鏈接池的更多信息,見本文監視鏈接池一節。關於鏈接池鏈接字符串關鍵字的完整列表,見http://msdn.microsoft.com/library/en-us/cpguide/html/cpconconnectionpoolingforsqlservernetdataprovider.asp

更多信息

在使用SQL Server .NET數據供應器鏈接池時,必須清楚:

鏈接是通過對鏈接字符串精確匹配的法則被池化的。池化機制對名稱-值對間的空格敏感。例如,下面的兩個鏈接字符串將生成單獨的池,因爲第二個字符串包含了一個額外的空字符。
SqlConnection conn = new SqlConnection(
         "Integrated Security=SSPI;Database=Northwind");
conn.Open(); // Pool A is created

SqlConmection conn = new SqlConnection(
         "Integrated Security=SSPI ; Database=Northwind");
conn.Open(); // Pool B is created (extra spaces in string)
在.NET框架Beta版中,當在調試器中運行時,鏈接池化總是失效了。在調試器外,對調試版和發行版,鏈接池都能正常運作。.NET框架的最終發行版(RTM)取消了這種限制,鏈接池在所有情況下都能運行。 鏈接池被劃分爲了多個特定於事務的池和一個用於目前沒有列在事務中的多個鏈接的池。對於與特定事務上下文相關的線程,將從(包含了與事務建立的鏈接的)合適的池中返回鏈接。這使得使用已建立的鏈接成爲透明過程。

用OLE DB .NET數據供應器池化

OLE DB .NET數據供應器利用OLE DB資源池化的底層服務將鏈接存儲到池中。很多方法可用於配置資源池化:

  • 可以使用鏈接字符串來配置、使能資源池化或使其使失效。
  • 可以使用註冊表。
  • 可以通過程序來配置資源池化。

爲了避開與註冊表相關的部署問題,應避免使用註冊表配置OLE DB資源池化。

關於OLE DB 資源池化的更多細節,見MSDN中“OLE DB程序員參考”一書的第19章:OLE DB服務中的資源池化部分。

用池化對象管理鏈接池化

作爲Windows DNA開發人員,建議你使OLE DB資源池化和/或ODBC鏈接池化失效,並把COM+對象池化用作將數據庫鏈接存儲到池中的技術。這樣做主要出於兩個原因:

  • 池容量和極限可以(在COM+目錄)被明確配置。
  • 性能提高了。池化對象的方法可以成倍的勝過固有池化。

然而,由於SQL Server .NET數據供應器內置地使用池化,所以(在使用此供應器時)你不再需要開發自己的對象池化機制。這樣就可以避免手工事務徵募帶來的複雜性。

如果正在使用OLE DB .NET數據供應器,那麼考慮COM+對象池化以從高級配置和改進的性能中受益。如果你爲此目的開發一個池化對象,那麼必須使用OLE DB資源池化和自動事務徵募失效(例如,通過將“OLE DB Services=-4”包含進鏈接字符串中)。必須在池化對象的實現中處理事務徵募。

監視鏈接池化

要監視應用程序對鏈接池化的應用情況,可以使用隨SQL Server發行的Profiler工具,或隨微軟Windows 2000發行的性能監視器。

要利用SQL Server Profiler 監視鏈接池化,操作如下:

  1. 單擊開始,指向程序,指向Microsoft SQL Server,然後單擊Profiler運行Profiler。
  2. 文件菜單中,指向新建,然後單擊跟蹤
  3. 提供鏈接內容,然後單擊確定
  4. 跟蹤屬性對話框中,單擊事件標籤。
  5. 已選事件類別列表中,確保審覈登錄審覈登出事件顯示在安全審覈下面。
  6. 單擊運行開始跟蹤。在鏈接建立時,將會看到審覈登錄事件;在鏈接關閉時看到審覈登出事件。

要通過性能監視器監視鏈接池化,操作如下:

  1. 單擊開始,指向程序,指向管理工具,然後單擊性能運行性能監視器。
  2. 在圖表背景中右擊,然後單擊增加計數器。
  3. 在性能對象下拉列表框中,單擊SQL Server:通用統計。
  4. 在出現的列表中,單擊用戶鏈接。
  5. 單擊增加,然後單擊關閉。

注意 .NET框架的RTM版本將另外包含一組ADO .NET性能計數器(這些計數器能與性能監視器結合起來使用),這些計數器用於爲SQL Server .NET數據供應器監視並積累鏈接池化狀態。

管理安全性

儘管數據庫鏈接池化提高了應用程序的整體擴展性,這也意味着你不再能夠在數據庫端管理安全性。這是因爲爲了支持鏈接池化,鏈接字符串必須是相同的。如果需要跟蹤每個用戶的數據庫操作,那麼考慮爲每個操作增加一個參數,通過這個參數就可以傳遞用戶身份,手工將用戶活動記入數據庫。

使用Windows 認證

在鏈接到SQL Server時,應當使用Windows認證,因爲它提供了許多優點:

  • 安全性易於管理,因爲使用了單一(Windows)安全模型而不是分散的SQL Server安全模型。
  • 避免了在鏈接字符串中嵌入用戶名和密碼。
  • 用戶名和密碼不是以明文方式在網絡中傳輸的。
  • 通過密碼過期期限,最小長度,多次無效登錄請求後帳號鎖定提高了登錄的安全性。

性能

.NETBeta 2版的性能測試表明,使用Windows認證與使用SQL Server認證相比,要花費更多的時間才能打開池化的數據庫鏈接。然而,儘管Windows認證的成本較高,但與執行一個命令或存儲過程所花費的時間相比,其(引起的)性能損失相對來說並不重要。結果,上面所列出的Windows認證的優點通常會稍微超過性能損失。

同樣,當打開一個池化鏈接時,在.NET框架的RTM版本中,Windows認證與SQL Server認證的差別有望變得更不明顯。

避免在中間層中冒充

Windows認證需要訪問數據庫的Windows帳號。雖然看上去在中間層中使用冒充更符合邏輯,但必須避免這樣做,因爲損害鏈接池化並對應用程序的擴展性產生嚴重影響。

爲了解決這個問題,考慮對有限的Windows帳號(而不是被認證的負責人)實施冒充,每個帳號代表一個特定的角色。

例如,可以考慮下面的方法:

  • 創建兩個Windows帳號,一個用於讀操作,一個用於寫操作(也可以用單獨的帳號映射針對特定應用程序的角色。例如,可以爲互聯網用戶使用一個帳號,而爲內部操作員和/或管理員使用另外的帳號)。
  • 將每個帳號映射到一個SQL Server數據庫角色,然後爲每個角色設置所需的數據庫權限。
  • 在數據訪問層中使用應用程序邏輯確定執行數據庫操作時,哪個Windows帳號需要冒充。

注意 每個帳號必須是同一域或信任域中在Internet信息服務(IIS)和SQL Server中存在的域帳號;也可以是在每臺計算機上創建(具有相同用戶名和密碼)的匹配帳號。

爲網絡庫使用TCP/IP

SQL Server 7.0及其以後版本支持用於所有網絡庫的Windows認證。使用TCP/IP可以獲得配置、性能及擴展性優點。關於使用TCP/IP的更多信息,見本文通過防火牆建立鏈接 一節。

存儲鏈接字符串

有多種方法可存儲鏈接字符串,每種方法具有不同程度的靈活性和安全性。儘管在源代碼中對字符串進行硬編碼提供了最優性能,但文件系統緩存確保了與在文憑系統外部存儲字符串相關的性能損失可被忽略。實際上外部鏈接字符串(允許管理員進行配置)所提供的附加靈活性在任何情況下都是受歡迎的。

選擇存儲鏈接字符串的方法時,首先要考慮的兩個重要因素是配置的安全性與簡易性,其次是性能。

可以選擇將數據庫鏈接字符串存儲在下列位置:

使用Windows認證訪問SQL Server,就可以避免在鏈接字符串存儲用戶名和密碼。如果 安全需求要求更嚴格的方式,那麼就考慮以加密格式存儲鏈接字符串。

對於ASP.NET Web應用程序,以加密格式將鏈接字符串存儲在Web.config文件中是一種安全而可配置的解決方案。

注意,在鏈接字符串中將Persist Security Info命名值設置爲假,就可以阻止利用SqlConnection 或OleDbConnection對象的ConnectionString屬性返回對安全敏感的內容,如密碼。

下面幾個小節討論瞭如何用這些方法存儲鏈接字符串,並說明了相對的優點和缺點。這使你能根據特定的應用程序環境作出相應的的選擇。

使用XML應用程序配置文件

可以使用元素appSettings將數據庫鏈接字符串存儲在應用程序配置文件的定製設置部分。該元素支持任意關鍵字-值對,如下面的代碼片段所示:

<configuration>
 <appSettings>
  <add key="DBConnStr"
     value="server=(local);Integrated Security=SSPI;database=northwind"/>
 </appSettings>
</configuration>

注意:appSettings元素現在在configuration元素下面,並且不能直接出現在system.web下面。

優點

  • 易於部署。通過常規.NET xcopy部署,鏈接字符串隨配置文件一起被部署。
  • 通過程序易於訪問。ConfigurationSettings類的AppSettings屬性使得在運行時讀取數據庫鏈接字符串更爲簡單。
  • 支持動態更新(僅限於ASP.NET)。如果管理員更新了Web.config文件中的鏈接字符串,那麼下次在字符串被訪問時所作出的變化生效,這對一個無狀態的組件來說,就象客戶再次利用組件作出了數據訪問請求一樣。

缺點

安全性。儘管ASP.NET Internet 服務器應用程序編程接口(ISAPI)DLL阻止了客戶直接訪問帶.config擴展名的文件,並且NTFS文件系統權限也用於進一步限制訪問,但你可能仍希望避免以明文方式將這些內容存儲在前端的Web服務器上。要增加安全性,需將鏈接字符串以加密格式存儲在配置文件中。

更多信息

利用System.Configuration.ConfigurationSettings類的AppSettings靜態屬性,可以獲取應用程序的定製設置。如下面的代碼片段所示,此處假定先前示例的定置關鍵字爲DBConnStr。

using System.Configuration;
private string GetDBaseConnectionString()
{
  return ConfigurationSettings.AppSettings["DBConnStr"];
}

關於配置.NET 框架應用程序的更多信息,見http://msdn.microsoft.com/library/en-us/cpguide/html/cpconconfiguringnetframeworkapplications.asp.

使用UDL文件

OLE DB .NET數據供應器支持在它的鏈接字符串中使用統一數據鏈接(UDL)文件名。可以以構建參數的形式將鏈接字符串傳給OleDbConnection對象,或利用對象的ConnectionString屬性設置鏈接字符串。

注意 SQL Server .NET數據供應器不支持在它的鏈接字符串中使用UDL文件。因此,只有使用OLE DB .NET數據供應器,此方法纔有效。

對於OLE DB 供應器,要利用鏈接字符串引用UDL文件,使用“File Name=name.udl.”。

優點

標準方法。你也許已經在用UDL文件進行鏈接字符串的管理了。

缺點

  • 性能。每次打開鏈接時,包含UDLs的鏈接字符串都被讀取並被解析。
  • 安全性。UDL文件以純文本格式存儲。利用NFTS文件權限可以確保這些文件的安全性,但這樣做將引發與使用.config文件相同的問題。
  • SqlClient對象不支持UDL文件。此方法不被 SQL Server .NET數據供應器所支持,而你要用此供應器訪問 SQL Server 7.0及其以後版本。

更多信息

  • 必須確保管理員擁有該文件的讀/寫訪問權限以便進行管理,並且還要確保運行應用程序的身份擁有讀權限。對於ASP.NET Web應用程序,應用程序工作者進程默認是以SYSTEM帳號運行的,但利用機器範圍的配置文件(Machine.config)中的元素可以將其覆蓋掉。利用Web.config文件中的元素,及一個可選的指定帳號,可以進行冒充。
  • 對於Web應用程序,要確保沒有將UDL文件放在虛目錄中,因爲那樣會使該文件可通過網絡下載。
  • 關於這些及其它與安全性相關的ASP.NET特性的更多信息,見http://msdn.microsoft.com/library/en-us/dnbda/html/authaspdotnet.asp.。

使用Windows註冊表

可以利用定製關鍵字將鏈接字符串存儲在Windows註冊表中,但由於部署問題,建議不要使用。

優點

  • 安全性。利用訪問控制列表(ACLs),可以對所選的註冊表關鍵字的訪問進行管理。對更高級別的安全性,考慮對數據進行加密。
  • 通過程序易於訪問。.NET類支持從註冊表中讀取字符串。

缺點

  • 部署。相關的註冊表設置必須同應用程序一起部署,從某種程度上抵消了xcopy部署的優點。

使用定置文件

可以使用定製文件來存儲鏈接字符串,然而這種技術沒有優點,因此並不推薦使用。

優點

  • 沒有

缺點

  • 額外編碼。這種方法需要額外編碼,並迫使你明確處理同時發生的問題。
  • 部署。此文件必須同其它ASP.NET應用程序文件一起拷貝。避免將此文件放在ASP.NET應用程序的目錄或子目錄中,就可以阻止通過網絡對其進行下載。

使用構建參數和COM+目錄

可以將鏈接字符串存儲在COM+目錄中,並利用對象的構造字符串將它自動地傳遞給對象。COM+在初始化對象,提供配置構造字符串後,將立即調用對象的Construct方法。

注意這個方法只用於服務組件。只有管理組件使用了其它服務,如分佈式事務處理支持或對象池化時,才考慮使用此方法。

優點

  • 管理性。利用組件服務MMC插件,管理員可以很方便地配置鏈接字符串。

缺點

  • 安全性。COM+目錄被認爲是一個不安全的存儲區(雖然利用COM+角色你可以限制對它的訪問),並因此不能用於以明文維護鏈接字符串。
  • 部署。COM+目錄中的條目必須隨.NET應用程序一同部署。如果使用了其它企業服務,如分佈式事務或對象池化,那麼將數據庫鏈接字符串存儲在目錄中不會增加部署的額外開銷,因爲要支持其它服務,必須部署COM+目錄。
  • 必須爲組件提供服務。可以只爲所服務的組件使用構造字符串。要使能構造字符串,不能簡單地從ServicedComponent類中派生所需組件類(這將爲組件提供服務)。

更多信息

鏈接使用方式

不管何種.NET數據供應器,你必須總是:

  • 儘可能晚地打開數據庫鏈接。
  • 以儘可能短的時間使用該鏈接。
  • 儘可能快地關閉該鏈接。鏈接直到通過Close或Dispose方法關閉後,它才返回到池中。即使發現它處於崩潰狀態,也應當關閉它。這樣做確保了它能返回池中,並被標記爲無效。對象池週期性地掃描池,以查找已被標記爲無效的對象。

爲確保在方法返回前鏈接已經關閉,考慮使用下面兩個代碼片段中演示的方法。第一個示例使用了finally塊,第二個示例使用了C# using聲明,此聲明確保了對象的Dispose方法被調用。

下面的代碼確保finally塊關閉了鏈接。注意,此方法只用於Visual Basic .NET及C#中,因爲Visual Basic .NET支持結構化例外處理。

public void DoSomeWork()
{
  SqlConnection conn = new SqlConnection(connectionString);
  SqlCommand cmd = new SqlCommand("CommandProc", conn );
  cmd.CommandType = CommandType.StoredProcedure;

  try
  {
    conn.Open();
    cmd.ExecuteNonQuery();
  }
  catch (Exception e)
  {
    // Handle and log error
  }
  finally
  {
    conn.Close();
  }
}

現在的代碼顯示了另外一種方法,此方法使用了C# using聲明。注意,Visual Basic .NET並不支持using聲明,或任何功能相同的對應語句。

public void DoSomeWork()
{
  // using guarantees that Dispose is called on conn, which will
  // close the connection.
  using (SqlConnection conn = new SqlConnection(connectionString))
  {
    SqlCommand cmd = new SqlCommand("CommandProc", conn);
    fcmd.CommandType = CommandType.StoredProcedure;
    conn.Open();
    cmd.ExecuteQuery();
  }
}

此方法也適用於其它對象,如SqlDataReader 或OleDbDataReader,在其它任何對象對當前鏈接進行處理前,這些對象必須被關閉。

錯誤處理

ADO.NET錯誤生成後,將由.NET框架內置的底層結構化異常處理支持所處理。結果,在數據訪問代碼中的錯誤處理方式與應用程序中其它地方的錯誤處理方式完全相同。通過標準的.NET異常處理語法和技術,異常被檢測到並被處理。

本節描述瞭如何開發強壯的數據訪問代碼,並解釋瞭如何處理數據訪問錯誤。本節還提供了與SQL Server .NET數據供應器相關的異常處理詳盡指南。

.NET 異常

.NET數據供應器將特定的數據庫的錯誤狀態轉化爲標準的異常類型,應當在數據訪問代碼中對這些異常進行處理。通過相關的異常對象的屬性,可以獲得特定數據庫的錯誤細節。

所有.NET異常類型最終是從System名稱空間的Exception基類中派生的。.NET數據供應器釋放特定的供應器異常類型。例如,一旦SQL Server 返回一個錯誤狀態時,SQL Server .NET數據供應器釋放SqlException對象。類似的,OLE DB .NET數據供應器釋放 OleDbException類型的異常,此對象包含了由底層OLE DB供應器暴露的細節。

圖3顯示了.NET數據供應器異常的層次結構。注意,OleDbException類是從 ExternalException類派生的ExternalException類是所有COM例外的基類。對象的ErrorCode屬性存儲了OLE DB生成的COM HRESULT。

adonet3.gif
圖3 NET數據供應器層次結構

緩存並處理.NET異常

要處理數據訪問例外狀態,將數據訪問代碼放在try塊中,並在catch塊中利用合適的過濾器捕獲生成的任何例外。例如,當利用SQL Server .NET數據供應器編寫數據訪問代碼時,應當捕獲SqlException類型的異常,如下面的代碼所示:

try
{
  // Data access code
}
catch (SqlException sqlex) // more specific
{
}
catch (Exception ex) // less specific
{
}

如果爲不止一個catch聲明提供了不同的過濾標準,記住,按最特殊類型到最不特殊類型的順序排列它們。通過這種方式,catch塊中最特殊類型將將爲任何給定的類型所執行。

SqlException 類所暴露的屬性包含了例外狀態的細節。其中包括:

  • Message屬性,它包含了用於描述錯誤的文本。
  • Number屬性,它包含唯一標識錯誤類型的錯誤號。
  • State屬性。它包含了關於錯誤啓用狀態的附加信息。它經常用於指示特殊錯誤狀態的某個特定事件。例如,如果單一存儲過程從不止一行中生成同樣的錯誤,那麼本屬性將用於標識某個具體的事件。
  • Errors集合。它包含了SQL Server生成的錯誤的詳細信息。此集合部是包含至少一個SqlError類型的對象。

下面的代碼片段演示瞭如何利用SQL Server .NET數據供應器處理SQL Server 錯誤狀態:

using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;

// Method exposed by a Data Access Layer (DAL) Component
public string GetProductName( int ProductID )
{
  SqlConnection conn = new SqlConnection(
        "server=(local);Integrated Security=SSPI;database=northwind");
  // Enclose all data access code within a try block
  try
  {
    conn.Open();
    SqlCommand cmd = new SqlCommand("LookupProductName", conn );
    cmd.CommandType = CommandType.StoredProcedure;

    cmd.Parameters.Add("@ProductID", ProductID );
    SqlParameter paramPN = 
         cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 );
    paramPN.Direction = ParameterDirection.Output;

    cmd.ExecuteNonQuery();
    // The finally code is executed before the method returns
    return paramPN.Value.ToString();  
  }
  catch (SqlException sqlex)
  {
    // Handle data access exception condition
    // Log specific exception details
    LogException(sqlex);
    // Wrap the current exception in a more relevant
    // outer exception and re-throw the new exception
    throw new DALException(
                  "Unknown ProductID: " + ProductID.ToString(), sqlex );
  }
  catch (Exception ex)
  {
    // Handle generic exception condition . . .
    throw ex;
  }
  finally
  {
    conn.Close(); // Ensures connection is closed
  }
}

// Helper routine that logs SqlException details to the 
// Application event log
private void LogException( SqlException sqlex )
{
  EventLog el = new EventLog();
  el.Source = "CustomAppLog";
  string strMessage;
  strMessage = "Exception Number : " + sqlex.Number + 
               "(" + sqlex.Message + ") has occurred";
  el.WriteEntry( strMessage );

  foreach (SqlError sqle in sqlex.Errors)
  {
    strMessage = "Message: " + sqle.Message +
                 " Number: " + sqle.Number +
                 " Procedure: " + sqle.Procedure +
                 " Server: " + sqle.Server +
                 " Source: " + sqle.Source +
                 " State: " + sqle.State +
                 " Severity: " + sqle.Class +
                 " LineNumber: " + sqle.LineNumber;
    el.WriteEntry( strMessage );
  }
}

在SqlException catch塊中,代碼最初利用LogException幫助函數記錄錯誤狀態,此函數利用foreach聲明枚舉了Errors集合中特定於供應器的細節,並將錯誤細節記錄到錯誤日誌中。 Catch塊中的代碼然後將特定於SQL Server的例外封裝在DALException類型的對象中,這樣做對調用者的GetProductName方法更具有意義。例外處理程序使用關鍵字throw將例外傳回調用者。

更多信息

從存儲過程中生成錯誤

T-SQL提供了一個RAISERROR(注意拼寫)函數。你可用此函數生成定置錯誤,並將錯誤返回客戶。對於ADO.NET客戶,SQL Server .NET數據供應器對這些數據錯誤進行解釋,並把它們轉化爲SqlError對象。

使用RAISERROR函數是簡單地方法是將消息文本作爲第一個參數包括進來,然後指定嚴重及狀態參數,如下面的代碼片段所示:

RAISERROR( 'Unknown Product ID: %s', 16, 1, @ProductID )

在這個例子中,替代參數用於將當前產品ID作爲錯誤消息文本的一部分返回,參數2是消息的嚴重性,參數3是消息狀態。

更多信息

  • 爲了避免對消息文本進行硬編碼,你可以利用sp_addmessage系統存儲過程或SQL Server 企業管理器將你自己的消息增加到sysmessages表中。然後你就可以使用傳遞到RAISERROR函數的ID引用消息了。你所定義的消息Ids必須大於50000,如下代碼片段所示:
  • RAISERROR( 50001, 16, 1, @ProductID )
  • 關於RAISERROR函數的完整細節,請在SQL Server的在線書目中查詢RAISERROR。

正確使用嚴重性等級

仔細選擇錯誤嚴重性等級,並要清楚每個級別造成的衝擊。錯誤嚴重性等級的範圍是0-25,並且它用於指出SQL Server 2000所遇到的問題的類型。在客戶端代碼中,通過在SqlException類的Errors集合中檢查SqlError對象的 Class屬性,你可以獲得錯誤的嚴重性。表1 指出了不同嚴重性等級的意義及所造成的衝擊。

表1.錯誤嚴重性等級--衝擊及意義

嚴重性等級 鏈接已關閉 生成SqlException對象 意義
10及其以下  No No 通知型消息,並不表示犯錯誤狀態。
11-16 No Yes 可由用戶修改的錯誤,例如,使用修改後的輸入數據重試操作。
17-19 No Yes 資源或系統錯誤。
20-25 Yes Yes 致命的系統錯誤(包括硬件錯誤)。客戶鏈接被終止。

控制自動化事務

SQL Server .NET數據供應器對它所遇到的任何嚴重性大於10的錯誤都拋出SqlException對象。當作爲自動化(COM+)事務一部分的組件檢測到SqlException對象後,該組件必須確保它能取消事務。這也許是,也許不是自動化過程,並要依賴該方法是否已經對AutoComplete屬性作出了標記。

關於在自動化事務上下文中處理對象的更多信息,見本文中的確定事務結果一節。

得到通知型消息

10及其以下嚴重性等級用於表示通知型消息,並且不會引發SqlException對象的拋出。

要獲得通知型消息:

  • >創建事件處理程序,並提交給SqlConnection對象所暴露的InfoMessage事件。下面的代碼片段顯示了事件代理。
public delegate void SqlInfoMessageEventHandler( object sender, 
                                                     SqlInfoMessageEventArgs e );

通過傳遞到你的事件處理處理程序中的SqlInfoMessageEventArgs對象,可以得到消息數據。此對象暴露了Errors屬性,該屬性包含一組SqlError對象--每個通知消息一個SqlError對象。下面的代碼片段演示瞭如何註冊用於記錄通知型消息的事件處理程序。

public string GetProductName( int ProductID )
{
  SqlConnection conn = new SqlConnection(
        "server=(local);Integrated Security=SSPI;database=northwind");
  try
  {
    // Register a message event handler
    conn.InfoMessage += new SqlInfoMessageEventHandler( MessageEventHandler );
    conn.Open();
    // Setup command object and execute it
    . . .
  }
  catch (SqlException sqlex)
  {
    // log and handle exception
    . . .
  }
  finally
  {
    conn.Close();
  }
}
// message event handler
void MessageEventHandler( object sender, SqlInfoMessageEventArgs e )
{
  foreach( SqlError sqle in e.Errors )
  {
    // Log SqlError properties
    . . .
  }
}

性能

本節介紹了一些常見的數據訪問方案,對每種方案,以ADO.NET 數據訪問代碼的形式描述了最優性能和擴展性解決方案。在合適的地方,還對性能,功能及開發最作出了比較。本節考慮了下面的功能方案。

  • 獲取多行. 獲取一個結果集,並在得到的行中重複。
  • 獲取一行. 獲取具有指定關鍵字的一行。
  • 獲取一項. 從指定的行中得到一項。
  • 確定某項數據的存在性. 檢查具有特定關鍵字的一行是否存在。這是單項查找方案的一種變體,這裏返回一個簡單的布爾值就足夠了。

獲取多行

在這個方案中,你要獲取一組表格化數據,並在得到的行中重複執行某個操作。例如你得到了一組數據,並以非鏈接的方式處理,然後(可能通過Web服務)將它作爲XML文檔傳遞給客戶應用程序。可選的,你也可以以HTML表的形式將這些數據顯示出來。

爲了幫助確定最合適的數據訪問方法,考慮你是否需要(非鏈接)DataSet 對象的附加靈活性,還是隻需要SqlDataReader對象提供的原有性能,這些性能非常適合於B2C Web應用程序的數據表示。圖4顯示了這兩種基本場景。

注意用於填充DataSet的SqlDataAdapter利用SqlDataReader方法數據。

adonet4.gif
圖4 多行數據訪問方案

方法比較

當從數據源中獲取多行時,你可以使用下面的方法:

  • 使用SqlDataAdapter對象生成DataSet 或 DataTabl對象。
  • 利用SqlDataReader對象提供只讀的只向前的數據流。
  • 利用XmlReader對象提供只讀的只向前的XML數據流。

SqlDataReader 與 DataSet/DataTable間的選擇本質上是性能與功能間的選擇。SqlDataReader 提供了最優性能,而DataSet提供了額外的功能與靈活性。

數據綁定

所有這三個對象都可以作爲數據綁定控件的數據源。而DataSet 和 DataTable 可作爲更廣範圍控件的數據源。這是因爲DataSet 和 DataTable 實現了(生成Ilist接口)IlistSource接口,而SqlDataReader 實現了Ienumerable接口。許多能進行數據綁定的WinForm控件需要實現了Ilist接口的數據源。

這種不同是因爲爲每種對象類型設計的場景類型不同。DataSet (它包含 DataTable)是一個豐富的、非鏈接結構,它適合於Web和桌面(WinForm)應用程序。另一方面,數據閱讀器已經爲Web應用程序進行了優化,這種應用程序需要優化的、只能向前的數據訪問。

檢查將要綁定到的特定控件類型的數據源需求。

在應用程序層間傳遞數據

DataSet提供了可作爲XML被任意操縱數據的關係圖,並允許數據的非鏈接緩存拷貝在應用程序層與組件間傳遞。然而,SqlDataReader提供了更優化的性能,因爲它避免了與創建DataSet相關的性能及內存開銷。記住,DataSet對象的創建將導致多個子對象--包括DataTable, DataRow 和DataColumn--及作爲這些子對象容器的集合對象的創建。

使用DataSet

使用SqlDataAdapter填充的DataSet對象,當:

  • 你需要非鏈接的駐留內存的緩存數據,以便你能將它傳遞到其它組件或應用程序中的其它層。
  • 你需要內存中的數據關係圖以執行XML或非XML操作。
  • 你正在使用的數據來自多個數據源,如多個數據庫、表或文件。
  • 你希望更新獲得的一些或所有行,並希望利用SqlDataAdapter的批更新功能。
  • 你要對控件綁定數據,而此控件需要支持IList接口的數據源。

更多信息

如果使用SqlDataAdapter生成DataSet 或 DataTable,需注意:

  • 不必明確打開或關閉數據庫鏈接。SqlDataAdapter Fill方法打開數據庫鏈接,並在此方法返回前關閉該鏈接。如果鏈接原來已經打開,那麼此方法仍使鏈接處於打開狀態。
  • 如果出於其它目的需要鏈接,那麼考慮在調用Fill方法前打開鏈接。這樣你就可以避免不必要的打開/關閉操作,提高性能。
  • 儘管能重複使用同一SqlCommand對象多執行同樣的命令,但不要重複使用此對象執行不同的命令。
  • 關於如何利用SqlDataAdapter對象填充DataSet 或 DataTable對象的代碼示例,見附錄中的如何利用SqlDataAdapter 對象獲得多行

使用SqlDataReader

些劣情況,可以使用通過調用 SqlCommand 對象的ExecuteReader方法得到的SqlDataReader對象:

  • 正在處理大量數據時--太多了而不能在單個緩衝區內維護。
  • 希望減少應用程序在內存中的印跡。
  • 希望避免與DataSet對象創建相關的開銷。
  • 希望對某控件執行數據綁定操作,而此控件支持實現了IEnumerable接口的數據源。
  • 希望流水線化數據訪問,並對其優化。
  • 正在讀取包含二進制大對象(BLOB)列的行。你可以使用SqlDataReader對象以可管理的大塊爲單位從數據庫中將BLOB數據拉出來,而不是一次性地將所有數據提取出來。關於處理BLOB數據的更多細節,見本文處理BLOBs 一節。

更多信息

如果使用SqlDataReader對象,請注意:

  • 在數據閱讀器活動期間,底層的數據庫鏈接保持打開,並不能用於其它任何目的。儘可能早地對SqlDataReader對象調用Close方法。
  • 每個鏈接只能有一個數據閱讀器。
  • 通過向ExecuteReader方法傳遞CommandBehavior.CloseConnection枚舉值,可以在使用完數據閱讀器後,明確地關閉鏈接;或者,將鏈接生命週期綁定到SqlDataReader對象。這預示着當SqlDataReader對象關閉時,鏈接也將關閉。
  • 在利用閱讀器訪問數據時,如果你知道列的底層數據類型,那麼就應使用類型化存取器方法(如GetInt32 和 GetString),這是因爲在讀取列數據時,這些方法減少了讀取列數據所需的類型轉換量。
  • 爲避免將不必要的數據從服務器發送到客戶端,如果你要關閉閱讀器並拋棄所有保留的結果,那麼在對閱讀器調用Close方法前調用命令對象的Cancel方法。Cancel方法確保了服務器的結果被拋棄,而不會被髮送到客戶端。相反,對數據閱讀器調用Close方法會使閱讀器不必要地提取出保留的結果,以清空數據流。
  • 如果要得到從存儲過程返回的輸出值或返回值,並且你在利用SqlCommand對象的ExecuteReader方法,那麼在得到輸出或返回值前,必須對閱讀器調用Close方法。
  • 關於演示如何利用SqlDataReader對象的代碼示例,附錄中的如何利用SqlDataReader對象獲取多行數據

使用XmlReader

下列情況下,使用通過調用SqlCommand對象的ExecuteXmlReader方法得到的XmlReader對象:

  • 希望將得到的數據作爲XML 處理,但不希望引發因創建DataSet對象而造成的額外性能開銷,並且不需要數據的非鏈接緩存。
  • 希望利用SQL Server FOR XML 語法的功能,這種語法允許以靈活的方式從數據庫中得到XML片段(即,不帶根元素的XML文檔)。例如,這種方法使你能夠精確指定元素名,是使用元素還是使用以屬性爲核心的圖解,圖解是否隨XML數據一起被返回,等等。

更多信息

如果使用XmlReader,請注意:

  • 在從XmlReader對象中讀取數據時,鏈接必須保持打開。SqlCommand對象的 ExecuteXmlReader方法目前不支持CommandBehavior.CloseConnection枚舉值,因此在使用完閱讀器後必須明確關閉鏈接。
  • 對於如何使用XmlReader對象的代碼示例,見附錄中的如何利用 XmlReader獲取多行數據

獲取單行數據

在這種場景中,將從數據源中獲取包含一組指定列的單行數據。例如,你得到一個客戶ID,並希望查找與客戶相關的細節;或得到一個產品ID,並希望得到產品信息。

方法比較

如果要對從數據源中得到的一行數據執行綁定操作,可以用SqlDataAdapter對象填充DataSet 或DataTable對象,其方式與在先前討論過的獲取多行數據及重複場景中描述的方式相同。然而,除非特別需要DataSet 或DataTable對象的功能,否則應當避免創建這些對象。

如果需要獲取單行數據,那麼請使用下面的一種方法:

這兩種方法都避免了在服務器端創建結果集,在客戶端創建DataSet對象的不必要額外開銷。每種方法的相對性能要依賴於強度等級及數據庫鏈接池化是否被使能。當數據庫鏈接池化使能時,性能測試表明存儲過程方法在高強度環境下(同時存在200多鏈接)其性能比SqlDataReader方法高近30%。

使用存儲過程輸出參數

如下情況中使用存儲過程輸出參數:

  • 要從鏈接池化使能的多層Web應用程序中獲得一行數據。

更多信息

使用SqlDataReader對象

下列情況,需使用SqlDataReader對象:

  • 除了數據值,還需要元數據時。可以利用數據閱讀器的GetSchemaTable方法獲取列元數據。
  • 未使用鏈接池化時。在鏈接池化無效時,SqlDataReader對象在所有強度環境下都是好方式;性能測試表明,在200瀏覽器鏈接時,此方法比存儲過程方法在性能上要高約20%。

更多信息

  • 如果知道查詢結果只需返回一行,那麼在調用SqlCommand對象的ExecuteReader 方法時,使用CommandBehavior.SingleRow枚舉值。一些供應器,如OLE DB .NET數據供應器,用此技巧來優化性能。例如,供應器使用IRow接口(如果此接口存在)而不是代價更高的IRowset接口。這個參數對SQL Server .NET數據供應器沒有影響。
  • 在使用SqlDataReader對象時,總是應當通過SqlDataReader對象的類型化存取器方法,如GetString 和GetDecimal,獲得輸出參數。這樣做就避免了不必要的類型轉換。
  • 關於如何使用SqlDataReader對象獲取單行數據的代碼示例,見附錄中的如何使用 SqlDataReader對象獲取單行數據

獲取單項數據

在本場景中,要獲取單項數據。例如,提供了產品ID後,希望查詢單一的產品名;或,給出了客戶名後,希望查詢客戶的信用等級。在這種場景中,爲得到單項數據,通常不希望引發創建DataSet 對象或甚至是 DataTable對象的額外開銷。

也許只希望檢查數據庫中是否存在特定的行。例如,當新用戶在網站註冊時,需要檢查所選用戶名是否已經存在。這是單項數據查詢中很特殊的例子,但在此例子中,返回一個簡單的布爾返回值就足夠了。

方法比較

當從數據源獲取單項數據時,考慮下面的方法:

  • 同存儲過程一起使用SqlCommand對象的ExecuteScalar方法。
  • 使用存儲過程輸出或返回參數。
  • 使用SqlDataReader對象。

ExecuteScalar方法直接返回數據項,因爲它是爲只返回單個值的查詢設計的,與存儲過程輸出參數和SqlDataReader方法相比,它需要更少的代碼。

從性能方面來說,應當使用存儲過程輸出或返回參數,因爲測試結果表明,存儲過程方法在從低強度到高強度環境中(從同時不到100瀏覽器鏈接到200瀏覽器鏈接)提供了一致的性能。

更多信息

通過防火牆建立鏈接

需要經常配置互聯網應用程序以使它能夠通過防火牆鏈接到SQL Server。例如,許多Web應用程序及防火牆的主要結構組件是周邊網絡(也被稱爲DMZ或非軍事化區),它們用於隔離高端Web服務器與內部網絡。

通過防火牆鏈接到SQL Server時,需要對防火牆,客戶和服務器進行明確配置。SQL Server提供了客戶網絡應用程序和服務器網絡應用程序以幫助進行配置。

選擇網絡庫

當通過防火牆建立鏈接時,使用SQL Server TCP/IP網絡庫來簡化配置,這是SQL Server2000安裝的默認選項。如果使用先前版本的SQL Server,那麼分別利用客戶端網絡應用程序和服務器端網絡應用程序檢查TCP/IP是否在客戶和服務器端已經被配置爲默認的網絡庫。

除了配置優點,使用TCP/IP庫還意味着:

  • 受益於大宗數據的改進性能和增加的擴展性。
  • 避免與指定管道相關的附加安全信息。

必須在客戶和服務器計算機上配置TCP/IP,因爲大多數防火牆限制了流量通過的端口,所以必須仔細考慮SQL Server所使用的端口號。

配置服務器

SQL Server的默認實例監聽1433端口。然而,SQL Server 2000的指定實例在它們首次開啓時,動態地分配端口號。網絡管理員有希望在防火牆打開一定範圍的端口;因此,當隨防火牆使用SQL Server的指定實例時,利用服務網絡應用程序對實例進行配置,使它監聽特定的端口。然後管理員對防火牆進行配置,以使防火牆允許流量到達特定的IP地址及服務器實例所監聽的端口。

注意,客戶端網絡庫所使用的源端口號在1024-5000間動態分配。這是TCP/IP客戶端應用程序的標準作法,但這意味着防火牆必須允許途經此範圍的任何端口流量能夠通過。關於SQL Server所使用的端口的更多信息,在微軟產品支持服務網站上,參見INF: P 通過防火牆對SQL Server進行通訊所需的TCP端口 。。

動態查找指定實例

如果改變了SQL Server所監聽的默認端口,那麼就要對客戶端進行配置,以使它鏈接到此端口。更多細節,見本文中的配置客戶端 一節。

如果改變了SQL Server 2000默認實例的端口號,那麼不修改客戶端將導致鏈接錯誤。如果存在多個SQL Server 實例,最新版本的MDAC數據訪問堆棧(2.6)將進行動態查找,並利用用戶數據報協議(UDP)協商(通過UDP端口1434)對指定實例進行定位。儘管這種方法在開發環境下也許有效,但在現在環境中卻不大可能正常工作,因爲典型發問下防火牆阻止UDP協商流量的通過。

爲了避開這種情況,總是將客戶端配置爲鏈接到已配置好的目的端口號。

配置客戶端

應當對客戶端進行配置以利用TCP/IP網絡庫鏈接到SQL Server,並且也應當確保客戶端庫使用了正確的目的端口號。

使用TCP/IP 網絡庫

利用SQL Server客戶端網絡庫,可以對客戶端進行配置。在某些安裝版本中,可能沒有將這個應用程序安裝到客戶端(如Web服務器)。在這種情況下,可以按如下方式之一解決:

  • 利用通過鏈接字符串提供的“Network Library=dbmssocn”名稱-值對指定網絡庫。字符串dbmssocn用於標識TCP/IP(套接字)庫。

注意 在使用SQL Server .NET數據供應器時,網絡庫的默認設置是使用“dbmssocn”。

指定端口

如果SQL Server的實例被配置爲監聽默認的1433以外的其它端口,那麼通過以下操作,就能指定鏈接到的端口號:

  • 使用客戶端網絡應用程序
  • 利用提供給鏈接字符串的“Server”或“Data Source”名稱-值對來指定端口號。要按下面的格式使用字符串:
  • "Data Source=ServerName,PortNumber"

注意 ServerName可以是IP地址,或域名系統(DNS)名,爲了優化性能,可以使用IP 地址以避免DNS 查詢。

分佈式事務處理

如果開發了使用COM+分佈式事務處理和微軟分佈式事務處理協調器(DTC)服務的服務組件,那麼就需要對防火牆進行配置,以允許DTC流在不同DTC實例間及DTC與資源管理器(例如SQL Server)間流動。

有關爲DTC開放端口的更多信息,見INFO:爲通過防火牆工作,配置微軟分佈式事務處理協調器 (DTC)

處理BLOBs

目前,很多應用程序除了處理許多傳統的字符串和數字型數據外,還要處理象圖形或聲音--甚至複雜的數據格式,如視頻格式的數據。圖形、聲音與視頻的數據格式類型不一。然而從存儲角度來說,它們都可被視爲二進制數據塊,通常將其稱爲BLOBs(二進制大對象)。

SQL Server提供了binary, varbinary, 和image數據格式來存儲BLOBs。不考慮名稱,BLOB數據也可被稱爲基於文件的數據。例如,你可能要存儲與特定行相關的二進制長註釋字段。SQL Server爲此目的提供了ntext 和text數據類型。

通常,對於小於8KB的二進制數據,使用varbinary數據類型。對於超過此大小的二進制數據,使用image 。表2 彙集了每個數據類型的主要特性。

表2 數據類型特性

數據類型 大小 描述
binary 範圍從1-8KB。存儲大小是指定大小加4字節。 固定長度的二進制數據
varbinary 範圍從1-8KB。存儲大小是所提供數據的實際大小加4字節。 可變長度的二進制數據
image 從0-2GB大小的可變長度二進制數據 大容量可變長度二進制數據
text 從0-2GB大小的可變長度數據 字符型數據
ntext 從0-2GB大小的可變長度數據 寬字節字符數據

何處存儲BLOB數據

SQL Server 7.0及其以後版本已經提高了存儲在數據庫中的BLOB數據的使用性能。這種情況的一個原因是數據庫頁面大小已經增加到了8KB。結果,小於8KB的文本或圖象數據不必再存儲在頁面單獨的二進制樹結構中,而是能被存儲在單行中。這意味着讀取和寫入text, ntext, 或 image數據能象讀取或寫入字符或二進制字符串那樣快。超出8KB後,將在行中建立一個指針,數據本身存儲在獨立數據頁面的二進制樹結構中,這不可避免會對性能產生衝擊。

關於迫使text, ntext, 和 image數據存儲在單行中的更多信息,見SQL Server在線圖書中的使用text和image數據主題。

一個經常使用的處理BLOB數據的可選方法是,將BLOB數據存儲在文件系統中,並在數據庫列中存儲一個指針(通常是一個統一資源定位器--URL鏈接)以引用正確的文件。對於SQL Server 7.0以前的版本,將BLOB數據存儲在數據庫外的文件系統中,可以提高性能。

然而,SQL Server 2000改進了BLOB支持,以及ADO.NET對讀取和寫入BLOB數據的支持,使在數據庫中存儲BLOB數據成爲一種可行的方法。

在數據庫中存儲BLOB 數據的優點

將BLOB數據存儲在數據庫中,帶來了很多優點:

  • 易於保持BLOB數據與行中其它項數據的同步。
  • BLOB數據由數據庫所支持,擁有單一的存儲流,易於管理。
  • 通過SQL Server 2000所支持的XML可以訪問BLOB數據,這將在XML流中返回64位編碼描述的數據。
  • 對包含了固定或可變長度的字符(包括寬字符)數據的列可以執行SQL Server全文本搜索(FTS)操作。也可以對包含在image字段中的已格式化的基於文本的數據--Word 或 Excel文檔--執行FTS操作。

將BLOB數據寫入到數據庫中

下面的代碼演示瞭如何利用ADO.NET將從某個文件獲得的二進制數據寫入SQL Server image字段中。

public void StorePicture( string filename )
{
  // Read the file into a byte array
  FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read );
  byte[] imageData = new Byte[fs.Length];
  fs.Read( imageData, 0, (int)fs.Length );
  fs.Close();

  SqlConnection conn = new SqlConnection("");
  SqlCommand cmd = new SqlCommand("StorePicture", conn);
  cmd.CommandType = CommandType.StoredProcedure;
  cmd.Parameters.Add("@filename", filename );
  cmd.Parameters["@filename"].Direction = ParameterDirection.Input;
  cmd.Parameters.Add("@blobdata", SqlDbType.Image);
  cmd.Parameters["@blobdata"].Direction = ParameterDirection.Input;
  // Store the byte array within the image field
  cmd.Parameters["@blobdata"].Value = imageData;
  try
  {
    conn.Open();
    cmd.ExecuteNonQuery();
  }
  catch
  {
    throw;
  }
  finally
  {
    conn.Close();
  }
}

從數據庫中讀取BLOB數據

在通過ExecuteReader方法創建SqlDataReader對象以讀取包含BLOB數據的行時,需使用CommandBehavior.SequentialAccess枚舉值。如果沒有此枚舉值,閱讀器一次只從服務器中向客戶端發送一行數據。如果行包含了BOLB數據,這預示着要佔用大量內存。通過利用枚舉值,就獲得了更好的控制權,因爲BLOB數據只在被引用時才被髮出(例如,利用GetBytes方法,可以控制讀取的字節數)。這在下面的代碼片段中進行了演示。

// Assume previously established command and connection
// The command SELECTs the IMAGE column from the table
conn.Open();
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
reader.Read();
// Get size of image data - pass null as the byte array parameter
long bytesize = reader.GetBytes(0, 0, null, 0, 0);
// Allocate byte array to hold image data
byte[] imageData = new byte[bytesize];
long bytesread = 0;
int curpos = 0;
while (bytesread < bytesize)
{
  // chunkSize is an arbitrary application defined value 
  bytesread += reader.GetBytes(0, curpos, imageData, curpos, chunkSize);
  curpos += chunkSize;
}
// byte array 'imageData' now contains BLOB from database

注意使用CommandBehavior.SequentialAccess需要以嚴格的順序訪問列數據。例如,如果BLOB數據存在於第3列,並且還需要從第1,2列中讀取數據,那麼在讀取第3列前必須先讀取第1,2列。

事務處理

實際上所有用於更新數據源的面向商業的應用程序都需要事務處理支持。通過提供四個基本擔保,即衆所周知的首字縮寫ACID:可分性,一致性,分離性,和耐久性,事務處理將用於確保包含在一個或多個數據源中的系統的完整性。

例如,考慮一個基於Web的零售應用程序,它用於處理購買訂單。每個訂單需要3個完全不同操作,這些操作涉及到3個數據庫更新:

  • 庫存水準必須減少所訂購的數量。
  • 所購買的量必須記入客戶的信用等級。
  • 新訂單必須增加到數據庫中。

這三個不同的操作作爲一個單元並自動執行是至關重要的。三個操作必須全部成功,或都不成功--任何一個操作出現誤差都將破壞數據完整性。事務處理提供了這種完整性及其它保證。

要進一步瞭解事務處理過程的基本原則,見http://msdn.microsoft.com/library/en-us/cpguide/html/cpcontransactionprocessingfundamentals.asp

可以採用很多方法將事務管理合併到數據訪問代碼中。每種方法適合下面兩種基本編程模型之一。

  • 手工事務處理。可以直接在組件代碼或存儲過程中分別編寫利用ADO.NET 或 Transact-SQL事務處理支持特性的代碼。
  • 自動化(COM+)事務處理。可以向.NET類中增加聲明在運行時指定對象事務處理需要的屬性。這種模型使你能方便地配置多個組件以使它們在同一事務處理內運行。

儘管自動化事務處理模型極大地簡化了分佈式事務處理過程,但兩種模型都用於執行本地事務處理(即對單個資源管理器如SQL Server 2000執行的事務處理)或分佈式事務處理(即,對位於遠程計算機上的多個資源管理執行的事務處理)。

你也許會試圖利用自動化(COM+)事務處理來從易於編程的模型中獲益。在有多個組件執行數據庫更新的系統中,這種優點更明顯。然而,在很多情況下,應當避免這種事務處理模型所帶來的額外開銷和性能損失。

本節將指導你根據特定的應用程序環境選擇最合適的模型。

選擇事務處理模型

在選擇事務處理模型前,首先應當考慮是否真正需要事務處理。事務處理是服務器應用程序使用的最昂貴的資源,在不必要使用的地方,它們降低了擴展性。考慮下面用於管理事務處理使用的準則:

  • 只在需要跨一組操作獲取鎖並需要加強ACID規則時才執行事務處理。
  • 儘可能短地保持事務處理,以最小化維持數據庫鎖的時間。
  • 永遠不要將客戶放到事務處理生命週期的控制之中。
  • 不要爲單個SQL語句使用事務處理。SQL Server自動把每個語句作爲單個事務處理執行。

自動化事務處理與手工事務處理的對比

儘管編程模型已經對自動化事務處理進行了簡化,特別是在多個組件執行數據庫更新時,但本地事務處理總是相當快,因爲它們不需要與微軟DTC交互。即使你對單個本地資源管理器(如SQL Server)使用自動化事務處理,也是這種情況(儘管性能損失減少了),因爲手式本地事務處理避免了所有不必要的與DTC的進程間通信。

對於下面的情況,需使用手工事務處理:

  • 對單個數據庫執行事務處理。

對於下列情況,則宜使用自動事務處理:

  • 需要將單個事務處理擴展到多個遠程數據庫時。
  • 需要單個事務處理擁有多個資源管理器(如數據庫和Windows 2000消息隊列(被稱爲MSMQ)資源管理器)時。

注意 避免混用事務處理模型。最好只使用其中一個。

在性能足夠好的應用程序環境中,(甚至對於單個數據庫)選擇自動化事務處理以簡化編程模型,這種做法是合理的。自動化事務處理使多個組件能很容易地執行現一事務處理中的多個操作。

使用手工事務處理

對於手工事務處理,可以直接在組件代碼或存儲過程中分別編寫使用ADO.NET 或 Transact-SQL事務處理支持特性的代碼。多數情況下,應選擇在存儲過程中控制事務處理,因爲這種方法提供了更高的封裝性,並且在性能方面,此方法與利用ADO.NET 代碼執行事務處理兼容。

利用ADO.NET執行手工事務處理

ADO.NET支持事務處理對象,利用此對象可以開始新事務處理過程,並明確控制事務處理是否執行還是回滾。事務處理對象與單個數據庫鏈接相關,可以通過鏈接對象的BeginTransaction方法獲得。調用此方法並不是暗示,接下來的命令是在事務處理上下文中發出的。必須通過設置命令的Transaction屬性,明確地將每個命令與事務處理關聯起來。可以將多個命令對象與事務處理對象關聯,因此在單個事務處理中就針對單個數據庫把多個操作進行分組。

關於使用ADO.NET事務處理代碼的示例,見附錄中如何編碼ADO.NET手工事務處理

更多信息

  • ADO.NET手工事務處理的默認分離級別是讀聯鎖,這意味着在讀取數據時,數據庫控制共享鎖,但在事務處理結束前,數據可以被修改。這種情況潛在地會產生不可重複的讀取或虛數據。通過將事務處理對象的IsolationLevel屬性設置爲IsolationLevel枚舉類型所定義的一個枚舉值,就可改變分離級別。
  • 必須仔細爲事務處理選擇合適的分離級別。其折衷是數據一致性與性能的比例。最高的分離等級(被序列化了)提供了絕對的數據一致性,但是以系統整體吞吐量爲代價。較低的分離等級會使應用程序更易於擴展,但同時增加了因數據不一致而導致出錯的可能性。對多數時間讀取數據、極少寫入數據的系統來說,較低的分離等級是合適的。
  • 關於選擇恰當事務處理級別極有價值的信息,見微軟出版社名爲Inside SQL Server 2000的書,作者Kalen Delaney。

利用存儲過程執行手工事務處理

也可以在存儲過程中使用Transact-SQL語句直接控制手工事務處理。例如,可以利用包含了Transact-SQL事務處理語句(如BEGIN TRANSACTION、END TRANSACTION及ROLLBACK TRANSACTION)的存儲過程執行事務處理。

更多信息

  • 如果需要,可以在存儲過程中使用SET TRANSACTION ISOLATION LEVEL語句控制事務處理的分離等級。讀聯鎖是SQL Server的默認設置。關於SQL Server分離級別的更多信息,見SQL Server在線書目“訪問和修改關係數據”一節中的分離級別部分。
  • 關於演示如何利用Transact-SQL事務處理語句執行事務更新的代碼示例,見附錄中的如何利用Transact-SQL執行事務處理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章