亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

.NET 數據訪問架構指南

2019-11-17 04:37:16
字體:
來源:轉載
供稿:網友
概述:本文提供了在多層.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數據訪問模型引入的基本變化。

  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的相關功能。

  .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對象的所有實現都必須支持這些接口。

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

  關于ADO到ADO.NET的演化,見MSDN雜志2000年11月期的文章“ADO+簡介:用于微軟.NET框架的數據訪問服務”。 QQread.com 推出各大專業服務器評測 linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 存儲過程與直接SQL的比較

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

  使用存儲過程而非SQL語句的原因是:
  • 存儲過程通常會使性能增加,因為數據庫可以優化過程使用的數據訪問計劃,并對其進行緩存以備將來重用。

  • 在數據庫中,存儲過程可分別得到保護??蛻艨梢员唤o予執行某個存儲過程的權限,但無權處理底層的表。

  • 存儲過程將導致維護簡單,因為在一個已部署組件內,修改存儲過程通常要比修改硬編碼的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鏈接池經常被稱為會話或資源池。

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

  • OLE DB .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)以幫助查找鏈接泄漏。

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

  更多信息

  在使用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的更多信息,見本文通過防火墻建立鏈接一節。

  存儲鏈接字符串

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

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

  可以選擇將數據庫鏈接字符串存儲在下列位置:
  • 應用程序配置文件 例如用于asp.net Web應用程序的Web.config文件。


  • 通用數據鏈接文件(UDL) (只被OLE DB .NET 數據供給器所支持)

  • Windows 注冊表

  • 定制文件

  • COM+ 目錄,通過過使用構造字符串(只用于服務組件)
  使用Windows認證訪問SQL Server,就可以避免在鏈接字符串存儲用戶名和密碼。假如 安全需求要求更嚴格的方式,那么就考慮以加密格式存儲鏈接字符串。

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

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

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

  使用XML應用程序配置文件

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

value="server=(local);Integrated Security=SSPI;database=northwind"/>

  注重: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"];
} QQread.com 推出各大專業服務器評測 Linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 使用UDL文件

  OLE DB .NET數據供給器支持在它的鏈接字符串中使用統一數據鏈接(UDL)文件名??梢砸詷嫿▍档男问綄㈡溄幼址畟鹘oOleDbConnection對象,或利用對象的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文件放在虛目錄中,因為那樣會使該文件可通過網絡下載。

  使用Windows注冊表

  可以利用定制要害字將鏈接字符串存儲在Windows注冊表中,但由于部署問題,建議不要使用。

  優點
  • 安全性。利用訪問控制列表(ACLs),可以對所選的注冊表要害字的訪問進行治理。對更高級別的安全性,考慮對數據進行加密。

  • 通過程序易于訪問。.NET類支持從注冊表中讀取字符串。
  缺點

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

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

  優點
  • 沒有
  缺點
  • 額外編碼。這種方法需要額外編碼,并迫使你明確處理同時發生的問題。

  • 部署。此文件必須同其它ASP.NET應用程序文件一起拷貝。避免將此文件放在ASP.NET應用程序的目錄或子目錄中,就可以阻止通過網絡對其進行下載。
  使用構建參數和COM+目錄

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

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

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

    缺點
  • 安全性。COM+目錄被認為是一個不安全的存儲區(雖然利用COM+角色你可以限制對它的訪問),并因此不能用于以明文維護鏈接字符串。

  • 部署。COM+目錄中的條目必須隨.NET應用程序一同部署。假如使用了其它企業服務,如分布式事務或對象池化,那么將數據庫鏈接字符串存儲在目錄中不會增加部署的額外開銷,因為要支持其它服務,必須部署COM+目錄。

  • 必須為組件提供服務??梢灾粸樗盏慕M件使用構造字符串。要使能構造字符串,不能簡單地從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。 


  緩存并處理.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將例外傳回調用者。 QQread.com 推出各大專業服務器評測 Linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 從存儲過程中生成錯誤

  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文檔傳遞給客戶應用程序??蛇x的,你也可以以Html表的形式將這些數據顯示出來。

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

  注重用于填充DataSet的SqlDataAdapter利用SqlDataReader方法數據。 


  方法比較

  當從數據源中獲取多行時,你可以使用下面的方法:
  • 使用SqlDataAdapter對象生成DataSet 或 DataTabl對象。

  • 利用SqlDataReader對象提供只讀的只向前的數據流。

  • 利用XmlReader對象提供只讀的只向前的XML數據流。
  SqlDataReader 與 DataSet/DataTable間的選擇本質上是性能與功能間的選擇。SqlDataReader 提供了最優性能,而DataSet提供了額外的功能與靈活性。 QQread.com 推出各大專業服務器評測 Linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 數據綁定

  所有這三個對象都可以作為數據綁定控件的數據源。而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對象多執行同樣的命令,但不要重復使用此對象執行不同的命令。
  使用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方法。
  使用XmlReader

  下列情況下,使用通過調用SqlCommand對象的ExecuteXmlReader方法得到的XmlReader對象:
  • 希望將得到的數據作為XML 處理,但不希望引發因創建DataSet對象而造成的額外性能開銷,并且不需要數據的非鏈接緩存。

  • 希望利用SQL Server FOR XML 語法的功能,這種語法答應以靈活的方式從數據庫中得到XML片段(即,不帶根元素的XML文檔)。例如,這種方法使你能夠精確指定元素名,是使用元素還是使用以屬性為核心的圖解,圖解是否隨XML數據一起被返回,等等。
  更多信息

  假如使用XmlReader,請注重:
  • 在從XmlReader對象中讀取數據時,鏈接必須保持打開。SqlCommand對象的 ExecuteXmlReader方法目前不支持CommandBehavior.CloseConnection枚舉值,因此在使用完閱讀器后必須明確關閉鏈接。
