本文來源于網頁設計愛好者web開發社區http://www.html.org.cn收集整理,歡迎訪問。使用自動化事務 
自動化事務簡化了編程模型,因為它們不需要明確地開始新事務處理過程,或明確執行或取消事務。然而,自動化事務的最大優點是它們能與dtc結合起來,這就使單個事務可以擴展到多個分布式數據源中。在大型分布式應用程序中,這個優點是很重要的。盡管通過手工對dtc直接編程來控制分布式事務是可能的,但自動化事務處理極大的簡化了工作量,并且它是為基于組件的系統而設計的。例如,可以方便地以說明方式配置多個組件以執行包含了單個事務處理的任務。 
自動化事務依賴于com+提供的分布式事務處理支持特性。結果,只有服務組件(即從servicedcomponent類中派生的組件)能夠使用自動化事務。 
要為自動化事務處理配置類,操作如下: 
從位于enterpriseservices名稱空間的servicedcomponent類中派生新類。 
通過transaction屬性定義類的事務處理需求。來自transactionoption的枚舉值決定了如何在com+類中配置類。可與此屬性一同設置的其它屬性包括事務處理分離等級和超時上限。 
為了避免必須明確選出事務處理結果,可以用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
{
 . . .
}
更多信息 
關于配置事務處理分離等級及其它windows .net com+增強特性的更多信息,見msdn雜志2001年8月期的“windows xp:利用com+ 1.5的增強特性使你的組件更強壯”一文。 
確定事務處理結果 
在單個事務流的所有事務處理組件上下文中,自動化事務處理結果由事務取消標志和一致性標志的狀態決定。當事務流中的根組件成為非活動狀態(并且控制權返回調用者)時,確定事務處理結果。這種情況在圖5中得到了演示,此圖顯示的是一個典型的銀行基金傳送事務。 
 
圖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欄的最后值,然后又插入回到存儲過程中以控制表的分頁。 
雖然手工實現增加了數據庫服務器上的應變,但它避免了在網絡上傳輸不必要的數據。性能測試表明在整個應變水平中這種方法都工作良好。然而,根據站點工作所涉及的數據分頁功能的多少,在服務器上進行手工分頁可能影響應用程序的可擴展性。應該在所在環境中運行性能測試,為應用程序找到最合適的方法。 
附錄 
如何為一個.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+應用程序的名字。關于匯編注冊的更多信息,參考: http://msdn.microsoft.com/library/en-us/cpguide/html/cpconregisteringserviced components.asp。 
// 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
如何編碼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
 }
 }
}
合作者 
非常感謝下列撰稿者和審校者: 
bill vaughn, mike pizzo, doug rothaus, kevin white, blaine dokter, david schleifer, graeme malcolm(內容專家), bernard chen(西班牙人), matt drucke(協調)和steve kirk. 
讀者有什么樣的問題、評論和建議?關于本文的反饋信息,請發e-mail至devfdbck®microsoft.com。 
你希望學習并利用.net的強大功能嗎?與微軟技術中心的技術專家一起工作,學習開發最佳方案。