Wrox Home  
Search
Professional WCF Programming: .NET Development with the Windows Communication Foundation


Excerpt from Professional WCF Programming: .NET Development with the Windows Communication Foundation

Transactions in WCF and .NET

by Scott Klein

A transaction is a collection or group of one or more units of operation executed as a whole. Another way to say it is that transactions provide a way to logically group single pieces of work and execute them as a single unit, or transaction.

For example, when you place an order online, a transaction occurs. Suppose you order a nice 21-inch wide-screen flat-panel monitor from your favorite online hardware source. Assume you were to pay for this monitor with a credit card. You enter the information required on the screen and click the "Place Order" button. At this point, two operations occur. The first operation takes place when your bank account is debited the amount of the monitor. The second operation occurs when the vendor is credited that amount. Each of those operations is a single unit of work.

Now imagine that one of those operations fails. For example, suppose that the money was removed from your bank account for the purchase of the monitor, but the payment to the vendor failed. First, you wouldn't receive your anxiously awaited monitor, and second, you would lose the amount of money for the cost of the monitor. I don't know about you, but I would be quite unhappy if this happened.

Conversely, a payment could be made to the vendor without debiting your account. In this case, the debit from your account failed but the payment to the vendor succeeded. You would likely receive the purchase item without having paid for it. Although this scenario is preferable to the former one, neither is acceptable in that in either case, someone is not receiving what is rightfully theirs.

The solution to this is to wrap both of these individual operations into a single unit of execution called a transaction. A transaction will make sure that both operations succeed or fail together. If either of the operations fails, the entire unit of work is cancelled and any and all changes are undone. At this point, each account is in the same state it was before you attempted your purchase. This undoing is known as "rolling back" the transaction.

This ensures that you receive your monitor and the vendor receives its money. Both parties are now happy and your confidence in doing business online hasn't wavered.

A pure and successful transaction has four characteristics. You can use the mnemonic aid "ACID" to help you remember each of them:

  • Atomic
  • Consistent
  • Isolated
  • Durable

Atomic

The word "atomic" comes from the Greek word "atamos," meaning "indivisible; cannot be split up." In computing terms, this meaning also applies to transactions. Transactions must be atomic, meaning either all the operations of the transactions succeed or none of them succeed (that is, all successful operations up to the point of failure are rolled back).

In the case of the monitor order, the money is removed from the bank account and deposited into the vendor bank account. If either of those operations fails, each account returns to the state it was in prior to the start of the purchase attempt.

Consistent

Consistent transactions mean that the outcome is exactly what you expected it to be. If you purchase the monitor for $300 and you have $1,000 in the bank account, consistent transactions mean that you expect to be charged $300 and have $700 remaining in the bank account when the transaction is committed and complete.

Isolated

Isolated transactions are "private," meaning that no one else knows about the transaction until it is committed.

For example, suppose you have $1,000 in a bank account from which to purchase the monitor. You purchase the monitor for $300, and during the purchase of the monitor, while the transaction is taking place, your husband or wife is at the local ATM checking the balance of the account from which the money for the monitor is being withdrawn.

Isolated transactions are invisible to all other transactions, and in this example the husband or wife would see a balance of $1,000. In fact, if the husband or wife were to withdraw money from the ATM while the online purchase was taking place, both transactions would be isolated transactions, completely unknown to one another.

Durable

Durable transactions must survive failures. When a transaction is complete, it is "committed," meaning that the changes have taken effect. For a transaction to be durable, it must maintain its committed state if there is a failure.

What is a failure? It could be a power outage, hardware failure, and so on. Regardless of the failure, the transaction must survive it.

Suppose that after the processing of the transaction, someone yanks the power cord out of the server that is processing your order. A durable transaction survives this failure. When the power is restored to the server, the result of the transaction must be in the committed state.

Transaction Attributes in System.ServiceModel

When version 2.0 of the .NET Framework was released, it included a new namespace (System.Transactions) that makes transaction programming easy and efficient. The System.Transactions namespace supports transactions initiated by many platforms including SQL Server, MSMQ, ADO.NET, as well as MSDTC (Microsoft Distributed Transaction Coordinator).

