How to use COM+ transactions in a Visual C++ .NET component or in a Visual C++ 2005 component (815814)
The information in this article applies to:
- Microsoft Visual C++ 2005 Express Edition
- Microsoft Visual C++ .NET (2003)
For a Microsoft C# version of this article, see
816141. For a Microsoft Visual Basic .NET version of this
article, see
315707. IN THIS TASKSUMMARYThis
step-by-step article describes how to use COM+ (Component Services)
transactions in a Microsoft Visual C++ .NET class or in a Microsoft Visual C++ 2005 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 topRequirementsThis article assumes that you are familiar with the following topics: - 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.
- Under
Project Types, click Visual C++ Projects, and then click Class Library
(.NET) under Templates. Name the project
prjEnterprise.
Note In Visual Studio 2005, click Visual C++ under Project Types, and then click Class Library under Templates. - In Solution Explorer, right-click
References, and then click Add Reference.
- Click the NET tab in the Add Reference dialog box.
- Double-click System.EnterpriseServices under Component Name.
- Make sure that System.EnterpriseServices
appears under Selected Components, and then click OK.
- Add the following code before any other statements in
the prjEnterprise.h file:
using namespace System;
using namespace System::Data;
using namespace System::Data::SqlTypes;
using namespace System::Data::Common;
using namespace System::EnterpriseServices;
using namespace System::Data::SqlClient;
- Add a new class that is named clsES
to the prjEnterprise.h file.
- To use COM+ Transactional Services, make sure that your class (clsES) inherits functionality from the ServicedComponent class as follows:
public __gc class clsES:public ServicedComponent
- Use a Transaction attribute to specify the level of transactional support
for the class as follows:
[Transaction(TransactionOption::Required,Timeout=5)]public __gc class clsES:public ServicedComponent
- Create a method in the clsES class that receives
four input integer parameters. Name the class dbAccess. 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:
public: void dbAccess(int pID1, int onOrder, int pID2, int inStock) - 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:
SqlConnection * Conn = new SqlConnection("user id=<username>;password=<password>;Initial Catalog=northwind;Data Source=<Your SQL Server name>;");
Conn->Open(); Note Remember to change the connection string parameters to
reflect the correct values for your SQL Server server. - 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 {
- 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("UPDATE Products SET UnitsonOrder = @onOrderString WHERE productID = @pID1String ", Conn);
sqlCommand->Parameters->Add(new SqlParameter("@onOrderString",SqlDbType::VarChar ,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,onOrder.ToString()));
sqlCommand->Parameters->Add(new SqlParameter("@pID1String", SqlDbType::VarChar,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,pID1.ToString()));
sqlCommand->ExecuteNonQuery();
- 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->Parameters->Add(new SqlParameter("@inStockString",SqlDbType::VarChar ,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,inStock.ToString()));
sqlCommand->Parameters->Add(new SqlParameter("@pID2String", SqlDbType::VarChar,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,pID2.ToString()));
sqlCommand->ExecuteNonQuery();
- 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();
- Use the following code to close the connection to the Northwind database:
Conn->Close(); }
- You must catch any exceptions that occur while running the
SQL commands so that you can abort the whole transaction:
catch(Exception * e){
- 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; }
- 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:
- 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.
- Copy snEnterprise.snk to your project folder.
- In AssemblyInfo.vc, add the following line of code
before or after other assembly attribute statements:
[assembly:AssemblyKeyFileAttribute("..\\snEnterprise.snk")]; - Save, and then build your project.
back to the topComplete code listingNote Remember to change your connection string parameters to
reflect the correct values for your SQL Server server.
#pragma once
using namespace System;
using namespace System::Data;
using namespace System::Data::SqlTypes;
using namespace System::Data::Common;
using namespace System::EnterpriseServices;
using namespace System::Data::SqlClient;
namespace prjEnterprise
{
[Transaction(TransactionOption::Required,Timeout=5)]
public __gc class clsES:public ServicedComponent
{
public: SqlConnection * Conn;
public: void dbAccess(int pID1, int onOrder, int pID2, int inStock)
{
try
{
SqlConnection * Conn = new SqlConnection("user id=<username>;password=<password>;Initial Catalog=northwind;Data Source=<Your SQL Server name>;");
Conn->Open();
SqlCommand * sqlCommand = new SqlCommand("UPDATE Products SET UnitsonOrder = @onOrderString WHERE productID = @pID1String ", Conn);
sqlCommand->Parameters->Add(new SqlParameter("@onOrderString",SqlDbType::VarChar ,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,onOrder.ToString()));
sqlCommand->Parameters->Add(new SqlParameter("@pID1String", SqlDbType::VarChar,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,pID1.ToString()));
sqlCommand->ExecuteNonQuery();
sqlCommand->CommandText = "UPDATE Products SET UnitsinStock = @inStockString WHERE productID = @pID2String" ;
sqlCommand->Parameters->Add(new SqlParameter("@inStockString",SqlDbType::VarChar ,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,inStock.ToString()));
sqlCommand->Parameters->Add(new SqlParameter("@pID2String", SqlDbType::VarChar,
40,ParameterDirection::Input,true,0,0,"Description",DataRowVersion::Current,pID2.ToString()));
sqlCommand->ExecuteNonQuery();
ContextUtil::SetComplete();
Conn->Close();
}
catch(Exception * e)
{
ContextUtil::SetAbort();
throw e;
}
}
};
} Note You must add the common language runtime support compiler option (/clr:oldSyntax) in Visual C++ 2005 to successfully compile the previous code sample.
To add the common language runtime support compiler option in Visual C++ 2005, follow these steps:
- Click Project, and then click <ProjectName> Properties.
Note <ProjectName> is a placeholder for the name of the project. - Expand Configuration Properties, and then click General.
- Click to select Common Language Runtime Support, Old Syntax (/clr:oldSyntax) in the Common Language Runtime support project setting in the right pane, click Apply, and then click OK.
For more information about the common language runtime support compiler option, visit the following Microsoft Web site:
back to the topVerify that it worksTo test this code, create a Console Application project that uses the clsES class. In one case, a transaction succeeds, and the UnitsOnOrder and UnitsInStock fields for the specified product are updated. In the second case,
the update for the UnitsOnOrder field for a specified product succeeds, but the update for the UnitsInStock field for a product fails because the specified product number
does not exist in the Products table. This causes a transaction failure,
and the transaction is ignored.
- Start Visual Studio .NET or Visual Studio 2005.
- On the File menu, point to New, and then click Project.
- Under
Project Types, click Visual C++ Projects, and then click Console Application
(.NET) under Templates.
Note In Visual Studio 2005, click Visual C++ under Project Types, and then click CLR Console Application under Templates. - In the Name text box, type
testES. Make sure that the Add to
Solution option is selected.
- Click OK to add this project to the
solution.
- For testES to test prjEnterprise, you must add a reference. In Solution Explorer,
right-click References under testES (that you
just added), and then click Add Reference.
- The Add Reference dialog box appears. On
the Projects tab, double-click prjEnterprise.
- A reference appears under Selected
Components. Click OK to add this reference to the
project.
- Add a reference to the project to the System.EnterpriseServices namespace. 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.
- Right-click testES, and then
click Set as Startup Project.
- Paste the following source code in the _tmain() function of the testES class:
prjEnterprise::clsES * myTest = new prjEnterprise::clsES();
try
{
myTest->dbAccess(1, 777, 2, 888);
Console::WriteLine("TRANSACTION ONE -- SUCCESS");
myTest->dbAccess(1, 5, 2, -20);
Console::WriteLine("TRANSACTION TWO -- SUCCESS");
}
catch (Exception * e)
{
Console::WriteLine("TRANSACTION FAILURE");
//Console::WriteLine("Error Message: {0}",e->Message);
//uncomment this line if you must get detailed error information
}
- 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 UnitsOnOrder field updated to 5, Product 2 cannot have its UnitsInStock field set to -20. Because of a constraint that is defined in the Product table
definition, UnitsInStock 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 - Examine the contents of the Northwind Products table by
using SQL Server Enterprise Manager. When you view product 1, the UnitsOnOrder field is equal to 777. When you view Product 2, the UnitsInStock field is 888. Therefore, the second call to dbAccess (which would have resulted in different values for these fields)
fails.
back to the topTroubleshooting- Make sure that any project that uses COM+ services has a
strong name.
- Any class that uses COM+ services must inherit from the
serviced component. The serviced component is located in the System.EnterpriseServices namespace.
- 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)]
- You may receive some unexpected exceptions when running the
code. To receive more information about these exceptions, uncomment the last two lines in step 13:
Console::WriteLine("Error Message: {0}",e->Message);
uncomment this line if you must get detailed error information back to the
topREFERENCESFor additional information, visit the following
Microsoft Developer Network (MSDN) Web sites: back to the
top
Modification Type: | Major | Last Reviewed: | 1/5/2006 |
---|
Keywords: | kbSqlClient kbComPlusQC kbcode kbHOWTOmaster kbhowto KB815814 kbAudDeveloper kbAudITPRO |
---|
|
|
©2004 Microsoft Corporation. All rights reserved.
|
|