QQRead.com 推出數據恢復指南教程 數據恢復指南教程 數據恢復故障解析 常用數據恢復方案 硬盤數據恢復教程 數據保護方法 數據恢復軟件 專業數據恢復服務指南 獲取單行數據

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

  方法比較

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

  假如需要獲取單行數據,那么請使用下面的一種方法:
  • 使用存儲過程輸出參數.

  • 使用SqlDataReader對象.
  這兩種方法都避免了在服務器端創建結果集,在客戶端創建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,獲得輸出參數。這樣做就避免了不必要的類型轉換。
  獲取單項數據

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

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

  方法比較

  當從數據源獲取單項數據時,考慮下面的方法:
  • 同存儲過程一起使用SqlCommand對象的ExecuteScalar方法。

  • 使用存儲過程輸出或返回參數。

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

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

  更多信息
  • 假如通過ExecuteQuery方法所執行的查詢返回多列和/或行,那么此方法只返回第一行的第一列。
  通過防火墻建立鏈接

  需要經常配置互聯網應用程序以使它能夠通過防火墻鏈接到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端口。 QQread.com 推出各大專業服務器評測 Linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 動態查找指定實例

  假如改變了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”。
  • 在客戶端機器上修改注冊表,把TCP/IP設置為默認庫。
  指定端口

  假如SQL Server的實例被配置為監聽默認的1433以外的其它端口,那么通過以下操作,就能指定鏈接到的端口號:
  • 使用客戶端網絡應用程序

  • 利用提供給鏈接字符串的“Server”或“Data Source”名稱-值對來指定端口號。要按下面的格式使用字符串:

  • "Data Source=ServerName,PortNumber"
  注重 ServerName可以是IP地址,或域名系統(DNS)名,為了優化性能,可以使用IP 地址以避免DNS 查詢。

  分布式事務處理

  假如開發了使用COM+分布式事務處理和微軟分布式事務處理協調器(DTC)服務的服務組件,那么就需要對防火墻進行配置,以答應DTC流在不同DTC實例間及DTC與資源治理器(例如SQL Server)間流動。

  處理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字節??勺冮L度的二進制數據 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字段中的已格式化的基于文本的數據--WordExcel文檔--執行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個數據庫更新:
  • 庫存水準必須減少所訂購的數量。

  • 所購買的量必須記入客戶的信用等級。

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

  可以采用很多方法將事務治理合并到數據訪問代碼中。每種方法適合下面兩種基本編程模型之一。
  • 手工事務處理??梢灾苯釉诮M件代碼或存儲過程中分別編寫利用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手工事務處理的默認分離級別是讀聯鎖,這意味著在讀取數據時,數據庫控制共享鎖,但在事務處理結束前,數據可以被修改。這種情況潛在地會產生不可重復的讀取或虛數據。通過將事務處理對象的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在線書目“訪問和修改關系數據”一節中的分離級別部分。
  使用自動化事務


  自動化事務簡化了編程模型,因為它們不需要明確地開始新事務處理過程,或明確執行或取消事務。然而,自動化事務的最大優點是它們能與DTC結合起來,這就使單個事務可以擴展到多個分布式數據源中。在大型分布式應用程序中,這個優點是很重要的。盡管通過手工對DTC直接編程來控制分布式事務是可能的,但自動化事務處理極大的簡化了工作量,并且它是為基于組件的系統而設計的。例如,可以方便地以說明方式配置多個組件以執行包含了單個事務處理的任務。

  自動化事務依靠于COM+提供的分布式事務處理支持特性。結果,只有服務組件(即從ServicedComponent類中派生的組件)能夠使用自動化事務。

  要為自動化事務處理配置類,操作如下:
  • 從位于EnterpriseServices名稱空間的ServicedComponent類中派生新類。

  • 通過Transaction屬性定義類的事務處理需求。來自TransactionOption的枚舉值決定了如何在COM+類中配置類??膳c此屬性一同設置的其它屬性包括事務處理分離等級和超時上限。

  • 為了避免必須明確選出事務處理結果,可以用AutoComplete屬性對方法進行注釋。假如這些方法釋放異常,事務將自動取消。注重,假如需要,仍可以直接挑選事務處理結果。
  更多信息
  • 關于COM+自動化事務的更多信息,可在平臺SDK文檔中搜索“通過COM+的自動化事務”獲取。

  • 關于.NE T事務處理類的示例,見附錄中的如何編碼.NET事務處理。
配置事務處理分離級別
 
  用于COM+1.0版--即運行在Windows 2000中的COM+--的事務處理分離級別被序列化了。這樣做提供了最高的分離等級,卻是以性能為代價的。系統的整體吞吐量被降低了。因為所涉及到的資源治理器(典型地是數據庫)在事務處理期間必須保持讀和寫鎖。在此期間,其它所有事務處理都被阻斷了,這種情況將對應用程序的擴展能力產生極大沖擊。

  隨微軟Windows .NET發行的COM+ 1.5版答應有COM+目錄中按組件配置事務處理分離等級。與事務中根組件相關的設置決定了事務處理的分離等級。另外,同一事務流中的內部子組件擁有的事務處理等級必須不能高于要組件所定義的等級。假如不是這樣,當子組件實例化時,將導致錯誤。

  對.NET治理類,Transaction屬性支持所有的公有Isolation屬性。你可以用此屬性陳述式地指定一非凡分離等級,如下面的代碼所示:

[Transaction(TransactionOption.Supported, Isolation=TransactionIsolationLevel.ReadCommitted)]
public class Account : ServicedComponent
{
. . .
}
  確定事務處理結果

  在單個事務流的所有事務處理組件上下文中,自動化事務處理結果由事務取消標志和一致性標志的狀態決定。當事務流中的根組件成為非活動狀態(并且控制權返回調用者)時,確定事務處理結果。這種情況在圖5中得到了演示,此圖顯示的是一個典型的銀行基金傳送事務。 

  當根對象(在本例中是對象)變為非活動狀態,并且客戶的方法調用返回時,確定事務處理結果。在任何上下文中的任何一致性標志被設為假,或假如事務處理取消標志設為真,那么底層的物理DTC事務將被取消。

  可以以下面兩種方式之一從.NET對象中控制事務處理結果:
  • 可以用AutoComplete屬性對方法進行注釋,并讓.NET自動存放將決定事務處理結果投票。假如方法釋放異常,利用此屬性,一致性標志自動地被設為假(此值最終使事務取消)。假如方法返回而沒有釋放異常,那么一致性標志將設為真,此值指出組件樂于執行事務。這并沒有得到保證,因為它依靠于同一事務流中其它對象的投票。

  • 可以調用ContextUtil類的靜態方法SetComplete或 SetAbort,這些方法分別將一致性標志設為真或假。
  嚴重性大于10的SQL Server錯誤將導致治理數據供給器釋放SqlException類型的異常。假如方法緩存并處理異常,就要確?;蛘咄ㄟ^手工取消了事務,或者方法被標記了[AutoComplete],以保證異常能傳遞回調用者。

  AutoComplete方法

  對于標記了屬性的方法,執行下面操作:
  • 將SqlException傳遞加調用堆棧。

  • 將SqlException封裝在外部例外中,并傳遞回調用者。也可以將異常封裝在對調用者更有意義的異常類型中。
  異常假如不能傳遞,將導致對象不會提出取消事務,從而忽視數據庫錯誤。這意味著共享同一事務流的其它對象的成功操作將被提交。

  下面的代碼緩存了SqlException,然后將它直接傳遞回調用者。事務處理最終將被取消,因為對象的一致性標志在對象變為非活動狀態時自動被設為假。

