假如你在為.NET應(yīng)用程序設(shè)計(jì)數(shù)據(jù)訪問(wèn)層,那么就應(yīng)該把 Microsoft ADO.NET用作數(shù)據(jù)訪問(wèn)模型。ADO.NET擴(kuò)展豐富,并且支持結(jié)合松散的數(shù)據(jù)訪問(wèn)需求、多層Web應(yīng)用程序及Web服務(wù)。通常,它利用許多擴(kuò)展豐富的對(duì)象模型, ADO.NET提供了多種方法用于解決一個(gè)特定問(wèn)題。
注重本文的重點(diǎn)是ADO.NET的使用:利用SQL Server .NETData PRovider--隨ADO.NET一起提供的兩個(gè)供給器之一--訪問(wèn)Microsoft SQL Server 2000。本文在合適的地方,將突出顯示在你使用OLE DB .NET數(shù)據(jù)供給器訪問(wèn)其它OLE DB敏感數(shù)據(jù)源時(shí)需要注重的所有差別。
要利用本指南構(gòu)建.NET應(yīng)用程序,你必須有利用ActiveX數(shù)據(jù)對(duì)象(ADO)和/或 OLE DB開(kāi)發(fā)數(shù)據(jù)訪問(wèn)代碼的實(shí)際經(jīng)驗(yàn),及SQL Server經(jīng)驗(yàn)。你也必須明白如何為.NET平臺(tái)開(kāi)發(fā)治理代碼,并且也必須清楚ADO.NET數(shù)據(jù)訪問(wèn)模型引入的基本變化。
ADO.NET簡(jiǎn)介
ADO.NET是.NET應(yīng)用程序的數(shù)據(jù)訪問(wèn)模型。它能用于訪問(wèn)關(guān)系型數(shù)據(jù)庫(kù)系統(tǒng),如SQL Server 2000,及很多其它已經(jīng)配備了OLE DB供給器的數(shù)據(jù)源。在某種程度上,ADO.NET代表了最新版本的ADO技術(shù)。然而,ADO.NET引入了一些重大變化和革新,它們專門用于結(jié)構(gòu)松散的、本質(zhì)非鏈接的Web應(yīng)用程序。關(guān)于ADO 與 ADO.NET的比較,見(jiàn)MSDN中的“用于ADO程序員的ADO.NET”一文。
SQL Server .NET 數(shù)據(jù)供給器。這是用于Microsoft SQL Server 7.0及其以后版本數(shù)據(jù)庫(kù)的供給器,它優(yōu)化了對(duì)SQL Server的訪問(wèn),并利用 SQL Server內(nèi)置的數(shù)據(jù)轉(zhuǎn)換協(xié)議直接與SQL Server通信。
當(dāng)鏈接到SQL Server 7.0 或 SQL Server 2000時(shí),總是要使用此供給器。
OLE DB .NET 數(shù)據(jù)供給器。. 這是一個(gè)用于治理OLE DB 數(shù)據(jù)源的供給器。它的效率稍低于SQL Server .NET Data Provider,因?yàn)樵谂c數(shù)據(jù)庫(kù)通信時(shí),它需通過(guò)OLE DB層進(jìn)行呼叫。注重,此供給器不支持用于開(kāi)放數(shù)據(jù)庫(kù)鏈接(ODBC),MSDASQL的OLE DB供給器。對(duì)于ODBC數(shù)據(jù)源,應(yīng)使用ODBC .NET數(shù)據(jù)供給器。有關(guān)與ADO.NET兼容的OLE DB供給器列表。
目前測(cè)試版中的其它.NET數(shù)據(jù)供給器包括:
ODBC .NET 數(shù)據(jù)供給器。目前Beta 1.0版可供下載。它提供了對(duì)ODBC驅(qū)動(dòng)器的內(nèi)置訪問(wèn),其方式與OLE DB .NET數(shù)據(jù)供給器提供的對(duì)本地OLE DB供給器的訪問(wèn)方式相同。關(guān)于ODBC .NET及Beta版下載的更多信息見(jiàn).
用于從SQL Server 2000中得到XML的治理供給器。用于SQL Server Web升級(jí)2版的XML還包括了專用于從SQL Server 2000中得到XML的治理供給器。關(guān)于此升級(jí)版本的更多信息,見(jiàn) .
// 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";
注重 過(guò)去,VB開(kāi)發(fā)人員被建議避免使用"Dim x As New…"結(jié)構(gòu)創(chuàng)建對(duì)象。在COM環(huán)境中,這些代碼將導(dǎo)致COM對(duì)象創(chuàng)建過(guò)程的“短路”,產(chǎn)生一些奇妙的和不怎么奇妙的錯(cuò)誤。然而,在.NET環(huán)境中,這已不再是一個(gè)問(wèn)題。
假如正在使用SQL Server .NET數(shù)據(jù)供給器,那么就可使用該供給器提供的鏈接池化支持特性。它是由供給器在治理代碼內(nèi)內(nèi)置實(shí)現(xiàn)的對(duì)事務(wù)敏感的高效機(jī)制。每個(gè)過(guò)程都將創(chuàng)建池,并且直到過(guò)程結(jié)束,池才被取消。
"Server=(local); Integrated Security=SSPI; Database=Northwind; Max Pool Size=75; Min Pool Size=5" 當(dāng)鏈接打開(kāi),池被創(chuàng)建時(shí),多個(gè)鏈接增加到池中以使鏈接數(shù)滿足所配置的最小值。此后,鏈接就能增加到池中,直到配置的最大池計(jì)數(shù)。當(dāng)達(dá)到最大計(jì)數(shù)時(shí),打開(kāi)新鏈接的請(qǐng)求將排隊(duì)一段可配置的時(shí)間。
在使用SQL Server .NET數(shù)據(jù)供給器鏈接池時(shí),必須清楚:鏈接是通過(guò)對(duì)鏈接字符串精確匹配的法則被池化的。池化機(jī)制對(duì)名稱-值對(duì)間的空格敏感。例如,下面的兩個(gè)鏈接字符串將生成單獨(dú)的池,因?yàn)榈诙€(gè)字符串包含了一個(gè)額外的空字符。
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)
然而,由于SQL Server .NET數(shù)據(jù)供給器內(nèi)置地使用池化,所以(在使用此供給器時(shí))你不再需要開(kāi)發(fā)自己的對(duì)象池化機(jī)制。這樣就可以避免手工事務(wù)征募帶來(lái)的復(fù)雜性。
假如正在使用OLE DB .NET數(shù)據(jù)供給器,那么考慮COM+對(duì)象池化以從高級(jí)配置和改進(jìn)的性能中受益。假如你為此目的開(kāi)發(fā)一個(gè)池化對(duì)象,那么必須使用OLE DB資源池化和自動(dòng)事務(wù)征募失效(例如,通過(guò)將“OLE DB Services=-4”包含進(jìn)鏈接字符串中)。必須在池化對(duì)象的實(shí)現(xiàn)中處理事務(wù)征募。
注重 .NET框架的RTM版本將另外包含一組ADO .NET性能計(jì)數(shù)器(這些計(jì)數(shù)器能與性能監(jiān)視器結(jié)合起來(lái)使用),這些計(jì)數(shù)器用于為SQL Server .NET數(shù)據(jù)供給器監(jiān)視并積累鏈接池化狀態(tài)。治理安全性
SQL Server 7.0及其以后版本支持用于所有網(wǎng)絡(luò)庫(kù)的Windows認(rèn)證。使用TCP/IP可以獲得配置、性能及擴(kuò)展性優(yōu)點(diǎn)。關(guān)于使用TCP/IP的更多信息,見(jiàn)本文通過(guò)防火墻建立鏈接一節(jié)。
安全性。盡管ASP.NET Internet 服務(wù)器應(yīng)用程序編程接口(ISAPI)DLL阻止了客戶直接訪問(wèn)帶.config擴(kuò)展名的文件,并且NTFS文件系統(tǒng)權(quán)限也用于進(jìn)一步限制訪問(wèn),但你可能仍希望避免以明文方式將這些內(nèi)容存儲(chǔ)在前端的Web服務(wù)器上。要增加安全性,需將鏈接字符串以加密格式存儲(chǔ)在配置文件中。
OLE DB .NET數(shù)據(jù)供給器支持在它的鏈接字符串中使用統(tǒng)一數(shù)據(jù)鏈接(UDL)文件名。可以以構(gòu)建參數(shù)的形式將鏈接字符串傳給OleDbConnection對(duì)象,或利用對(duì)象的ConnectionString屬性設(shè)置鏈接字符串。
注重 SQL Server .NET數(shù)據(jù)供給器不支持在它的鏈接字符串中使用UDL文件。因此,只有使用OLE DB .NET數(shù)據(jù)供給器,此方法才有效。
對(duì)于OLE DB 供給器,要利用鏈接字符串引用UDL文件,使用“File Name=name.udl.”。
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(); } } 此方法也適用于其它對(duì)象,如SqlDataReader 或OleDbDataReader,在其它任何對(duì)象對(duì)當(dāng)前鏈接進(jìn)行處理前,這些對(duì)象必須被關(guān)閉。錯(cuò)誤處理
所有.NET異常類型最終是從System名稱空間的Exception基類中派生的。.NET數(shù)據(jù)供給器釋放特定的供給器異常類型。例如,一旦SQL Server 返回一個(gè)錯(cuò)誤狀態(tài)時(shí),SQL Server .NET數(shù)據(jù)供給器釋放SqlException對(duì)象。類似的,OLE DB .NET數(shù)據(jù)供給器釋放 OleDbException類型的異常,此對(duì)象包含了由底層OLE DB供給器暴露的細(xì)節(jié)。
要處理數(shù)據(jù)訪問(wèn)例外狀態(tài),將數(shù)據(jù)訪問(wèn)代碼放在try塊中,并在catch塊中利用合適的過(guò)濾器捕捉生成的任何例外。例如,當(dāng)利用SQL Server .NET數(shù)據(jù)供給器編寫數(shù)據(jù)訪問(wèn)代碼時(shí),應(yīng)當(dāng)捕捉SqlException類型的異常,如下面的代碼所示:
try { // Data access code } catch (SqlException sqlex) // more specific { } catch (Exception ex) // less specific { } 假如為不止一個(gè)catch聲明提供了不同的過(guò)濾標(biāo)準(zhǔn),記住,按最非凡類型到最不非凡類型的順序排列它們。通過(guò)這種方式,catch塊中最非凡類型將將為任何給定的類型所執(zhí)行。
下面的代碼片段演示了如何利用SQL Server .NET數(shù)據(jù)供給器處理SQL Server 錯(cuò)誤狀態(tài):
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;
T-SQL提供了一個(gè)RAISERROR(注重拼寫)函數(shù)。你可用此函數(shù)生成定置錯(cuò)誤,并將錯(cuò)誤返回客戶。對(duì)于ADO.NET客戶,SQL Server .NET數(shù)據(jù)供給器對(duì)這些數(shù)據(jù)錯(cuò)誤進(jìn)行解釋,并把它們轉(zhuǎn)化為SqlError對(duì)象。
為了避免對(duì)消息文本進(jìn)行硬編碼,你可以利用sp_addmessage系統(tǒng)存儲(chǔ)過(guò)程或SQL Server 企業(yè)治理器將你自己的消息增加到sysmessages表中。然后你就可以使用傳遞到RAISERROR函數(shù)的ID引用消息了。你所定義的消息Ids必須大于50000,如下代碼片段所示:
仔細(xì)選擇錯(cuò)誤嚴(yán)重性等級(jí),并要清楚每個(gè)級(jí)別造成的沖擊。錯(cuò)誤嚴(yán)重性等級(jí)的范圍是0-25,并且它用于指出SQL Server 2000所碰到的問(wèn)題的類型。在客戶端代碼中,通過(guò)在SqlException類的Errors集合中檢查SqlError對(duì)象的 Class屬性,你可以獲得錯(cuò)誤的嚴(yán)重性。表1 指出了不同嚴(yán)重性等級(jí)的意義及所造成的沖擊。
表1.錯(cuò)誤嚴(yán)重性等級(jí)--沖擊及意義
嚴(yán)重性等級(jí) 鏈接已關(guān)閉 生成SqlException對(duì)象
意義
10及其以下 No No 通知型消息,并不表示犯錯(cuò)誤狀態(tài)。 11-16 No Yes 可由用戶修改的錯(cuò)誤,例如,使用修改后的輸入數(shù)據(jù)重試操作。 17-19 No Yes 資源或系統(tǒng)錯(cuò)誤。 20-25 Yes Yes 致命的系統(tǒng)錯(cuò)誤(包括硬件錯(cuò)誤)。客戶鏈接被終止。 控制自動(dòng)化事務(wù)
SQL Server .NET數(shù)據(jù)供給器對(duì)它所碰到的任何嚴(yán)重性大于10的錯(cuò)誤都拋出SqlException對(duì)象。當(dāng)作為自動(dòng)化(COM+)事務(wù)一部分的組件檢測(cè)到SqlException對(duì)象后,該組件必須確保它能取消事務(wù)。這也許是,也許不是自動(dòng)化過(guò)程,并要依靠該方法是否已經(jīng)對(duì)AutoComplete屬性作出了標(biāo)記。
public delegate void SqlInfoMessageEventHandler( object sender, SqlInfoMessageEventArgs e ); 通過(guò)傳遞到你的事件處理處理程序中的SqlInfoMessageEventArgs對(duì)象,可以得到消息數(shù)據(jù)。此對(duì)象暴露了Errors屬性,該屬性包含一組SqlError對(duì)象--每個(gè)通知消息一個(gè)SqlError對(duì)象。下面的代碼片段演示了如何注冊(cè)用于記錄通知型消息的事件處理程序。
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 . . . } } 性能
希望利用SQL Server FOR XML 語(yǔ)法的功能,這種語(yǔ)法答應(yīng)以靈活的方式從數(shù)據(jù)庫(kù)中得到XML片段(即,不帶根元素的XML文檔)。例如,這種方法使你能夠精確指定元素名,是使用元素還是使用以屬性為核心的圖解,圖解是否隨XML數(shù)據(jù)一起被返回,等等。
假如知道查詢結(jié)果只需返回一行,那么在調(diào)用SqlCommand對(duì)象的ExecuteReader 方法時(shí),使用CommandBehavior.SingleRow枚舉值。一些供給器,如OLE DB .NET數(shù)據(jù)供給器,用此技巧來(lái)優(yōu)化性能。例如,供給器使用IRow接口(假如此接口存在)而不是代價(jià)更高的IRowset接口。這個(gè)參數(shù)對(duì)SQL Server .NET數(shù)據(jù)供給器沒(méi)有影響。
當(dāng)通過(guò)防火墻建立鏈接時(shí),使用SQL Server TCP/IP網(wǎng)絡(luò)庫(kù)來(lái)簡(jiǎn)化配置,這是SQL Server2000安裝的默認(rèn)選項(xiàng)。假如使用先前版本的SQL Server,那么分別利用客戶端網(wǎng)絡(luò)應(yīng)用程序和服務(wù)器端網(wǎng)絡(luò)應(yīng)用程序檢查TCP/IP是否在客戶和服務(wù)器端已經(jīng)被配置為默認(rèn)的網(wǎng)絡(luò)庫(kù)。
SQL Server的默認(rèn)實(shí)例監(jiān)聽(tīng)1433端口。然而,SQL Server 2000的指定實(shí)例在它們首次開(kāi)啟時(shí),動(dòng)態(tài)地分配端口號(hào)。網(wǎng)絡(luò)治理員有希望在防火墻打開(kāi)一定范圍的端口;因此,當(dāng)隨防火墻使用SQL Server的指定實(shí)例時(shí),利用服務(wù)網(wǎng)絡(luò)應(yīng)用程序?qū)?shí)例進(jìn)行配置,使它監(jiān)聽(tīng)特定的端口。然后治理員對(duì)防火墻進(jìn)行配置,以使防火墻答應(yīng)流量到達(dá)特定的IP地址及服務(wù)器實(shí)例所監(jiān)聽(tīng)的端口。
假如改變了SQL Server 2000默認(rèn)實(shí)例的端口號(hào),那么不修改客戶端將導(dǎo)致鏈接錯(cuò)誤。假如存在多個(gè)SQL Server 實(shí)例,最新版本的MDAC數(shù)據(jù)訪問(wèn)堆棧(2.6)將進(jìn)行動(dòng)態(tài)查找,并利用用戶數(shù)據(jù)報(bào)協(xié)議(UDP)協(xié)商(通過(guò)UDP端口1434)對(duì)指定實(shí)例進(jìn)行定位。盡管這種方法在開(kāi)發(fā)環(huán)境下也許有效,但在現(xiàn)在環(huán)境中卻不大可能正常工作,因?yàn)榈湫桶l(fā)問(wèn)下防火墻阻止UDP協(xié)商流量的通過(guò)。
一個(gè)經(jīng)常使用的處理BLOB數(shù)據(jù)的可選方法是,將BLOB數(shù)據(jù)存儲(chǔ)在文件系統(tǒng)中,并在數(shù)據(jù)庫(kù)列中存儲(chǔ)一個(gè)指針(通常是一個(gè)統(tǒng)一資源定位器--URL鏈接)以引用正確的文件。對(duì)于SQL Server 7.0以前的版本,將BLOB數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫(kù)外的文件系統(tǒng)中,可以提高性能。
然而,SQL Server 2000改進(jìn)了BLOB支持,以及ADO.NET對(duì)讀取和寫入BLOB數(shù)據(jù)的支持,使在數(shù)據(jù)庫(kù)中存儲(chǔ)BLOB數(shù)據(jù)成為一種可行的方法。
// 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需要以嚴(yán)格的順序訪問(wèn)列數(shù)據(jù)。例如,假如BLOB數(shù)據(jù)存在于第3列,并且還需要從第1,2列中讀取數(shù)據(jù),那么在讀取第3列前必須先讀取第1,2列。
盡管自動(dòng)化事務(wù)處理模型極大地簡(jiǎn)化了分布式事務(wù)處理過(guò)程,但兩種模型都用于執(zhí)行本地事務(wù)處理(即對(duì)單個(gè)資源治理器如SQL Server 2000執(zhí)行的事務(wù)處理)或分布式事務(wù)處理(即,對(duì)位于遠(yuǎn)程計(jì)算機(jī)上的多個(gè)資源治理執(zhí)行的事務(wù)處理)。
[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方法
{ // 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 . . . } } 注重 假如有多個(gè)catch塊,在方法開(kāi)始的時(shí)候調(diào)用ContextVtil.SetAbort,以及在try塊的末尾調(diào)用ContextUtil.SetComplete都會(huì)變得輕易。用這種方法,就不需要在每個(gè)catch塊中重復(fù)調(diào)用ContextUtil.SetAbort。通過(guò)這種方法確定的相容標(biāo)志的設(shè)置只在方法返回時(shí)有效。
public int Fill( DataSet dataSet, int startRecord, int maxRecords, string srcTable ); StartRecord值標(biāo)示從零開(kāi)始的記錄起始索引值。MaxRecord值表示從startRecord開(kāi)始的記錄數(shù),并將拷貝到新的DataSet中。
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 這個(gè)存儲(chǔ)過(guò)程的調(diào)用程序僅僅維護(hù)LastProductID的值,并通過(guò)所選的連續(xù)調(diào)用之間的頁(yè)的大小增加或減小該值。
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 . 對(duì)于該表,結(jié)合Col 、Col2 和Col3就可能產(chǎn)生一種唯一性。這樣,就可以利用下面存儲(chǔ)過(guò)程中的方法實(shí)現(xiàn)分布原理:
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 客戶保持存儲(chǔ)過(guò)程返回的keyField欄的最后值,然后又插入回到存儲(chǔ)過(guò)程中以控制表的分頁(yè)。
public override void Construct( string constructString ) { // Construct method is called next after constructor. // The configured DSN is supplied as the single argument }
// 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)]
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來(lái)檢索多個(gè)行
下面的代碼說(shuō)明如何利用SqlDataAdapter對(duì)象發(fā)出一個(gè)生成Data Set或Datatable的命令。它從SQL Server Northwind數(shù)據(jù)庫(kù)中檢索一系列產(chǎn)品目錄。
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; } }
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 }
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(); } }
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(); } }
CREATE PROCEDURE DATGetProductDetailsReader @ProductID int AS SELECT ProductName, UnitPrice FROM Products WHERE ProductID = @ProductID GO 如何利用ExecuteScalar單個(gè)項(xiàng)
CREATE PROCEDURE LookupProductNameScalar @ProductID int AS SELECT TOP 1 ProductName FROM Products WHERE ProductID = @ProductID GO 如何利用存儲(chǔ)過(guò)程輸出或返回的參數(shù)檢索單個(gè)項(xiàng)
CREATE PROCEDURE CheckProductSP @ProductID int AS IF EXISTS( SELECT ProductID FROM Products WHERE ProductID = @ProductID ) return 1 ELSE return 0 GO 如何利用SqlDataReader檢索單個(gè)項(xiàng)。
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; } } } }
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 該存儲(chǔ)過(guò)程使用BEGIN TRANSACTION, COMMIT TRANSACTION,和ROLLBACK TRANSACTION狀態(tài)手工控制事務(wù)。