Microsoft KB Archive/316247

= HOW TO: Perform a Distributed Transaction with a .NET Provider by Using ServicedComponent in Visual C# .NET =

Article ID: 316247

Article Last Modified on 9/4/2003

-

APPLIES TO


 * Microsoft ADO.NET 1.0
 * Microsoft ADO.NET 1.1
 * Microsoft Visual C# .NET 2002 Standard Edition
 * Microsoft Visual C# .NET 2003 Standard Edition
 * Microsoft Enterprise Services (included with the .NET Framework) 1.0
 * Microsoft Enterprise Services (included with the .NET Framework 1.1)

-



This article was previously published under Q316247



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

This article refers to the following Microsoft .NET Framework Class Library namespaces:
 * System.Data.SqlClient
 * System.EnterpriseServices
 * System.Runtime.CompilerServices
 * System.Reflection

IN THIS TASK
SUMMARY
 * Requirements
 * Overview
 * Create the Project

REFERENCES



SUMMARY
This step-by-step article demonstrates how to perform a distributed transaction by using a .NET provider with the ServicedComponent class. Although this article uses the SqlClient .NET provider against a Microsoft SQL Server server, you can also use the ODBC or OLE DB .NET managed provider.

back to the top

Requirements
The following list outlines the recommended hardware, software, network infrastructure, and service packs that are required:
 * Microsoft Windows 2000 Professional, Microsoft Windows 2000 Server, Microsoft Windows 2000 Advanced Server
 * Microsoft Visual Studio .NET
 * Microsoft SQL Server 7.0 or Microsoft SQL Server 2000

back to the top

Overview
Instances of a .NET Framework class can participate in an automatic transaction if you prepare the class to do this. Each resource that a class instance or an object accesses enlists in the transaction. For example, if an object uses ADO.NET to post money on an account in a database, the resource manager for the database determines whether the object runs in a transaction. If the object should run in a transaction, the resource manager automatically enlists the database in the transaction.

Use the following process to prepare a class to participate in an automatic transaction:
 * 1) Apply the TransactionAttribute class to your class to specify the automatic transaction type that the component requests.

The transaction type must be a member of the TransactionOption enumeration.
 * 1) Derive your class from the ServicedComponent class. ServicedComponent is the base class of all classes that use COM+ services.
 * 2) Sign the assembly with a strong name to make sure that the assembly contains a unique key pair.
 * 3) Register the assembly that contains your class with the COM+ catalog.

NOTE: If the client that calls an instance of your class is managed by the common language runtime, the registration is performed for you. This step is required only if an unmanaged caller creates and calls instances of your class. Use the .NET Services Installation Tool (Regsvcs.exe) to manually register the assembly.

For more information about how to sign an assembly with a strong name, see the following topic in the Microsoft .NET Framework Developer's Guide:

Signing an Assembly with a Strong Name

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconassigningassemblystrongname.asp

For more information about this process, see the following topic in the Microsoft .NET Framework Developer's Guide:

Automatic Transactions and .NET Framework Classes

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconautomatictransactionsnetframeworkclasses.asp

back to the top

Create the Project
 Follow these steps to create a new Console Application project in Visual C# .NET:  Start Visual Studio .NET. On the File menu, point to New, and then click Project. In the New Project dialog box, click Visual C# Projects under Project Types, click Console Application under Templates, and then click OK. In Solution Explorer, rename the Class1.cs file as DistributedTransaction.cs.  Delete all of the code from the DistributedTransaction.cs file.</li> On the Project menu, click Add Reference, and then add the following references: <ul> System.EnterpriseServices</li> System.Data.dll</li></ul>

</li>  In the AssemblyInfo.cs file, comment out the following lines of code: [assembly: AssemblyKeyFile(&quot;&quot;)] [assembly: AssemblyKeyName(&quot;&quot;)] </li>  Add the following code to the DistributedTransaction.cs file: using System; using System.Data.SqlClient; using System.EnterpriseServices; using System.Runtime.CompilerServices; using System.Reflection;

[assembly: ApplicationName(&quot;DistributedTransaction&quot;)] [assembly: AssemblyKeyFileAttribute(&quot;..\..\DistributedTransaction.snk&quot;)]