Windows Communication Foundation utilizes the many available objects of this namespace to provide all the necessary transaction capabilities you will need when building your WCF services and client applications.

ServiceBehavior Attribute

The [ServiceBehavior] attribute has three properties that deal with handling and managing transactions. Each is shown in the following list:

  • TransactionAutoCompleteOnSessionClose: Specifies whether pending transactions are completed when the current session closes.
  • TransactionIsolationLevel: Determines the isolation level of the transaction.
  • TransactionTimeout: Specifies the period in which a transaction has to complete.

The first of these is the TransactionAutoCompleteOnSessionClose property. It should be used for cases where you want to ensure that transactions are completed when the session is closed. As such, the transaction will either be committed or rolled back when the session is closed, depending on its state.

The one that needs to be highlighted here is the TransactionIsolationLevel property. This property determines how the data is handled when other transactions make modifications to the data. It also has an impact on how long your transaction can hold locks on the data, protecting it from other transactions. For a review of the available values for this property, see Chapter 8, "Services," of the book, Professional WCF Programming: .NET Development with the Windows Communication Foundation (Wrox, 2007, ISBN: 978-0-470-08984-2).

The TransactionTimeout property determines how long the transaction can run before it is cancelled. If the transaction has not completed before the timeout value has been reached, the transaction is rolled back. Great care must be taken when choosing a timeout interval. Too high of an interval will cause needless wait times when a failure has occurred. Too small of an interval will cause the transaction to fail before it has had time to complete.

These properties are properties of the [ServiceBehavior] attribute, which is applied to the class that implements the service interface. The following code snippet shows the three transaction properties applied to the [ServiceBehavior] attribute:

[ServiceBehavior(TransactionAutoCompleteOnSessionClose=true,
      TransactionIsolationLevel=IsolationLevel.ReadCommitted,
      TransactionTimeout="00:00:30")]
public class ServiceClass : IServiceClass
{
   [OperationBehavior]
   string IServiceClass.GetText()
   {
      StreamReader sw = new StreamReader(@"c:\wrox\WCFServiceTest.txt");
      return sw.ReadLine();
   }
}

In this case, the TransactionAutoCompleteOnSessionClose property is set to true, the TransactionIsolationLevel is set to ReadCommitted, and the TransactionTimeout is set to 30 seconds. The TransactionTimeout property value is of a Timespan object.

Prior to running the example, make sure that the c:\wrox\WCFServiceTest.txt file exists and contains at least a few words of text. If your test file is located somewhere else, make sure the example is pointed to the correct location.

OperationBehavior Attribute

The [OperationBehavior] also has a couple of properties related to transactions. The two properties are:

  • TransactionAutoComplete: Specifies that transactions will be auto-completed if no exceptions occur.
  • TransactionScopeRequired: Specifies whether the associate method requires a transaction.

The characteristics of a successful transaction were discussed earlier in this article. Both the TransactionAutoComplete property and TransactionScopeRequired property help fulfill the durable requirement because it will automatically complete a transaction, thus ensuring that the specific operation is successful.

The following example illustrates a service operation that is annotated with the [OperationBehavior] attribute, which specifies the two transaction properties:

[OperationBehavior(TransactionAutoComplete=true,
TransactionScopeRequired=true)]
string IServiceClass.GetText()
{
    StreamReader sw = new StreamReader(@"c:\wrox\WCFServiceTest.txt");
    return sw.ReadLine();
}

In this example, the TransactionAutoComplete property is set to true and the TransactionScopeRequired property is set to true as well.

TransactionFlow Attribute

The [TransactionFlow] attribute is used to specify the level at which a service operation can accept a transaction header. This attribute has a single property and is the attribute used to annotate a service operation method. The values for this property come from the TransactionFlowOption enumeration and are shown in the following list:

  • Allowed: Transaction may be flowed.
  • Mandatory: Transaction must be flowed.
  • NotAllowed: Transaction cannot be flowed.

This property and the associated values are used to indicate whether transaction flow is enabled for the associated method.

The following example illustrates a service operation that is annotated with the [TransactionFlow] attribute, which specifies the level at which the operation is willing to accept incoming transactions. This example sets the level at mandatory, signifying that transactions are required for this operation:

 [TransactionFlow(TransactionFlowOption.Mandatory)]
