Wednesday, August 18, 2010

TransactionScope Class (System.Transactions)

 
TransactionScope Class (System.Transactions)
It is recommended that you create implicit transactions using the TransactionScope class, so that the ambient transaction context is automatically managed for you. You should also use the TransactionScope and DependentTransaction class for applications that require the use of the same transaction across multiple function calls or multiple thread calls. For more information on this model, see the Implementing an Implicit Transaction using Transaction Scope topic. For more information on writing a transactional application, see Writing a Transactional Application.
The top-most transaction scope is referred to as the root scope.
The TransactionScope class provides several overloaded constructors that accept an enumeration of the type TransactionScopeOption, which defines the transactional behavior of the scope.
A TransactionScope object has three options:
  • Join the ambient transaction, or create a new one if one does not exist.
  • Be a new root scope, that is, start a new transaction and have that transaction be the new ambient transaction inside its own scope.
  • Not take part in a transaction at all. There is no ambient transaction as a result.
If the scope is instantiated with Required, and an ambient transaction is present, the scope joins that transaction. If, on the other hand, there is no ambient transaction, then the scope creates a new transaction, and become the root scope. This is the default value. When Required is used, the code inside the scope does not need to behave differently whether it is the root or just joining the ambient transaction. It should operate identically in both cases.
If the scope is instantiated with RequiresNew, it is always the root scope. It starts a new transaction, and its transaction becomes the new ambient transaction inside the scope.
If the scope is instantiated with Suppress, it never takes part in a transaction, regardless of whether an ambient transaction is present. A scope instantiated with this value always have null as its ambient transaction.
The above options are summarized in the following table.
TransactionScopeOption
Ambient Transaction
The scope takes part in
Required
No
New Transaction (will be the root)
Requires New
No
New Transaction (will be the root)
Suppress
No
No Transaction
Required
Yes
Ambient Transaction
Requires New
Yes
New Transaction (will be the root)
Suppress
Yes
No Transaction

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.
Although the default and most commonly used value of TransactionScopeOption is Required, each of the other values has its unique purpose.
Suppress is useful when you want to preserve the operations performed by the code section, and do not want to abort the ambient transaction if the operations fail. For example, when you want to perform logging or audit operations, or when you want to publish events to subscribers regardless of whether your ambient transaction commits or aborts. This value allows you to have a non-transactional code section inside a transaction scope.
Setting the TransactionScope isolation level
Some of the overloaded constructors of TransactionScope accept a structure of type TransactionOptions to specify an isolation level, in addition to a timeout value. By default, the transaction executes with isolation level set to Serializable. Selecting an isolation level other than Serializable is commonly used for read-intensive systems. This requires a solid understanding of transaction processing theory and the semantics of the transaction itself, the concurrency issues involved, and the consequences for system consistency.
In addition, not all resource managers support all levels of isolation, and they may elect to take part in the transaction at a higher level than the one configured.
Every isolation level besides Serializable is susceptible to inconsistency resulting from other transactions accessing the same information. The difference between the different isolation levels is in the way read and write locks are used. A lock can be held only when the transaction accesses the data in the resource manager, or it can be held until the transaction is committed or aborted. The former is better for throughput, the latter for consistency. The two kinds of locks and the two kinds of operations (read/write) give four basic isolation levels. See IsolationLevel for more information.
When using nested TransactionScope objects, all nested scopes must be configured to use exactly the same isolation level if they want to join the ambient transaction. If a nested TransactionScope object tries to join the ambient transaction yet it specifies a different isolation level, an ArgumentException is thrown.
IsolationLevel property of a transaction.
The lowest isolation level, ReadUncommitted, allows many transactions to operate on a data store simultaneously and provides no protection against data corruption due to interruptive transactions. The highest isolation level, Serializable, provides a high degree of protection against interruptive transactions, but requires that each transaction complete before any other transactions are allowed to operate on the data.
The isolation level of a transaction is determined when the transaction is created. By default, the System.Transactions infrastructure creates Serializable transactions.
Data Points :: ADO.NET and System.Transactions

TransactionScopeOptions
TransactionScopeOptions
Description
Required
If within a currently active transaction scope, this transaction scope will join it. Otherwise it will create its own transaction scope.
RequiresNew
This transaction will create its own transaction scope.
Supports
If within a currently active transaction scope, this transaction scope will join it. Otherwise no transaction scope will be created.
NotSupported
No transaction scope will be created.
Use the new SqlConnectionStringBuilder to construct valid connection strings at run time.
Members

