Microsoft KB Archive/816141

= How to use COM+ transactions in a Visual C# component =

Article ID: 816141

Article Last Modified on 11/22/2007

-

APPLIES TO


 * Microsoft Visual C# 2005 Express Edition
 * Microsoft Visual C# .NET 2003 Standard Edition
 * Microsoft Visual C# .NET 2002 Standard Edition

-





For a Microsoft Visual Basic .NET version of this article, see 315707.

IN THIS TASK

 * SUMMARY
 * Requirements
 * COM+ Transaction Services
 * Complete Code Listing
 * Verify That It Works
 * Troubleshoot
 * REFERENCES



SUMMARY
This step-by-step article describes how to use COM+ (Component Services) transactions in a Visual C# class. A set of database operations is considered one unit. Either all operations succeed or, if one operation fails, the whole transaction fails. In the latter case, any database operations that were tried are not posted to the underlying database.

back to the top

Requirements
The following items describe the recommended hardware, software, network infrastructure, skills and knowledge, and service packs that you must have:
 * Microsoft Windows 2000 Server SP1
 * Microsoft Internet Information Services (IIS) version 4.0 or later
 * Microsoft Internet Explorer versions 5.0, 5.5, or 6.0

This article assumes that you are familiar with the following:
 * Transactional concepts and processing
 * COM+ (Component Services)

back to the top

COM+ Transaction Services
You can implement transaction processing with the System.EnterpriseServices namespace in the Microsoft .NET Framework. To access COM+ transactional services, create a class. To do this, follow these steps:  Start Visual Studio .NET or Visual Studio 2005. On the File menu, point to New, and then click Project. Click Visual C# Projects under Project Types, and then click Class Library under Templates. Name the project prjEnterprise.

Note In Visual Studio 2005, click Visual C# under Project Types, and then click Class Library under Templates. Name the project prjEnterprise . By default, Class1 is created. In Solution Explorer, right-click References, and then click Add Reference. The Add Reference dialog box appears. Under Component Name on the .NET tab, double-click System.EnterpriseServices. Make sure that System.EnterpriseServices appears under Selected Components. Click OK.</li>  Add the following code before to any other statements in the Class1.cs file: using System.EnterpriseServices; using System.Data.SqlClient; </li> Add a new class that is named clsES to the Class1.cs file.</li>  To use COM+ Transactional Services, your class (clsES) must inherit functionality from ServicedComponent as follows: public class clsES : ServicedComponent </li>  A Transaction attribute is used to specify the level of transactional support for the class as follows: [Transaction(TransactionOption.Required)]public class clsES : ServicedComponent </li>  Create a method in the clsES class, and name it dbAccess that receives four input integer parameters. The first two parameters provide a product ID, and the units on order for that product. The second two parameters provide a product ID, and the units in stock for that product. This method performs a set of database operations against these specified product IDs that are to be treated as a transaction: void dbAccess(int pID1,int onOrder, int pID2, int inStock) </li>  In the dbAccess method, create a SQL connection object for the Northwind database, and then open the connection. Database operations occur by using the following database:

Note Remember to change the following connection string parameters to reflect the correct values for your SQL Server server.

SqlConnection Conn = new SqlConnection(&quot;user id= ;password= ;Initial Catalog=northwind;Data Source=2E124\\SQL;&quot;); Conn.Open; </li>  Set a try block to capture any exceptions that might occur during database processing. You must catch these exceptions to abort the transaction. The try block includes two database operations. Each operation updates a different field in a specified products table record. try { </li>  Perform the first update to the products table. Update the UnitsonOrder field with the onOrder value for product with ID, as specified in the first two input parameters. Use the following SQL command to run this SQL update: SqlCommand sqlCommand = new SqlCommand(&quot;UPDATE myProducts SET UnitsonOrder = &quot; + onOrder + &quot; WHERE productID = &quot; + pID1, Conn); sqlCommand.ExecuteNonQuery; </li>  Perform another update to the products table. Update the UnitsinStock field with the inStock value for product with ID, as specified in the third and fourth input parameters. Use the following SQL command to run this SQL update: sqlCommand.CommandText = &quot;UPDATE myProducts SET UnitsinStock = &quot; + inStock + &quot; WHERE productID = &quot; + pID2; sqlCommand.ExecuteNonQuery; </li>  Because these updates are part of a COM+ transaction, they are committed as a unit. The setComplete method of the contextUtil class from the System.EnterpriseServices namespace is used to commit the transaction (in this case the two updates) if no errors were thrown: ContextUtil.SetComplete; </li>  The connection to the Northwind database is closed: Conn.Close; } </li>  You must catch any exceptions that occur while running the SQL commands so that you can abort the whole transaction: catch(Exception e){ </li>  The setAbort method of the contextUtil class from the System.EnterpriseServices namespace is used to abort the whole transaction. If the first update is successful and the second update fails, neither update is posted to the products table. The caught exception is thrown to the caller, indicating that the transaction failed: ContextUtil.SetAbort; throw e; } </li> For this component to function correctly, the component must have a strong name. Generate a strong name, and then sign the assembly with the strong name. To do this, follow these steps: <ol style="list-style-type: lower-alpha;"> At the Visual Studio .NET command prompt, type sn.exe -k snEnterprise.snk to create a key file. For more information about signing assemblies with strong names, see the .NET Framework SDK documentation.</li> <li>Copy snEnterprise.snk to your project folder.</li> <li> In AssemblyInfo.vc, add the following line of code before or after other assembly attribute statements: [assembly: AssemblyKeyFileAttribute(&quot;..\\..\\snEnterprise.snk&quot;)]      </li> <li>Save, and then build your project.</li></ol> </li></ol>

