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



The information in this article applies to:

  • Microsoft ADO.NET (included with the .NET Framework) 1.0
  • Microsoft ADO.NET (included with the .NET Framework 1.1)
  • Microsoft Visual C# .NET (2002)
  • Microsoft Visual C# .NET (2003)
  • 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

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.
  2. Derive your class from the ServicedComponent class. ServicedComponent is the base class of all classes that use COM+ services.
  3. Sign the assembly with a strong name to make sure that the assembly contains a unique key pair.
  4. 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: For more information about this process, see the following topic in the Microsoft .NET Framework Developer's Guide: back to the top

Create the Project

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

  4. In the AssemblyInfo.cs file, comment out the following lines of code:
    [assembly: AssemblyKeyFile("")]
    [assembly: AssemblyKeyName("")]
    					
  5. 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("DistributedTransaction")]
    [assembly: AssemblyKeyFileAttribute("..\..\DistributedTransaction.snk")]
    
    
    namespace DistributedTransaction
    {
       /// <summary>
       /// Summary description for Class1.
       /// </summary>
       class Class1
       {
          /// <summary>
          /// The main entry point for the application.
          /// </summary>
          [STAThread]
          static void Main(string[] args)
          {
             try
             {
                DistributedTran myDistributedTran = new DistributedTran();
                myDistributedTran.TestDistributedTransaction();
             }
             catch (System.Data.SqlClient.SqlException e)
             {
                System.Console.WriteLine("Transaction Aborted: Error returned: " + e.Message);
             }         
          }
       }
       /// <summary>
       /// Summary description for TestApp.
       /// </summary>
       [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 = "Insert Into TestTransaction (Col1, Col2) Values (1,'Sql Test')";
    
             // The following Delete statement goes to the second server.
             // Because the table does not exist, this code throws an exception.
             String exceptionCausingCmdSQL = "Delete from NonExistentTable";
                         
             // 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("Server=Server_Name;uid=User_Id;database=DatabaseName;pwd=Password");
             SqlConnection SqlConn2 = new SqlConnection("Server=Server_Name;uid=User_Id;database=DatabaseName;pwd=Password");
    
             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("Hello");
                
             }
             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() == "Open")
                   SqlConn1.Close();
    
                if (SqlConn2.State.ToString() == "Open")
                   SqlConn2.Close();
             }
    
             return "Success";
           
          }
    
       }
    }
    					
  6. On the File menu, click Save All.
  7. 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.
  8. 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

  9. Build your application.
  10. 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 
    					
  11. Run your application. Notice that you receive the following error message (which is the expected behavior):
    Transaction Aborted: Error returned: Invalid object name 'NonExistentTable'.
  12. 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.
  13. Locate the following code in your Visual C# project:
    String exceptionCausingCmdSQL = "Delete from NonExistentTable";
    						
    and replace the SQL statement with a valid query that does not cause the transaction to abort. For example:
    String exceptionCausingCmdSQL = "Select @@Identity from customers";
    					
  14. Press F5 to compile and to run the application again.
  15. 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.
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

REFERENCES

For additional information, click the article number below to view the article in the Microsoft Knowledge Base:

306296 HOW TO: Create a Serviced .NET Component That Uses Transactions in Visual C# .NET


Modification Type:MajorLast Reviewed:9/4/2003
Keywords:kbCompiler kbHOWTOmaster kbSqlClient kbSystemData KB316247 kbAudDeveloper