The TransactionScope class provides a simple way to mark a block of code as participating in a transaction, without requiring you to interact with the transaction itself. A transaction scope can select and manage the ambient transaction automatically. Due to its ease of use and efficiency, it is recommended that you use the TransactionScope class when developing a transaction application.
The following sample shows a simple usage of the TransactionScope class.
- // This function takes arguments for 2 connection strings and commands to create a transaction
- // involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
- // transaction is rolled back. To test this code, you can connect to two different databases
- // on the same server by altering the connection string, or to another 3rd party RDBMS by
- // altering the code in the connection2 code block.
- static public int CreateTransactionScope(
- string connectString1, string connectString2,
- string commandText1, string commandText2)
- {
- // Initialize the return value to zero and create a StringWriter to display results.
- int returnValue = 0;
- System.IO.StringWriter writer = new System.IO.StringWriter();
- try
- {
- // Create the TransactionScope to execute the commands, guaranteeing
- // that both commands can commit or roll back as a single unit of work.
- using (TransactionScope scope = new TransactionScope())
- {
- using (SqlConnection connection1 = new SqlConnection(connectString1))
- {
- // Opening the connection automatically enlists it in the
- // TransactionScope as a lightweight transaction.
- connection1.Open();
- // Create the SqlCommand object and execute the first command.
- SqlCommand command1 = new SqlCommand(commandText1, connection1);
- returnValue = command1.ExecuteNonQuery();
- writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
- // If you get here, this means that command1 succeeded. By nesting
- // the using block for connection2 inside that of connection1, you
- // conserve server and network resources as connection2 is opened
- // only when there is a chance that the transaction can commit.
- using (SqlConnection connection2 = new SqlConnection(connectString2))
- {
- // The transaction is escalated to a full distributed
- // transaction when connection2 is opened.
- connection2.Open();
- // Execute the second command in the second database.
- returnValue = 0;
- SqlCommand command2 = new SqlCommand(commandText2, connection2);
- returnValue = command2.ExecuteNonQuery();
- writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
- }
- }
- // The Complete method commits the transaction. If an exception has been thrown,
- // Complete is not called and the transaction is rolled back.
- scope.Complete();
- }
- }
- catch (TransactionAbortedException ex)
- {
- writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
- }
- catch (ApplicationException ex)
- {
- writer.WriteLine("ApplicationException Message: {0}", ex.Message);
- }
- // Display messages.
- Console.WriteLine(writer.ToString());
- return returnValue;
- }
The transaction scope is started once you create a new TransactionScope object. As illustrated in the code sample, it is recommended that you create scopes with a using statement. The using statement is available both in C# and in Visual Basic, and works like a try…finally block to ensure that the scope is disposed of properly.
When your application completes all the work it wants to perform in a transaction, you should call the Complete method only once to inform the transaction manager that it is acceptable to commit the transaction. It is very good practice to put the call to Complete as the last statement in the using block.
Failing to call this method aborts the transaction, because the transaction manager interprets this as a system failure, or equivalent to an exception thrown within the scope of transaction. However, calling this method does not guarantee that the transaction wil be committed. It is merely a way of informing the transaction manager of your status. After calling the Complete method, you can no longer access the ambient transaction by using the Current property, and attempting to do so will result in an exception being thrown.
If the TransactionScope object created the transaction initially, the actual work of committing the transaction by the transaction manager occurs after the last line of code in the using block.
The using statement ensures that the Dispose method of the TransactionScope object is called even if an exception occurs. The Dispose method marks the end of the transaction scope. Exceptions that occur after calling this method may not affect the transaction. This method also restores the ambient transaction to it previous state.
A TransactionAbortedException is thrown if the scope creates the transaction, and the transaction is aborted.System.Transactions.TransactionIndoubtException is thrown if the transaction manager cannot reach a Commit decision. No exception is thrown if the transaction is committed.
If you want to rollback a transaction, you should not call the Complete method within the transaction scope. For example, you can throw an exception within the scope. The transaction in which it participates in will be rolled back.
Transaction scope can be nested by calling a method that uses a TransactionScope from within a method that uses its own scope, as is the case with the RootMethod method in the following example
- void RootMethod()
- {
- using(TransactionScope scope = new TransactionScope())
- {
- /* Perform transactional work here */
- SomeMethod();
- scope.Complete();
- }
- }
- void SomeMethod()
- {
- using(TransactionScope scope = new TransactionScope())
- {
- /* Perform transactional work here */
- scope.Complete();
- }
- }
The top-most transaction scope is referred to as the root scope. When a TransactionScope object joins an existing ambient transaction, disposing of the scope object may not end the transaction, unless the scope aborts the transaction. If the ambient transaction was created by a root scope, only when the root scope is disposed of, does Commit get called on the transaction. If the transaction was created manually, the transaction ends when it is either aborted, or committed by its creator.
Comments
Post a Comment