Member name
Description

Serializable
Volatile data can be read but not modified, and no new data can be added during the transaction.

RepeatableRead
Volatile data can be read but not modified during the transaction. New data can be added during the transaction.

ReadCommitted
Volatile data cannot be read during the transaction, but can be modified.

ReadUncommitted
Volatile data can be read and modified during the transaction.

Snapshot
Volatile data can be read. Before a transaction modifies data, it verifies if another transaction has changed the data after it was initially read. If the data has been updated, an error is raised. This allows a transaction to get to the previously committed value of the data.When you try to promote a transaction that was created with this isolation level, an InvalidOperationException is thrown with the error message “Transactions with IsolationLevel Snapshot cannot be promoted”.

Chaos
The pending changes from more highly isolated transactions cannot be overwritten.

Unspecified
A different isolation level than the one specified is being used, but the level cannot be determined. An exception is thrown if this value is set.

Interop with COM+
When you create a new TransactionScope instance, you can use the EnterpriseServicesInteropOption enumeration in one of the constructors to specify how to interact with COM+. For more information on this, see Interoperability with Enterprise Services and COM+ Transactions.
The System.Transactions namespace can make the management of transactions quick and easy without the need to inherit from a ServicedComponent. One of the greatest features of the LightweightTransaction object is that it can determine if it needs to promote itself to a distributed transaction. The lightweight transactions are also a faster alternative to using the DTC for local transactions.
Transaction Binding
Implicit Unbind
Controls connection association with an enlisted System.Transactions transaction.
Possible values are:
Transaction Binding=Implicit Unbind;
Transaction Binding=Explicit Unbind;
Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The System.Transactions.Transaction.Current property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode.
Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit SqlConnection.TransactionEnlist(null) is called. An InvalidOperationException is thrown if Transaction.Current is not the enlisted transaction or if the enlisted transaction is not active.
The TransactionScope Object Makes ADO.NET Transactions Easy
In database development, the concept of consistency dictates that the database is always in a valid state. Transactions exist to support consistency. The transaction is a mechanism by which you can treat multiple operations as an all-or-nothing whole: All the pieces work and all the changes are made or everything goes back to a prior known, consistent state. For example, for each customer deleted from a company's database, all of that customer's orders have to be deleted as well for consistency.
This article shows you how easy it is to use transactions with previous versions of .NET, and how you can use the TransactionScope object to auto-enlist database operations in a transaction. Other kinds of things, such as COM+ objects, can participate in transactions as well, but this article focuses on ADO.NET transactions. (If you are interested in other kinds of transactions, use the System.Transactions.TransactionScope class in .NET 2.0 as a point of further study.)

Using Transactions in .NET 1.1

T were pretty easy to use in .NET versions prior to 2.0, as long as you knew what to look for and where to look for it. The basic process is the same for every single transaction:
1.               Create a Connection object.
2.               Open the connection.
3.               Request a transaction object from the connection.
4.               Notify all command objects of the transaction's existence.
5.               If there are no errors, commit the transaction.
6.               If there is an error, roll back the transaction.
7.               Finally, close the connection.
To ensure that a transaction could be rolled back—which means that all protected writes to the database are rolled back—use a try...catch exception handler. If the catch block is entered, you can assume that something went wrong and call Rollback in the catch block. This formula never changes and, when used correctly, will ensure that your database stays in a consistent state.
You don't need to protect a single write (but you can) or any number of reads with a transaction. That said, Listing 1 demonstrates how to enroll an insert SQL statement in a transaction. Rather then arbitrarily inserting and deleting a lot of data, one operation is enough to demonstrate the transaction behavior. Simply assume more than one command is executed in the try block.