[AutoComplete]
void SomeMethod()
{
try
{
// Open the connection, and perform database Operation
. . .
}
catch (SqlException sqlex )
{
LogException( sqlex ); // Log the exception details
throw; // Rethrow the exception, causing the consistent
// flag to be set to false.
}
finally
{
// Close the database connection
. . .
}
}
  Non-AutoComlete方法

  對于沒有AutoComplete的屬性的方法,必須:
  • 在catch塊內調用ContextUtil.SetAbort以終止事務處理。這就將相容標志設置為假。

  • 假如沒有發生異常事件,調用ContextUtil.SetComplete,以提交事務,這就將相容標志設置為真(缺省狀態)。
  代碼說明了這種方法。

void SomeOtherMethod()
{
 try

 {
  // Open the connection, and perform database operation
  . . .
  ContextUtil.SetComplete(); // Manually vote to commit the transaction
 }
 catch (SqlException sqlex)
 {
  LogException( sqlex ); // Log the exception details
  ContextUtil.SetAbort(); // Manually vote to abort the transaction
  // Exception is handled at this point and is not propagated to the caller
 }
 finally
 {
  // Close the database connection
  . . .
 }
}
  注重 假如有多個catch塊,在方法開始的時候調用ContextVtil.SetAbort,以及在try塊的末尾調用ContextUtil.SetComplete都會變得輕易。用這種方法,就不需要在每個catch塊中重復調用ContextUtil.SetAbort。通過這種方法確定的相容標志的設置只在方法返回時有效。

  對于異常事件(或循環異常),必須把它傳遞到調用堆棧中,因為這使得調用代碼認為事務處理失敗。它答應調用代碼做出優化選擇。比如,在銀行資金轉賬中,假如債務操作失敗,則轉帳分支可以決定不執行債務操作。

  假如把相容標志設置為假并且在返回時沒有出現異常事件,則調用代碼就沒有辦法知道事務處理是否一定失敗。雖然可以返回Boolean值或設置Boolean輸出參數,但還是應該前后一致,通過顯示異常事件以表明有錯誤發生。這樣代碼就有一種標準的錯誤處理方法,因此更簡明、更具有相容性。數據分頁

  在分布式應用程序中利用數據進行分頁是一項普遍的要求。比如,用戶可能得到書的列表而該列表又不能夠一次完全顯示,用戶就需要在數據上執行一些熟悉的操作,比如瀏覽下一頁或上一頁的數據,或者跳到列表的第一頁或最后一頁。

  這部分內容將討論實現這種功能的選項,以及每種選項在性能和縮放性上的效果。

  選項比較

  數據分頁的選項有:
  • 利用SqlDataAdapter的Fill方法,將來自查詢處的結果填充到DataSet中。

  • 通過COM的可相互操作性使用ADO,并利用服務器光標。

  • 利用存儲的過程手工實現數據分頁。
  對數據進行分頁的最優選項依靠于下列因素:
  • 擴展性要求

  • 性能要求

  • 網絡帶寬

  • 數據庫服務器的存儲器和功率

  • 中級服務器的存儲器和功率

  • 由分頁查詢所返回的行數

  • 數據總頁數的大小
  性能測試表明利用存儲過程的手工方法在很大的應力水平范圍上都提供了最佳性能。然而,由于手工方法在服務器上執行工作,假如大部分站點功能都依靠數據分頁功能,那么服務器性能就會成一個要害要素。為確保這種方法能適合非凡環境,應該測試各種非凡要求的選項。

  下面將討論各種不同的選項。

  使用SqlDataAdapter

  如前面所討論的,SqlDataAdapter是用來把來自數據庫的數據填充到DataSet中,過載的Fill方法中的任一個都需要兩個整數索引值(如下列代碼所示):

public int Fill(
DataSet dataSet,
int startRecord,
int maxRecords,
string srcTable
);
  StartRecord值標示從零開始的記錄起始索引值。MaxRecord值表示從startRecord開始的記錄數,并將拷貝到新的DataSet中。

  SqlDataAdapter在內部利用SqlDataReader執行查詢并返回結果。SqlDataAdapter讀取結果并創建基于來自SalDataReader的數據的Dataset。SqlDataAdapter通過startRecord和maxRecords把所有結果都拷貝到新生成的DataSet中,并丟棄不需要的數據。這意味著許多不必要的數據將潛在的通過網絡進入數據訪問客戶--這是這種方法的主要缺陷。

  比如,假如有1000個記錄,而需要的是第900到950個記錄,那么前面的899個記錄將仍然穿越網絡然后被丟棄。對于小數量的記錄,這種開銷可能是比較小的,但假如針對大量數據的分頁,則這種開銷就會非常巨大。

  使用ADO

  實現分頁的另一個選項是利用基于COM的ADO進行分頁。這種方法的目標是獲得訪問服務器光標。服務器光標通過ADO Recordset對象顯示??梢园裄ecordset光標的位置設置到adUseServer中。假如你的OLE DB供給器支持這種設置(如SQLOLEDB那樣),就可以使用服務器光標。這樣就可以利用光標直接導航到起始記錄,而不需要將所有數據傳過網絡進入訪問數據的用戶代碼中。

  這種方法有下面兩個缺點:
  • 在大多數情況下,可能需要將返回到Recordset對象中的記錄翻譯成DataSet中的內容,以便在客戶治理的代碼中使用。雖然OleDbDataAdapter確實在獲取ADO Recordset對象并把它翻譯成Dataset時過載了Fill方法,但是并沒有利用非凡記錄進行開始與結束操作的功能。唯一現實的選項是把開始記錄移動到Recordset對象中,循環每個記錄,然后手工拷貝數據到手工生成的新Dataset中。這種操作,尤其是利用COM Interop調用,其優點可能不僅僅是不需要在網絡上傳輸多余的數據,尤其對于小的DataSet更明顯。

  • 從服務器輸出所需數據時,將保持連接和服務器光標開放。在數據庫服務器上,光標的開放與維護需要昂貴的資源。雖然該選項提高了性能,但是由于為延長的時間兩消耗服務器資源,從而也有可能降低可擴展性。
  提供手工實現

  在本部分中討論的數據分頁的最后一個選項是利用存儲過程手工實現應用程序的分頁功能。對于包含唯一要害字的表格,實現存儲過程相對輕易一些。而對于沒有唯一要害字的表格(也不應該有許多要害字),該過程會相對復雜一些。


  帶有唯一要害字的表格的分頁

  假如表格包含一個唯一要害字,就可以利用WHERE條款中的要害字創建從某個非凡行起始的結果設置。這種方法,與用來限制結果設置大小的SET ROWCOUNT狀態是相匹配的,提供了一種有效的分頁原理。這一方法將在下面存儲的代碼中說明:

