Declarative Transactions Sample

Open this sample in Visual Studio

The PostSharp.Samples.Transaction project demonstrates the use of PostSharp Laos to author a custom attribute that defines transaction boundaries. This sample relies on the System.Transactions namespace. It demonstrates the use of the custom attribute with the classic "bank account transfer" scenario.

This sample is written in Visual Basic .NET.

Objective

What we are trying to achieve is to define a custom attribute that marks transaction boundaries, like the TransactionAttribute class of System.EnterpriseServices, but relying on the lighter System.Transactions framework. Transaction boundaries can be defined procedurally using the TransactionScope class, so our only ambition is to author a TransactionScopeAttribute class that would use TransactionScope as a back-end.

Implementation

What we need to do is to:

  1. Create a new instance of the TransactionScope class in the beginning of the method, 
  2. Enclose the method body in a try...finally clause
  3. Commit the TransactionScope in case of success (i.e. in case of absence of exception).
  4. Dispose the TransactionScope instance in the finally clause.

This is exactly the cup of tea of the OnMethodBoundaryAspect class. We will basically override two methods: OnEntry and OnExit. In the first, we will create the TransactionScope instance and, in the second, we will eventually commit it and anyway dispose it.

In order to expose the full functionality of the TransactionScope class in our custom attribute, we add the properties ScopeOption, IsolationLevel and Timeout, which will be the parameters of the TransactionScope constructor.

The principal methods look like this:

Public Overrides Sub OnEntry(ByVal eventArgs As MethodExecutionEventArgs)

Dim transactionOptions As TransactionOptions
transactionOptions.Timeout = TimeSpan.FromSeconds(Me._timeout)
transactionOptions.IsolationLevel = Me._isolationLevel

eventArgs.MethodExecutionTag = New TransactionScope(Me._scopeOption, transactionOptions, Me._interopOption)

End Sub

Public Overrides Sub OnExit(ByVal eventArgs As MethodExecutionEventArgs)

Dim transactionScope As TransactionScope = eventArgs.MethodExecutionTag

If eventArgs.Exception Is Nothing Then
transactionScope.Complete()
End If

transactionScope.Dispose()

End Sub

Usage

In order to demonstrate how to use the TransactionScope custom attribute, we have created a class Account that supports in-memory transactions. This implementation is uninteresting for the current discussion, so we don't comment it further.

Now take the typical example of a bank transfer. Suppose that we have a Transfer method that should adjust the balances of two accounts in a transaction. The code is now as simple as this:

<TransactionScope()> _
Sub Transfer(ByVal fromAccount As Account, ByVal toAccount As Account, ByVal amount As Decimal)
fromAccount.Balance -= amount
toAccount.Balance += amount
End Sub