namespace DistributedTransaction {  ///    /// Summary description for Class1. ///   class Class1 {     ///       /// The main entry point for the application. ///      [STAThread] static void Main(string[] args) {        try {           DistributedTran myDistributedTran = new DistributedTran; myDistributedTran.TestDistributedTransaction; }        catch (System.Data.SqlClient.SqlException e)         { System.Console.WriteLine(&quot;Transaction Aborted: Error returned: &quot; + e.Message); }              }   }   ///    /// Summary description for TestApp. ///   [Transaction(TransactionOption.Required)] public class DistributedTran: ServicedComponent {     public DistributedTran {     }      [AutoComplete] public string TestDistributedTransaction {        // The following Insert statement goes to the first server. // This Insert statement does not produce any errors. String insertCmdSql = &quot;Insert Into TestTransaction (Col1, Col2) Values (1,'Sql Test')&quot;;

// The following Delete statement goes to the second server. // Because the table does not exist, this code throws an exception. String exceptionCausingCmdSQL = &quot;Delete from NonExistentTable&quot;; // The following connection strings create instances of two SqlConnection objects // to connect to two different SQL Server servers in your environment. // Modify the connection strings as necessary for your environment. SqlConnection SqlConn1 = new SqlConnection(&quot;Server=Server_Name;uid=User_Id;database=DatabaseName;pwd=Password&quot;); SqlConnection SqlConn2 = new SqlConnection(&quot;Server=Server_Name;uid=User_Id;database=DatabaseName;pwd=Password&quot;);

try {           SqlCommand insertCmd = new SqlCommand(insertCmdSql,SqlConn1); SqlCommand exceptionCausingCmd = new SqlCommand(exceptionCausingCmdSQL,SqlConn2); // This command runs properly. insertCmd.Connection.Open; insertCmd.ExecuteNonQuery;

// This command results in an exception, which automatically rolls back // the first command (the insertCmd command). exceptionCausingCmd.Connection.Open; int cmdResult = exceptionCausingCmd.ExecuteNonQuery; SqlConn1.Close; SqlConn2.Close;

Console.WriteLine(&quot;Hello&quot;); }        catch (System.Data.SqlClient.SqlException ex) {           // After you catch the exception in this function, throw it. // The service component receives this exception and // aborts the transaction. The service component then // throws the same exception, and the calling function // receives the error message. Console.WriteLine (ex.Message); throw (ex); }        finally {           // Close the connection. if (SqlConn1.State.ToString == &quot;Open&quot;) SqlConn1.Close;

if (SqlConn2.State.ToString == &quot;Open&quot;) SqlConn2.Close; }

return &quot;Success&quot;; }

} }                   </li> On the File menu, click Save All.</li> Click Start, point to Programs, point to Microsoft Visual Studio .NET, point to Visual Studio .NET Tools, and then click Visual Studio .NET Command Prompt.</li> Open the folder that contains your project, and then run the following command to sign the assembly with a strong name:

sn -k DistributedTransaction.snk

</li> Build your application.</li>  Create the following table in the first SQL Server server: if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[TestTransaction]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [dbo].[TestTransaction] GO

CREATE TABLE [dbo].[TestTransaction] (   [Col1] [int] NULL,    [Col2] [varchar] (100) NULL ) ON [PRIMARY] GO </li> Run your application. Notice that you receive the following error message (which is the expected behavior):

Transaction Aborted: Error returned: Invalid object name 'NonExistentTable'.

</li>  Open the SQL Server Query Analyzer, add the following code, and then press F5 to run the query: USE NORTHWIND; SELECT * FROM TestTransaction WHERE Col1=1 AND Col2='Sql Test' Note that the query does not return any rows because the transaction was aborted. </li>  Locate the following code in your Visual C# project: String exceptionCausingCmdSQL = &quot;Delete from NonExistentTable&quot;; and replace the SQL statement with a valid query that does not cause the transaction to abort. For example: String exceptionCausingCmdSQL = &quot;Select @@Identity from customers&quot;; </li> Press F5 to compile and to run the application again.</li> Run the command from step 12 in Query Analyzer again. Notice that the query returns a row because the transaction was able to complete successfully.</li></ol>

NOTES:
 * This example does not perform error handling.
 * SQL Server and Microsoft Distributed Transaction Coordinator (MS DTC) must be running on all clients and servers.

back to the top

<div class="references_section">