CREATE PROCEDURE GetProductsPaged
 @lastProductID int,
 @pageSize int
AS
 SET ROWCOUNT @pageSize
 SELECT *
 FROM Products
 WHERE [standard search criteria]
 AND ProductID > @lastProductID
 ORDER BY [Criteria that leaves ProductID monotonically increasing]
GO
  這個存儲過程的調用程序僅僅維護LastProductID的值,并通過所選的連續調用之間的頁的大小增加或減小該值。

  不帶有唯一要害字的表格的分頁

  假如需要分頁的表格沒有唯一要害字,可以考慮添加一個--比如利用標識欄。這樣就可以實現上面討論的分頁方案了。

  只要能夠通過結合結果記錄中的兩個或更多區域來產生唯一性,就仍然有可能實現無唯一要害字表格的有效分頁方案。

  比如,考察下列表格:

Col1 Col2 Col3 Other columns… A 1 W … A 1 X   . A 1 Y   . A 1 Z   . A 2 W   . A 2 X   . B 1 W … B 1 X   .
  對于該表,結合Col 、Col2 和Col3就可能產生一種唯一性。這樣,就可以利用下面存儲過程中的方法實現分布原理:

CREATE PROCEDURE RetrieveDataPaged
 @lastKey char(40),
 @pageSize int
AS
 SET ROWCOUNT @pageSize
 SELECT
  Col1, Col2, Col3, Col4, Col1+Col2+Col3 As KeyField
  FROM SampleTable
  WHERE [Standard search criteria]
  AND Col1+Col2+Col3 > @lastKey
  ORDER BY Col1 ASC, Col2 ASC, Col3 ASC
GO
  客戶保持存儲過程返回的keyField欄的最后值,然后又插入回到存儲過程中以控制表的分頁。

  雖然手工實現增加了數據庫服務器上的應變,但它避免了在網絡上傳輸不必要的數據。性能測試表明在整個應變水平中這種方法都工作良好。然而,根據站點工作所涉及的數據分頁功能的多少,在服務器上進行手工分頁可能影響應用程序的可擴展性。應該在所在環境中運行性能測試,為應用程序找到最合適的方法。 QQread.com 推出各大專業服務器評測 Linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 附錄

  如何為一個.NET類啟用對象結構

  要利用Enterprise (COM+)Services為對象結構啟用.NET治理的類,需要執行下列步驟:
  • 從位于System. Enterprise Services名字空間中的Serviced Component中導出所需類。

    using System.EnterpriseServices;
    public class DataAccessComponent : ServicedComponent
  • 為該類添加Construction Enabled屬性,并合理地指定缺省結構字符串,該缺省值保存在COM+目錄中,治理員可以利用組件服務微軟治理控制臺(MNC)的snap-in來維護該缺省值。

    [ConstructionEnabled(Default="default DSN")]
    public class DataAccessComponent : ServicedComponent
  • 提供虛擬Construct方法的替換實現方案。該方法在對象語言構造程序之后調用。在COM目錄中保存的結構字符串是該方法的唯一字符串。

    public override void Construct( string constructString )
    {
    // Construct method is called next after constructor.
    // The configured DSN is supplied as the single argument
    }
  • 通過Assembly key文件或Assembly key Name屬性為該匯編提供一個強名字。任何用COM+服務注冊的匯編必須有一個強名字。關于帶有強名字匯編的更多信息,參考:http://msdn.microsoft.com/library/en-us/cpguide/html/cpconworkingwithstrongly- namedassemblies.Asp。
    [assembly: AssemblyKeyFile("DataServices.snk")]

  • 為支持動態注冊,可以利用匯編層上的屬性ApplicationName和Application Action分別指定用于保持匯編元素和應用程序動作類型的COM+應用程序的名字。
// the ApplicationName attribute specifies the name of the
// COM+ Application which will hold assembly components
[assembly : ApplicationName("DataServices")]

// the ApplicationActivation.ActivationOption attribute specifies
// where assembly components are loaded on activation
// Library : components run in the creator's process