back to the top

Complete Code Listing
Note Remember to change the following connection string parameters to reflect the correct values for your SQL Server server. using System; using System.Data; using System.Data.SqlTypes; using System.Data.Common; using System.EnterpriseServices; using System.Data.SqlClient;

namespace prjEnterprise {   [Transaction(TransactionOption.Required)]public class clsES:ServicedComponent {       public SqlConnection Conn;

public void dbAccess(int pID1, int onOrder, int pID2, int inStock) {           try {                          SqlConnection Conn = new SqlConnection(&quot;user id= ;password= ;Initial Catalog=northwind;Data Source=2E124\\SQL;&quot;); Conn.Open; SqlCommand sqlCommand = new SqlCommand(&quot;UPDATE myProducts SET UnitsonOrder = &quot; + onOrder + &quot; WHERE productID = &quot; + pID1, Conn); sqlCommand.ExecuteNonQuery; sqlCommand.CommandText = &quot;UPDATE myProducts SET UnitsinStock = &quot; + inStock + &quot; WHERE productID = &quot; + pID2; sqlCommand.ExecuteNonQuery;

ContextUtil.SetComplete; Conn.Close; }           catch(Exception e)            { ContextUtil.SetAbort; throw e;           } finally {

}       }    }   }

back to the top

Verify That It Works
To test this code, create a console application that uses the clsES project. In one case, a transaction succeeds, and the onorder and instock fields for the specified product are updated. In the second case, the update for the onOrder field for a specified product succeeds, but the update for the inStock field for a product fails because the specified product number does not exist in the Products table. This results in a transaction failure, and the transaction is ignored. <ol> <li>In Visual Studio .NET or Visual Studio 2005, point to New on the File menu, and then click Project.</li> <li>Click Visual C# Projects under Project Types, and then click Console Application under Templates.

Note In Visual Studio 2005, click Visual C# under Project Types, and then click Console Application under Templates.</li> <li>In the Name text box, type testES. Make sure that the Add to Solution option is selected.</li> <li>Click OK to add this project to the solution.</li> <li>For testES to test clsES, you must add a reference. In Solution Explorer, right-click References under testES (that you just added), and then click Add Reference.</li> <li>The Add Reference dialog box appears. On the Projects tab, double-click prjEnterprise.</li> <li>A reference appears under Selected Components. Click OK to add this reference to the project.</li> <li>Add a reference to the project to the System.EnterpriseServices library. In Solution Explorer, right-click References, and then click Add Reference .</li> <li>The Add Reference dialog box appears. Under Component Name on the .NET tab, double-click System.EnterpriseServices.</li> <li>Make sure that System.EnterpriseServices appears under Selected Components. Click OK.</li> <li>Right-click the console application ( testES ), and then click Set as Startup Project</li> <li> Paste the following source code in the Main function of the Class1 class : prjEnterprise.clsES myTest = new prjEnterprise.clsES;

try {                   myTest.dbAccess(1, 777, 2, 888); Console.WriteLine(&quot;TRANSACTION ONE -- SUCCESS&quot;);

myTest.dbAccess(1, 5, 2, -20); Console.WriteLine(&quot;TRANSACTION TWO -- SUCCESS&quot;); }               catch (Exception e)                { Console.WriteLine(&quot;TRANSACTION FAILURE&quot;); } </li> <li>Press F5 to run the test code.

In the code in step 7, the first call to dbAccess succeeds. Product 1 and Product 2 are in the Products table. The onOrder field for Product 1 is updated to 777, and the inStock field for Product 2 is updated to 888. Because this transaction succeeded, you receive the following message in the output window

TRANSACTION ONE - SUCCESS

The second call to dbAccess fails. Therefore, neither update statement in dbAccess to the Products table is posted to the database. Although Product 1 could have its onOrder field updated to 5, Product 2 cannot have its inStock field set to -20. Because of a constraint that is defined in the Product table definition, inStock is not permitted to have negative numbers.

Therefore, this call to dbAccess fails, and the whole transaction fails. The Products table remains as it was before the call to dbAccess. The catch statement handles notification of the transaction failure from dbAccess, and you receive the following error message in the output window:

TRANSACTION FAILURE

</li> <li>Examine the contents of the Northwind Products table by using SQL Server Enterprise Manager. When you view product 1, the onOrder field is equal to 777. When you view Product 2, the instock field is 888. Therefore, the second call to dbAccess (which would have resulted in different values for these fields) fails.</li></ol>

back to the top

Troubleshoot
<ul> <li>Make sure that any project that uses COM+ services has a strong name.</li> <li>Any class that uses COM+ services must inherit from the serviced component. The serviced component is located in the System.EnterpriseServices namespace.</li> <li> While debugging, a transaction may time out before it is committed or aborted. To avoid a timeout, use a timeout property on the transaction attribute. In the following example, the associated method has 1,200 seconds to complete any transaction before it times out: [Transaction(TransactionOption.Required,timeout=1200)] </li></ul>

back to the top

<div class="references_section">