int IServiceClass.MultiplyNumbers(int firstvalue, int secondvalue)
{
    return firstvalue * secondvalue;
}

The default value for this property is NotAllowed.

A flowed transaction is a situation in which a transaction id is passed over the wire and used on the receiving side to perform work, usually enlisting in the corresponding transaction and executing within that scope.

WS-Atomic Transaction

Windows Communication Foundation utilizes the WS-AT (WS-Atomic Transaction) protocol to flow transactions to other applications. The WS-AT protocol is an interoperable protocol that enables distributed transactions to be flowed using web service messages, and incorporates a two-phase commit protocol to facilitate the outcome between distributed applications and transaction managers. The transaction protocol used when flowing a transaction between a client and service is determined by the binding that is exposed on the endpoint.

You do not need to use this protocol if your communication is using strictly Microsoft technology (WCF). Simply enabling the TransactionFlow attribute will get you the desired results. However, this protocol will be necessary if you are flowing transactions to other platforms and third-party technologies.

Specifying Transactions Through Configuration

On the client side, transaction flow is enabled via the binding. The following configuration file was taken from the behavior example in Chapter 8, "Services," of the book, Professional WCF Programming: .NET Development with the Windows Communication Foundation (Wrox, 2007, ISBN: 978-0-470-08984-2). In that example, several properties on the [ServiceBehavior] and [OperationBehavior] attributes were set so that they enabled transactions on the service. When the service references were added to the client, the client interrogated the consumed service and set the appropriate binding attributes in the configuration file.

Transaction flow is enabled by setting the value of the transactionFlow attribute to true, as shown by the highlighted line in the following code:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_IServiceClass" 
                 closeTimeout="00:01:00"
                 openTimeout="00:01:00" 
                 receiveTimeout="00:10:00" 
                 sendTimeout="00:01:00"
                 bypassProxyOnLocal="false" 
                 transactionFlow="true" 
                 hostNameComparisonMode="StrongWildcard"
                 maxBufferPoolSize="524288" 
                 maxReceivedMessageSize="65536"
                 messageEncoding="Text" 
                 textEncoding="utf-8" 
                 useDefaultWebProxy="true"
                 allowCookies="false">
          <readerQuotas maxDepth="32" 
                        maxStringContentLength="8192" 
                        maxArrayLength="16384"
                        maxBytesPerRead="4096" 
                        maxNameTableCharCount="16384" />
          <reliableSession ordered="true" 
                           inactivityTimeout="00:10:00"
                           enabled="false" />
           <security mode="Message">
             <transport clientCredentialType="Windows"
                        proxyCredentialType="None"
                        realm="" />
             <message clientCredentialType="Windows" 
                      egotiateServiceCredential="true"
                      algorithmSuite="Default" 
                      establishSecurityContext="true" />
           </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:8080/WCFService" 
                binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_IServiceClass"
                contract="WCFClientApp.TCP.IServiceClass"
                name="NetTcpBinding_IServiceClass">
        <identity>
          <userPrincipalName value="Scott@Avalon" />
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

You should now understand how WCF handles transactions. The remainder of Chapter 9, "Transactions and Reliable Sessions," in the book, Professional WCF Programming: .NET Development with the Windows Communication Foundation (Wrox, 2007, ISBN: 978-0-470-08984-2) discusses reliable sessions.

This article is excerpted from Chapter 9, "Transactions and Reliable Sessions," of the book, Professional WCF Programming: .NET Development with the Windows Communication Foundation (Wrox, 2007, ISBN: 978-0-470-08984-2), by Scott Klein. Scott is an independent consultant with passions for all things SQL Server, .NET, and XML. He is the author of Professional SQL Server 2005 XML (Wrox, 2006, ISBN: 978-0-7645-9792-3), writes the bi-weekly feature article for the SQL PASS Community Connector, and has contributed articles to both Wrox (www.Wrox.com) and TopXML (www.TopXML.com). He frequently speaks at SQL Server and .NET user groups. Scott's other articles on Wrox.com include "Windows Communication Foundation" and "SQL Server 2005 Programmability Enhancements — Common Table Expressions."