Listing 1: Database Transactions for Every Version of .NET (Although the Sample Was Coded in VS 2005).
Public Sub UseTransaction()
  Const SQL As String = _
    "INSERT INTO CUSTOMERS " + _
    "(CustomerID, CompanyName, ContactName, ContactTitle, " + _
    "Address, City, Region, PostalCode, Country, " + _
    "Phone, Fax) VALUES (" + _
    "'KimCo', 'Kimmel', 'Paul Kimmel', 'Dude', " + _
    "'', '', '', '', 'USA', '', '')"


    Dim connectionString As String = _
      "Data Source=EREWHON;Initial Catalog=Northwind;Integrated " + _
      "Security=True" 

    ' declare here because try and catch have different scope
    Dim trans As SqlTransaction = Nothing
    Dim connection As SqlConnection = _
      New SqlConnection(connectionString)
    connection.Open()
    Try
      trans = connection.BeginTransaction
      Dim command As SqlCommand = New SqlCommand( _
        SQL, connection, trans)
      command.ExecuteNonQuery()
      trans.Commit()
    Catch ex As Exception
      trans.Rollback()
      Console.WriteLine(ex.Message)
      Console.WriteLine("press enter")
      Console.ReadLine()
      ' nothing else you can do here, so re-throw exception
      Throw ex
    Finally
      ' called every time
      connection.Close()
    End Try

In all likelihood, you have seen code similar to this before. You can actually add and read the connection string from an App.config property by using the ConifgurationManager in .NET 2.0. The SqlTransaction is declared outside of the try block because try and catch have different scopes and you need to "see" the transaction object in both try and catch. The transaction is actually initialized right after the try block is entered; it is initialized from the connection object as shown. The SqlCommand is provided with the query, connection, and transaction. (Northwind has a primary key on the CustomerID column, so this query should fail the second time through.)
Assuming ExecuteNonQuery works, SqlTransaction.Commit is called. If ExecuteNonQuery fails, the code jumps to the catch block and the transaction is rolled back. The Console statements exist for informational purposes, and you re-throw the exception because there is no other useful handling you can do here. No matter what happens, the finally block is always executed.

Using the TransactionScope

The code for the TransactionScope fundamentally does the same thing as the code in Listing 1. They have two noteworthy differences: Listing 1 uses the new-to-Visual-Basic using statement, and the TransactionScope implicitly enrolls the connection and SQL command into a transaction.
The using statement is nothing to worry about. It has been around in C# for years, and it is just shorthand for try...finally. Any object that implements IDispose, such as SqlConnection or TransactionScope, can be created in a using block and the using block guarantees that the object's Dispose method will be called. For instance, the SqlConnection's Dipose checks to see whether the connection is open and, if so, closes it.
The TransactionScope class automatically runs a whole bunch of code (I checked using Reflector) that determines whether objects created in the scope support transactions. If they do (as the SQL Provider does), the TransactionScope's Dipose method determines whether the implicitly enrolled transaction has completed. If the transaction has completed, the changes are committed. If the transaction is not complete, the transaction is rolled back. You signify that the transaction is complete by calling TransactionScope.Complete. If you don't call Complete, the transaction is rolled back by TransactionScope when it is discarded.
Tip: You can download Reflector for free. Just Google for it by name or by the author Lutz Roeder's name.
For all intents and purposes, the code in Listing 2 functions exactly the same as the code in Listing 1, except that it uses the newly introduced using statement for Visual Basic .NET and the TransactionScope; this makes using transactions much easier.

Listing 2: Database Transactions Using the Using Statement for Visual Basic .NET and the TransactionScope
Public Sub UseTransactionScope()


  Const SQL As String = _
    "INSERT INTO CUSTOMERS " + _
    "(CustomerID, CompanyName, ContactName, ContactTitle, " + _
    "Address, City, Region, PostalCode, Country, " + _
    "Phone, Fax) VALUES (" + _
    "'KimCo', 'Kimmel', 'Paul Kimmel', 'Dude', " + _
    "'', '', '', '', 'USA', '', '')"


    Dim connectionString As String = _
      "Data Source=EREWHON;Initial Catalog=Northwind;" + _
      Integrated Security=True"


    Using scope As Transactions.TransactionScope =  New Transactions.TransactionScope()
      Using connection As New SqlConnection(connectionString)
        connection.Open()
        Dim command As SqlCommand = New SqlCommand(SQL, connection)
        command.ExecuteNonQuery()
        scope.Complete()
        ' connection close is complicit in using statement
      End Using
  End Using
End Sub

That's all there is to it, friends. Microsoft claims the TransactionScope applies to more than just database providers and there is no reason to doubt this, but how it works seems a little like magic.

Fun with Using and TransactionScope

The using statement helps ensure that finite resources like SqlConnections are cleaned up with an implicit try...finally block, and the TransactionScope makes it easier than ever to use transactions correctly. VB programmers can find many more new VB.NET features like this that make them more productive than ever.





No comments: