You are on page 1of 8

Understanding Transactions and Creating

Transaction Enabled WCF Services


Rahul Rajat Singh, 2013
Introduction
In this article we will discuss about creating transaction enabled WCF service. We will see what
needs to be done on the WCF service end so that it support transactions. We will also see how a
client application can work with these transaction enabled services using a sample application.

Background
When we talk about transactions in database operations, we need to perform some
operation(database operations) in such a way that either all the operations are successful or all of
them fail. This would result in the amount information being same once the transaction is
successful or it fails.

Properties of Transaction

By definition a transaction must be Atomic, Consistent, Isolated and Durable. What does we
mean by all these terms

Atomic: Atomic means that all the statements (SQL statement or operations) that are a part of
the transaction should work as atomic operation i.e. either all are successful or all should fail.
Consistent: This means that in case the atomic transaction success, the database should be in a
state that reflect changes. If the transaction fails then database should be exactly like it was when
the transaction started.
Isolated: If more than one transactions are in process then each of these transactions should
work independently and should not effect the other transactions.
Durable: Durability means that once the transaction is committed, the changes should be
permanent i.e. these changes will get saved in database and should persist no matter what(like
power failure or something).

Now having transactions from a simple class library's perspective ot from a simple .Net
applications, it is just a matter of calling the operations within a transaction, checking whether all
the operations are successful or not and deciding whether to commit or rollback the transaction.
Refer this article to know about transactions in normal application: A Beginner's Tutorial for
Understanding Transactions and TransactionScope in ADO.NET[^]

But from a WCF service perspective, since the service itself may be running on a remote server
and all the communication between the service and the client is in form of messages, the service
itself need some configuration so that it can be made transaction enabled.

Now in rest of the article we will see how we can configure a WCF service to
support transactions and how we can call a WCF operation within transactions.

Using the code


Understanding Two Phase Commit

The WCF transactions happen using two phase commit protocol. Two phase commit protocol is
the protocol that is used to enable transactions in a distributed environment. This protocol
mainly consist of two phases:

Prepare phase: In this phase the client application performs the operations of a WCF service.
WCF service determines whether the requested operation will be successful or not and notify the
client about the same.
Commit Phase: In the commit phase the client checks for the responses it got from the prepare
phase and if all the responses are indicating that the operation can be carried out successfully the
transaction is committed. If the response from any one of the operations indicates failure then
the transaction will be rolled back. The actual operation on the service end will happen in the
commit phase.

Now from the protocol, it is pretty clear that the WCF service will have to send the notification of
whether the operation will succeed or fail to the client application. It would mean that the One
way operations can never support transactions. The operations that support transactions have to
follow the Request-Response Model(refer this for details on message exchange modes: Tutorial
on Message Exchange Patterns and Asynchronous Operations in WCF[^])

A note on binding

Since services communicate in form of messages the underlying message specifications play a
very important role in supporting transactions. To have the possibility of transactions, the WS-
AT(WS-AtomicTransaction) protocol need to be used. The binding that supports this
is wsHttpBinding. So we will be using this binding to create our transaction enabled service.

Description of the Test Application

To illustrate the above process, let say I have two account holders, one person is trying to transfer
some money to other person. From the database perspective this operation consist of two sub-
operations i.e.

Debiting the first account by specified amount.


Secondly, crediting the second account with required amount.

Now from a technical perspective, if the first operation is successful but second one fails the
result would be that the first persons account will be debited but second one will not be credited
i.e. we loose the amount of information. The other way round will in fact increase the amount ion
second account without even debiting the first amount.

So the bottom-line here is that we need either both of them to be successful to both of them
should fail. Success of any one operation will result in inconsistent results and thus even if one
operation fails we need to rollback what we did in the other operation. This is precisely where
transaction are useful.
Let us say that the operations for credit and debit are exposed separately from a service. What we
need to do is that, we need to transaction enable this service and then call these two methods
within a transaction. The transaction will be committed only when both the operation indicate
success. In case any one operation indicates failure or throws an exception, the transaction will
not be committed.

Creating the Service

Let us create a simple service with a ServiceContract that exposes operations to debit an
account, credit an account and get the account balance information for any account.

[ServiceContract]
public interface IService1
{
[OperationContract]
bool PerformCreditTransaction(string creditAccountID, double amount);

[OperationContract]
bool PerformDebitTransaction(string debitAccountID, double amount);

[OperationContract]
decimal GetAccountDetails(int id);
}

Now we want the


operations PerformCreditTransaction and PerformDebitTransaction to work within a
transaction. To do this we need to make some configuration in the service. The very first thing
that is required with the OperationContract is to set the TransactionFlow property with
the desired TransactionFlowOption. There are 3 possible values
for TransactionFlowOption:

Mandatory: This specifies that this function can only be called within a transaction.
Allowed: This specifies that this operation can be called within a transaction but its not
mandatory.
NotAllowed: This specifies that this operation can not be called within a transaction.

Now for both our operations, we want the operations to be called mandatory within
a transaction so we will specify them with this option.

[ServiceContract]
public interface IService1
{
[OperationContract, TransactionFlow(TransactionFlowOption.Mandatory)]
bool PerformCreditTransaction(string creditAccountID, double amount);

[OperationContract, TransactionFlow(TransactionFlowOption.Mandatory)]
bool PerformDebitTransaction(string debitAccountID, double amount);

[OperationContract]
decimal GetAccountDetails(int id);
}
Now to illustrate the implementation part, We will work on a small application that contains a
single table database. This table contains the account id and the amount present in the account.

The sample DB table looks like:

The UI will look like:

And now to perform these operations, we will write simple ADO.NET code in our service
implementation. The important thing from the service implementation perspective is that the
service implementation also needs to be decorated/adorned
with OperationBehavior attribute with TransactionScopeRequired property set to true.
So now let us look at the sample implementation of the service operations.

public class Service1 : IService1


{
readonly string CONNECTION_STRING =
ConfigurationManager.ConnectionStrings["SampleDbConnectionString1"].ConnectionString;

[OperationBehavior(TransactionScopeRequired = true)]
public bool PerformCreditTransaction(string creditAccountID, double amount)
{
bool creditResult = false;

try
{
using (SqlConnection con = new SqlConnection(CONNECTION_STRING))
{
con.Open();

// And now do a credit


using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = string.Format(
"update Account set Amount = Amount + {0} where ID = {1}",
amount, creditAccountID);

// Let us emulate some failure here to see the that transaction will
not
// get committed
// return false;

creditResult = cmd.ExecuteNonQuery() == 1;
}
}
}
catch
{
throw new FaultException("Something went wring during credit");
}
return creditResult;
}

[OperationBehavior(TransactionScopeRequired = true)]
public bool PerformDebitTransaction(string debitAccountID, double amount)
{
bool debitResult = false;

try
{
using (SqlConnection con = new SqlConnection(CONNECTION_STRING))
{
con.Open();

// Let us do a debit
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = string.Format(
"update Account set Amount = Amount - {0} where ID = {1}",
amount, debitAccountID);

debitResult = cmd.ExecuteNonQuery() == 1;
}
}
}
catch
{
throw new FaultException("Something went wring during debit");
}
return debitResult;
}

public decimal GetAccountDetails(int id)


{
decimal? result = null;

using (SqlConnection con = new SqlConnection(CONNECTION_STRING))


{
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = string.Format("select Amount from Account where ID =
{0}", id);
try
{
con.Open();
result = cmd.ExecuteScalar() as decimal?;
}
catch (Exception ex)
{
throw new FaultException(ex.Message);
}
}
}

if (result.HasValue)
{
return result.Value;
}
else
{
throw new FaultException("Unable to retrieve the amount");
}
}
}

Note: The code is written to elaborate the transaction enabled services only, it is not as per the
coding standards i.e it is vulnerable to SQL injection. It should not be taken as code that could go
in production. It is just the sample code and has a lot of scope for improvement.

Now we have our ServiceContract ready and service implementation ready. Let us now use
the appropriate binding i.e. wsHttpBinding to use this service. Also, in the service
configuration we need to specify that this service is transaction enabled. This can be done by
setting the transactionFlow property of the binding configuration
of wsHttpBiding to true

Note: Please refer the web.config of the sample code to see how this is done.

Test Application

From our test application, we will simply call the functions within a
transaction(using TransactionScope). We will check both the operations' return value, if both
of the indicates success, we will commit the transaction(by calling Complete method
on TransactionScope object). If any operation fails we will rollback the transaction by not
calling Complete function and simply letting the TransactionScope object go out of scope).

private void PerformTransaction(string creditAccountID, string debitAccountID, double


amount)
{
// they will be used to decide whether to commit or rollback the transaction
bool debitResult = false;
bool creditResult = false;

try
{
using (TransactionScope ts = new TransactionScope())
{
using (ServiceReference1.Service1Client client = new
ServiceReference1.Service1Client())
{
debitResult = client.PerformDebitTransaction(debitAccountID, amount);
creditResult = client.PerformCreditTransaction(creditAccountID, amount);
}

if (debitResult && creditResult)


{
// To commit the transaction
ts.Complete();
}
}
}
catch
{
// the transaction scope will take care of rolling back
}
}

The code currently will work fine i.e. both the methods will return true and the transaction will get
committed. To check the rollback action, we have a simple return false; statement commented in
our service's PerformCreditTransaction operation, un-comment this statement to check
that the transaction will not get committed.

Note: The code snippets shows the relevant code in the context. Please look at the sample code
to get the full understanding.

So now we have a service that supports transactions. Before wrapping up let is look at the main
operations we need in order to make a service transaction enabled.

1. Decorate the OperationContract with required TransactionFlowOption.


2. Decorate the operation implementation with OperationBehavior with
TransactionScopeRequired as true.
3. Use wsHttpBinding to utilize the underlying WS-AtomicTransaction protocol.
4. Specify the transactionFlow property of the
binding configuration of wsHttpBinding as true.

Point of interest
In this article we have discussed how we can create WCF services that supports transactions. To
fully understand this article knowledge of creating a WCF service, Contracts and Bindings
and knowledge of transactions and TransactionScope is required. I hope this has been
informative.

You might also like