// Server : components run in a system process, dllhost.exe
[assembly: ApplicationActivation(ActivationOption.Library)]

      下列代碼段是一個叫做DataAccessComponent的服務組件,它利用COM+結構字符串來獲得數據庫連接字符串。

    using System;
    using System.EnterpriseServices;
    // the ApplicationName attribute specifies the name of the
    // COM+ Application which will hold assembly components
    [assembly : ApplicationName("DataServices")]

    // the ApplicationActivation.ActivationOption attribute specifies
    // where assembly components are loaded on activation
    // Library : components run in the creator's process
    // Server : components run in a system process, dllhost.exe
    [assembly: ApplicationActivation(ActivationOption.Library)]

    // Sign the assembly. The snk key file is created using the
    // sn.exe utility
    [assembly: AssemblyKeyFile("DataServices.snk")]

    [ConstructionEnabled(Default="Default DSN")]
    public class DataAccessComponent : ServicedComponent
    {
    private string connectionString;
    public DataAccessComponent()
    {
    // constructor is called on instance creation
    }
    public override void Construct( string constructString )
    {
    // Construct method is called next after constructor.
    // The configured DSN is supplied as the single argument
    this.connectionString = constructString;
    }
    }

      如何利用SqlDataAdapter來檢索多個行

      下面的代碼說明如何利用SqlDataAdapter對象發出一個生成Data Set或Datatable的命令。它從SQL Server Northwind數據庫中檢索一系列產品目錄。

    using System.Data;
    using System.Data.SqlClient;
    public DataTable RetrieveRowsWithDataTable()
    {
     using ( SqlConnection conn = new SqlConnection(connectionString) )
     {
      SqlCommand cmd = new SqlCommand("DATRetrieveProducts", conn);
      cmd.CommandType = CommandType.StoredProcedure;
      SqlDataAdapter da = new SqlDataAdapter( cmd );
      DataTable dt = new DataTable("Products");
      da.Fill(dt);
      return dt;
     }
    }

      按下列步驟利用SqlAdapter生成DataSet或DataTable:
    • 創建SqlCommand對象啟用存儲過程,并把它與SqlConnection對象(顯示的)或連接字符串(未顯示)相聯系。

    • 創建一個新的SqlDataAdapter對象,并把它SqlCommand對象相聯系。

    • 創建DataTable(或者DataSet)對象。利用構造程序自變量命名DataTable.

    • 調用SqlData Adapter對象的Fill方法,把檢索的行轉移到DataSet或Datatable中。
      如何利用SqlDataReader檢索多個行

      下列代碼說明了如何利用SqlDataReader方法檢索多行:

    using System.IO;
    using System.Data;
    using System.Data.SqlClient;
    public SqlDataReader RetrieveRowsWithDataReader()
    {
     SqlConnection conn = new SqlConnection("server=(local);
             Integrated Security=SSPI;database=northwind");
     SqlCommand cmd = new SqlCommand("DATRetrieveProducts", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     try
     {
      conn.Open();
      // Generate the reader. CommandBehavior.CloseConnection causes the
      // the connection to be closed when the reader object is closed
      return( cmd.ExecuteReader( CommandBehavior.CloseConnection ) );
     }
     catch
     {
      conn.Close();

      throw;
     }
    }

    // Display the product list using the console
    private void DisplayProducts()
    {
     SqlDataReader reader = RetrieveRowsWithDataReader();
     while (reader.Read())
     {
      Console.WriteLine("{0} {1} {2}",
      reader.GetInt32(0).ToString(),
      reader.GetString(1) );
     }
     reader.Close(); // Also closes the connection due to the
     // CommandBehavior enum used when generating the reader
    }

      按下列步驟利用SqlDataReader檢索多行:
    • 創建用于執行存儲的過程的SqlCommand對象,并把它與SqlConnection對象相聯系。

    • 打開鏈接。

    • 通過調用SqlCommand對象的Excute Reader方法生成SqlDataReader對象。

    • 從流中讀取數據,調用SqlDataReader對象的Read方法來檢索行,并利用分類的存取程序方法(如GetIut 32和Get String方法)檢索列的值。

    • 完成讀取后,調用Close方法。
    如何利用XmlReader檢索多個行

      可以利用SqlCommand對象生成XmlReader對象,它提供對XML數據的基于流的前向訪問。該命令(通常是一個存儲的過程)必須生成一個基于XML的結果設置,它對于SQL Server2000通常是由帶有有效條款FOR XML的SELECT狀態組成。下列代碼段說明了這種方法:

    public void RetrieveAndDisplayRowsWithXmlReader()
    {
     SqlConnection conn = new SqlConnection(connectionString);
     SqlCommand cmd = new SqlCommand("DATRetrieveProductsXML", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     try
     {
      conn.Open();
      XmlTextReader xreader = (XmlTextReader)cmd.ExecuteXmlReader();
      while ( xreader.Read() )
      {
       if ( xreader.Name == "PRODUCTS" )
       {
        string strOutput = xreader.GetAttribute("ProductID");
        strOutput += " ";
        strOutput += xreader.GetAttribute("ProductName");
        Console.WriteLine( strOutput );
       }
      }
      xreader.Close();
     }
     catch
     {
      throw;
     }
     finally
     {
      conn.Close();
     }
    }
      上述代碼使用了下列存儲過程:


    CREATE PROCEDURE DATRetrieveProductsXML
    AS
     SELECT * FROM PRODUCTS
     FOR XML AUTO
    GO
      按下列步驟檢索XML數據:
    • 創建SqlCommand對象啟用生成XML結果設置的過程。(比如,利用SELECT狀態中的FOR XML條款)。把SqlCommand對象與一個鏈接相聯系。

    • 調用SqlCommand對象的ExecuteXmlReader方法,并把結果分配給前向對象XmlTextReader。當不需要任何返回數據的基于XML的驗證時,這是應該使用的最快類型的XmlReader對象。

    • 利用XmlTextReader對象的Read方法讀取數據。
      如何利用存儲過程輸出參數檢索單個行

      可以調用一個存儲過程,它通過一種稱做輸出參數的方式可以在單個行中返回檢索數據項。下列代碼段利用存儲的過程檢索產品的名稱和單價,該產品包含在Northwind數據庫中。

    void GetProductDetails( int ProductID,
    out string ProductName, out decimal UnitPrice )
    {
     SqlConnection conn = new SqlConnection( "server=(local);
              Integrated Security=SSPI;database=Northwind");
     // Set up the command object used to execute the stored proc
     SqlCommand cmd = new SqlCommand( "DATGetProductDetailsSPOutput", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     // Establish stored proc parameters.
     // @ProductID int INPUT
     // @ProductName nvarchar(40) OUTPUT
     // @UnitPrice money OUTPUT

     // Must explicitly set the direction of output parameters
     SqlParameter paramProdID = cmd.Parameters.Add( "@ProductID", ProductID );
     paramProdID.Direction = ParameterDirection.Input;
     SqlParameter paramProdName = cmd.Parameters.Add( "@ProductName", SqlDbType.VarChar, 40 );

     paramProdName.Direction = ParameterDirection.Output;
     SqlParameter paramUnitPrice = cmd.Parameters.Add( "@UnitPrice", SqlDbType.Money );
     paramUnitPrice.Direction = ParameterDirection.Output;
     try
     {
      conn.Open();
      // Use ExecuteNonQuery to run the command.
      // Although no rows are returned any mapped output parameters
      // (and potentially return values) are populated
      cmd.ExecuteNonQuery( );
      // Return output parameters from stored proc
      ProductName = paramProdName.Value.ToString();
      UnitPrice = (decimal)paramUnitPrice.Value;
     }
     catch
     {
      throw;
     }
     finally
     {
      conn.Close();
     }
    }

      按下列步驟利用存儲的過程輸出參數檢索單個行:
    • 創建一個SqlCommand對象,并把它與SqlConnection對象相聯系。

    • 通過調用SqlCommand’s Parameters集合的Add方法設置存儲過程參數。缺省情況下,參數假定為輸出參數,所以必須明確設置任何輸出參數的方向。
      注重 明確設置所有參數的方向是一次很好的練習,包括輸入參數。
    • 打開連接。

    • 調用Sqlcommand對象的ExecuteNonQuery方法。它在輸出參數(并潛在地帶有一個返回值)中。

    • 利用Value屬性從合適的SqlParameter對象中檢索輸出參數。

    • 關閉連接。
      上述代碼段啟用了下列存儲過程。

    CREATE PROCEDURE DATGetProductDetailsSPOutput
     @ProductID int,
     @ProductName nvarchar(40) OUTPUT,
     @UnitPrice money OUTPUT
    AS
     SELECT @ProductName = ProductName,
        @UnitPrice = UnitPrice
      FROM Products
      WHERE ProductID = @ProductID
    GO
      如何利用SqlDataReader檢索單個行

      可以利用SqlDataReader對象檢索單個行,以及來自返回數據流的所需欄的值。這由下列代碼說明:

    void GetProductDetailsUsingReader( int ProductID,
    out string ProductName, out decimal UnitPrice )
    {
     SqlConnection conn = new SqlConnection("server=(local);
            Integrated Security=SSPI;database=Northwind");
     // Set up the command object used to execute the stored proc
     SqlCommand cmd = new SqlCommand( "DATGetProductDetailsReader", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     // Establish stored proc parameters.
     // @ProductID int INPUT

     SqlParameter paramProdID = cmd.Parameters.Add( "@ProductID", ProductID );
     paramProdID.Direction = ParameterDirection.Input;
     try
     {
      conn.Open();
      SqlDataReader reader = cmd.ExecuteReader();
      reader.Read(); // Advance to the one and only row

      // Return output parameters from returned data stream
      ProductName = reader.GetString(0);
      UnitPrice = reader.GetDecimal(1);
      reader.Close();
     }
     catch
     {
      throw;
     }
     finally
     {
      conn.Close();
     }
    }

      按下列步驟返回帶有SqlDataReader對象:
    • 建立SqlCommand對象。

    • 打開連接。

    • 調用SqlDReader對象的ExecuteReader對象。

    • 利用SqlDataReader對象的分類的存取程序方法檢索輸出參數--在這里是GetString和GetDecimal.
      上述代碼段啟用了下列存儲過程:

    CREATE PROCEDURE DATGetProductDetailsReader
     @ProductID int
    AS
     SELECT ProductName, UnitPrice FROM Products
      WHERE ProductID = @ProductID
    GO
      如何利用ExecuteScalar單個項

      ExecuteScalar方法是設計成用于返回單個值的訪問。在返回多列或多行的訪問事件中,ExecuteScalar只返回第一行的第一例。

      下列代碼說明如何查詢某個產品ID的產品名稱:


    void GetProductNameExecuteScalar( int ProductID, out string ProductName )
    {
     SqlConnection conn = new SqlConnection( "server=(local);
            Integrated Security=SSPI;database=northwind");
     SqlCommand cmd = new SqlCommand("LookupProductNameScalar", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@ProductID", ProductID );
     try
     {
      conn.Open();
      ProductName = (string)cmd.ExecuteScalar();
     }
     catch
     {
      throw;
     }
     finally
     {
      conn.Close();
     }
    }

      按下列步驟利用Execute Scalar檢索單個項:
    • 建立調用存儲過程的SqlCommand對象。

    • 打開鏈接。

    • 調用ExecuteScalar方法,注重該方法返回對象類型。它包含檢索的第一列的值,并且必須設計成合適的類型。

    • 關閉鏈接。
      上述代碼啟用了下列存儲過程:

    CREATE PROCEDURE LookupProductNameScalar
     @ProductID int
    AS
     SELECT TOP 1 ProductName
      FROM Products
      WHERE ProductID = @ProductID
    GO
      如何利用存儲過程輸出或返回的參數檢索單個項

      利用存儲過程輸出或返回的參數可以查詢單個值,下列代碼說明了輸出參數的使用:

    void GetProductNameUsingSPOutput( int ProductID, out string ProductName )
    {
    SqlConnection conn = new SqlConnection( "server=(local);
                Integrated Security=SSPI;
                database=northwind");
    SqlCommand cmd = new SqlCommand("LookupProductNameSPOutput", conn );
    cmd.CommandType = CommandType.StoredProcedure;
    SqlParameter paramProdID = cmd.Parameters.Add("@ProductID", ProductID );
    ParamProdID.Direction = ParameterDirection.Input;
    SqlParameter paramPN = cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 );
    paramPN.Direction = ParameterDirection.Output;
    try
    {
     conn.Open();
     cmd.ExecuteNonQuery();
     ProductName = paramPN.Value.ToString();
    }
    catch
    {
     throw;
    }
    finally
    {
     conn.Close();
    }
    }

      按下列步驟利用存儲過程的輸出參數檢索單個值:
    • 創建調用存儲過程的SqlCommand對象。

    • 通過把SqlParmeters添加到SqlCommand’s Parameters集合中設置任何輸入參數和單個輸出參數。

    • 打開鏈接。

    • 調用SqlCommand對象的Execute NonQuery方法。

    • 關閉鏈接。

    • 利用輸出SqlParameter的Value屬性檢索輸出值。
      上述代碼使用了下列存儲過程:

    CREATE PROCEDURE LookupProductNameSPOutput
     @ProductID int,
     @ProductName nvarchar(40) OUTPUT
    AS
     SELECT @ProductName = ProductName
     FROM Products
     WHERE ProductID = @ProductID
    GO
      下列代碼說明如何利用返回值確定是否存在非凡行。從編碼的角度看,這與使用存儲過程輸出參數相類似,除了需要明確設置到ParameterDirection.ReturnValue的SqlParameter方向。

    bool CheckProduct( int ProductID )
    {
     SqlConnection conn = new SqlConnection( "server=(local);
                 Integrated Security=SSPI;database=northwind");
     SqlCommand cmd = new SqlCommand("CheckProductSP", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@ProductID", ProductID );
     SqlParameter paramRet = cmd.Parameters.Add("@ProductExists", SqlDbType.Int );
     paramRet.Direction = ParameterDirection.ReturnValue;
     try
     {
      conn.Open();
      cmd.ExecuteNonQuery();
     }
     catch
     {
      throw;
     }
     finally

     {
      conn.Close();
     }
     return (int)paramRet.Value == 1;
    }

      按下列步驟,可以利用存儲過程返回值檢查是否存在非凡行:
    • 建立調用存儲過程的SqlCommand對象。

    • 設置包含需要訪問的行的主要要害字的輸入參數。

    • 設置單個返回值參數。把SqlParameter對象添加到SqlCommand’s Parameter集合中,并設置它到ParameterDireetion.ReturnValue的方面。

    • 打開鏈接。

    • 調用SqlCommand對象的ExecuteNonQuery的方法.

    • 關閉鏈接。

    • 利用返回值SqlParameter的Value屬性檢索返回值。
      上述代碼使用了下列存儲過程:

    CREATE PROCEDURE CheckProductSP
     @ProductID int
    AS
     IF EXISTS( SELECT ProductID FROM Products
          WHERE ProductID = @ProductID )
       return 1
     ELSE
      return 0
    GO
        如何利用SqlDataReader檢索單個項。
     
      通過調用命令對象的ExecuteReader方法,可以利用SqlDataReader對象獲得單個輸出值。這需要稍微多一些的代碼,因為SqlDataReader Read方法必須調用,然后所需值通過讀者存取程序方法得到檢索。SqlDataReader對象的使用在下列代碼中說明:

    bool CheckProductWithReader( int ProductID )
    {
     SqlConnection conn = new SqlConnection( "server=(local);
                  Integrated Security=SSPI;database=northwind");
     SqlCommand cmd = new SqlCommand("CheckProductExistsWithCount", conn );
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@ProductID", ProductID );
     cmd.Parameters["@ProductID"].Direction = ParameterDirection.Input;
     try
     {
      conn.Open();
      SqlDataReader reader = cmd.ExecuteReader( CommandBehavior.SingleResult );
      reader.Read();

      bool bRecordExists = reader.GetInt32(0) > 0;
      reader.Close();
      return bRecordExists;
     }
     catch
     {
      throw;
     }
     finally
     {
      conn.Close();
     }

    }

      上述代碼使用了下列存儲過程:

    CREATE PROCEDURE CheckProductExistsWithCount
     @ProductID int
    AS
     SELECT COUNT(*) FROM Products
       WHERE ProductID = @ProductID
    GO QQread.com 推出各大專業服務器評測 Linux服務器的安全性能 SUN服務器 HP服務器 DELL服務器 IBM服務器 聯想服務器 浪潮服務器 曙光服務器 同方服務器 華碩服務器 寶德服務器 如何編碼ADO.NET手工事務

      下列代碼說明如何利用SQL Server. NET數據供給器提供的事務支持來保護事務的支金轉帳操作。該操作在位于同一數據庫中的兩個帳戶之間轉移支金。

    public void TransferMoney( string toAccount, string fromAccount, decimal amount )
    {
     using ( SqlConnection conn = new SqlConnection(
          "server=(local);Integrated Security=SSPI;database=SimpleBank" ) )
     {
      SqlCommand cmdCredit = new SqlCommand("Credit", conn );
      cmdCredit.CommandType = CommandType.StoredProcedure;
      cmdCredit.Parameters.Add( new SqlParameter("@AccountNo", toAccount) );
      cmdCredit.Parameters.Add( new SqlParameter("@Amount", amount ));
      SqlCommand cmdDebit = new SqlCommand("Debit", conn );
      cmdDebit.CommandType = CommandType.StoredProcedure;
      cmdDebit.Parameters.Add( new SqlParameter("@AccountNo", fromAccount) );
      cmdDebit.Parameters.Add( new SqlParameter("@Amount", amount ));

      conn.Open();
      // Start a new transaction
      using ( SqlTransaction trans = conn.BeginTransaction() )
      {
       // Associate the two command objects with the same transaction
       cmdCredit.Transaction = trans;
       cmdDebit.Transaction = trans;

      try
      {
       cmdCredit.ExecuteNonQuery();
       cmdDebit.ExecuteNonQuery();
       // Both commands (credit and debit) were successful
       trans.Commit();
      }
      catch( Exception ex )
      {
       // transaction failed
       trans.Rollback();
       // log exception details . . .
       throw ex;
      }
     }
     }
    }

      如何利用Transact-SQL執行事務

      下列存儲過程說明了如何在Transact-SQL過程內執行事務的支金轉移操作。

    CREATE PROCEDURE MoneyTransfer
     @FromAccount char(20),
     @ToAccount char(20),
     @Amount money
    AS

    BEGIN TRANSACTION
     -- PERFORM DEBIT OPERATION
     UPDATE Accounts
     SET Balance = Balance - @Amount
     WHERE AccountNumber = @FromAccount
     IF @@RowCount = 0
     BEGIN
      RAISERROR('Invalid From Account Number', 11, 1)
      GOTO ABORT
     END

    DECLARE @Balance money
     SELECT @Balance = Balance FROM ACCOUNTS
      WHERE AccountNumber = @FromAccount
      IF @BALANCE < 0
      BEGIN
       RAISERROR('Insufficient funds', 11, 1)
       GOTO ABORT
      END
      -- PERFORM CREDIT OPERATION
      UPDATE Accounts
       SET Balance = Balance + @Amount
        WHERE AccountNumber = @ToAccount
        IF @@RowCount = 0
        BEGIN
         RAISERROR('Invalid To Account Number', 11, 1)
         GOTO ABORT
        END
      COMMIT TRANSACTION
      RETURN 0
      ABORT:
       ROLLBACK TRANSACTION
     GO
      該存儲過程使用BEGIN TRANSACTION, COMMIT TRANSACTION,和ROLLBACK TRANSACTION狀態手工控制事務。

      如何編碼事務性的.NET類

      下述例子是三種服務性的NET類,它們配置或用于自動事務。每個類都帶有Transaction屬性,它的值將決定是否啟動新事務流或者對象是否共享即時調用程序的數據流。這些元素一起工作來執行銀行支金轉移。Transfer類配置有RequiresNew事務屬性,而Debit和Credit類配置有Required屬性。這樣,在運行的時候三個對象共享同一個事務。

    using System;
    using System.EnterpriseServices;
    [Transaction(TransactionOption.RequiresNew)]
    public class Transfer : ServicedComponent
    {
     [AutoComplete]
     public void Transfer( string toAccount,
     string fromAccount, decimal amount )
     {
      try
      {
       // Perform the debit operation
       Debit debit = new Debit();
       debit.DebitAccount( fromAccount, amount );
       // Perform the credit operation
       Credit credit = new Credit();
       credit.CreditAccount( toAccount, amount );
      }
      catch( SqlException sqlex )
      {
       // Handle and log exception details
       // Wrap and propagate the exception
       throw new TransferException( "Transfer Failure", sqlex );
      }
     }
    }

    [Transaction(TransactionOption.Required)]
    public class Credit : ServicedComponent
    {
     [AutoComplete]
     public void CreditAccount( string account, decimal amount )
     {
      SqlConnection conn = new SqlConnection(
        "Server=(local); Integrated Security=SSPI"; database="SimpleBank");
      SqlCommand cmd = new SqlCommand("Credit", conn );
      cmd.CommandType = CommandType.StoredProcedure;
      cmd.Parameters.Add( new SqlParameter("@AccountNo", account) );
      cmd.Parameters.Add( new SqlParameter("@Amount", amount ));
      try
      {
       conn.Open();
       cmd.ExecuteNonQuery();

      }
      catch (SqlException sqlex)
      {
       // Log exception details here
       throw; // Propagate exception
      }
     }
    }

    [Transaction(TransactionOption.Required)]
    public class Debit : ServicedComponent
    {
     public void DebitAccount( string account, decimal amount )
     {
      SqlConnection conn = new SqlConnection(
       "Server=(local); Integrated Security=SSPI"; database="SimpleBank");
      SqlCommand cmd = new SqlCommand("Debit", conn );
      cmd.CommandType = CommandType.StoredProcedure;
      cmd.Parameters.Add( new SqlParameter("@AccountNo", account) );
      cmd.Parameters.Add( new SqlParameter("@Amount", amount ));
      try
      {
       conn.Open();
       cmd.ExecuteNonQuery();
      }
      catch (SqlException sqlex)
      {
       // Log exception details here
       throw; // Propagate exception back to caller
      }
     }
    }


    發表評論 共有條評論
    用戶名: 密碼:
    驗證碼: 匿名發表
    亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
    国产成人精品免费视频| 成人性教育视频在线观看| 亚洲国产成人精品电影| 亚洲另类激情图| 91国产美女视频| 欧美高清视频在线播放| 亚洲精品一区av在线播放| 国内精品久久久久久影视8| 欧美精品videofree1080p| 疯狂欧美牲乱大交777| 亚洲精品日韩丝袜精品| 国产视频精品va久久久久久| 国产suv精品一区二区三区88区| 欧洲美女免费图片一区| 91久久夜色精品国产网站| 亚洲人在线观看| 欧美激情亚洲视频| 国产亚洲成av人片在线观看桃| 国产亚洲激情在线| 777777777亚洲妇女| 国产精品第一区| 欧美性xxxx| 欧美三级欧美成人高清www| 日本成人精品在线| 久久777国产线看观看精品| 国产精品久久久久久一区二区| 久久成人亚洲精品| 自拍偷拍免费精品| 欧美在线视频在线播放完整版免费观看| 亚洲性猛交xxxxwww| 日韩美女视频免费在线观看| 欧美另类精品xxxx孕妇| 日韩精品极品视频| 日韩电影第一页| 国产精品一区二区久久| 欧美电影免费播放| 欧美日韩中文在线| 欧美激情精品在线| 亚洲免费av电影| 欧美午夜激情小视频| 55夜色66夜色国产精品视频| 4438全国成人免费| 91超碰中文字幕久久精品| 亚洲国产精品va在线观看黑人| 欧美亚洲另类视频| 欧美国产视频一区二区| 欧美日本黄视频| 日韩成人小视频| 欧美大片va欧美在线播放| 成人午夜两性视频| 亚洲一二在线观看| 国产裸体写真av一区二区| 亚洲一区二区久久| 精品美女久久久久久免费| 欧美成人免费va影院高清| 91九色在线视频| 美女精品视频一区| 欧美性猛交xxxx乱大交| 红桃视频成人在线观看| 欧美性猛交xxxx富婆弯腰| 欧美精品少妇videofree| 人人做人人澡人人爽欧美| 日韩电影在线观看中文字幕| 影音先锋欧美精品| 日韩专区在线播放| 欧美一级大片在线免费观看| 欧美激情精品久久久久久久变态| 欧美人在线视频| 久久综合久中文字幕青草| 最近2019年手机中文字幕| 欧美最猛性xxxxx亚洲精品| 亚洲一区亚洲二区| 成人福利网站在线观看11| 免费97视频在线精品国自产拍| 欧美亚洲国产视频小说| 欧洲精品久久久| 欧美日韩中文字幕在线| 欧美高清一级大片| 一本大道亚洲视频| 一区二区av在线| 精品久久久久久久久中文字幕| 成人黄在线观看| 91中文精品字幕在线视频| 国产有码一区二区| 国内精品久久久久影院优| 久久久久久久爱| 久久久精品一区| 成人做爰www免费看视频网站| 在线日韩av观看| 国产精品亚洲美女av网站| 久久久亚洲网站| 久久深夜福利免费观看| 日韩成人激情影院| 456亚洲影院| 成人精品网站在线观看| 国产区亚洲区欧美区| 欧美成人精品在线| 91av网站在线播放| 久久亚洲影音av资源网| 久久亚洲私人国产精品va| 国产精品精品视频| 久久青草精品视频免费观看| 亚洲欧美另类国产| 欧美日韩成人在线视频| 日韩第一页在线| 岛国视频午夜一区免费在线观看| 亚洲国产天堂久久国产91| 成人国产精品一区二区| 久久99热这里只有精品国产| 亚洲精品国产品国语在线| 视频一区视频二区国产精品| 色偷偷88888欧美精品久久久| 在线国产精品视频| 国产主播在线一区| 亚洲综合在线播放| 成人欧美一区二区三区在线湿哒哒| 欧美精品电影免费在线观看| 欧美电影免费观看网站| 精品电影在线观看| 日韩电影中文字幕| 亚洲欧美国产日韩天堂区| 欧美男插女视频| 国产热re99久久6国产精品| 欧美巨乳美女视频| 国产91对白在线播放| 久久九九免费视频| 中文字幕亚洲欧美日韩在线不卡| 日韩av在线不卡| 成人写真视频福利网| 亚洲精品乱码久久久久久按摩观| 国产欧美一区二区三区四区| 久久亚洲精品中文字幕冲田杏梨| 国产精品流白浆视频| 亚洲伊人成综合成人网| 91在线观看免费观看| 97在线视频免费观看| 日本久久中文字幕| 国产欧美在线视频| 欧美日韩国产限制| 精品网站999www| 亚洲成人a**站| 日韩在线不卡视频| 日本午夜精品理论片a级appf发布| 国产精品福利观看| 日本精品性网站在线观看| 国产精品日韩欧美大师| 亚洲成年网站在线观看| 日本一区二区不卡| 97香蕉久久超级碰碰高清版| 日韩av免费在线观看| 欧美日韩999| 97视频在线观看成人| 欧美另类99xxxxx| 欧美成人免费小视频| 2019中文在线观看| 国产一区二区三区在线视频| 久久久久成人网| 国产综合视频在线观看| 国产精品av在线播放| 亚洲欧美日韩精品久久亚洲区| 成人h视频在线| 国产精品久久久久国产a级| 国产精品久久综合av爱欲tv| 久久久久国色av免费观看性色|