Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Transaction handling with AOP

4.00/5 (2 votes)
3 Apr 2013CPOL1 min read 15.3K  
How to create and apply TransactionScope using AOP.

Introduction

Who likes to write boiler plate code? Anyone? Bueler?

This article is a continuation of my journey with arguably the best Aspect Oriented Programming framework, PostSharp from SharpCrafters.

Disclaimer: I am only an extremely satisfied customer of SharpCrafter, not an employee, partner, or in any other way related to the founders. However, my satisfaction level has been recently affected by PostSharp licensing changes.

Background

The System.Transactions infrastructure provides both an explicit programming model based on the Transaction class, as well as an implicit programming model using the TransactionScope class, in which transactions are automatically managed by the infrastructure. TransactionScope can be used with your database connection, WCF Services, and any other class which is recognized by transaction manager as in scope. For more information on this model, see the Implementing An Implicit Transaction Using Transaction Scope topic, and on Writing A Transactional Application. In this example we are going to skip over these details and concentrate on the solution itself.

Transaction Scope Attribute

This attribute, like the others I've written before, is a MethodInterceptionAspect, which means that it will applied at runtime, during method invocation. So to support capturing various exceptions encountered during the transaction, we must initialize a list, which will contain them all. This should cover scenarios, where during multiple retries, the nature of the exceptions might change from a timeout to invalid argument value for example. To allow for specifying of the number of retries as well as delay between consecutive retries, I have created two public properties, MaxRetries (default is infinite number of retries) and RetryDelay (in seconds).

C#
namespace TransactionAspect
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Transactions;

    using PostSharp.Aspects;

    [Serializable]
    public class TransactionScopeAttribute : MethodInterceptionAspect
    {
        #region Fields

        private int maxRetries = int.MaxValue;

        private int retryDelay = 30;

        #endregion

        #region Public Properties

        public int MaxRetries
        {
            get
            {
                return this.maxRetries;
            }

            set
            {
                this.maxRetries = value;
            }
        }

        public int RetryDelay
        {
            get
            {
                return this.retryDelay;
            }

            set
            {
                this.retryDelay = value;
            }
        }

        #endregion

        #region Public Methods and Operators

        public override void OnInvoke(MethodInterceptionArgs args)
        {
            var aggregateExceptions = new List<Exception>();
            int retries = 0;
            while (retries++ < this.maxRetries)
            {
                try
                {
                    using (var scope = new TransactionScope())
                    {
                        args.Proceed();
                        scope.Complete();
                    }

                    break;
                }
                catch (Exception ex)
                {
                    aggregateExceptions.Add(ex);
                    if (retries >= this.maxRetries)
                    {
                        throw new AggregateException(string.Format("Trasaction failed after {0} attempts", this.maxRetries), aggregateExceptions);
                    }

                    Thread.Sleep(TimeSpan.FromSeconds(this.retryDelay));
                }
            }
        }

        #endregion
    }
}

Than the rest is simple. Any time you want a method to be retried under a transaction, simply decorate it with the TransactionScope attribute.

C#
[TransactionScope(MaxRetries = 3, RetryDelay = 1)]
private void QueryDB(string sql)
{
    var sqlConnectionStringBuilder = new SqlConnectionStringBuilder { DataSource = "localhost", 
                InitialCatalog = "master", IntegratedSecurity = true };
    using (var sqlConn = new SqlConnection(sqlConnectionStringBuilder.ConnectionString))
    {
        using (var sqlComm = new SqlCommand(sql,sqlConn))
        {
            sqlConn.Open();
            using(var sqlReader = sqlComm.ExecuteReader())
            {
                while (sqlReader.Read())
                {
                    Console.WriteLine("{0}", sqlReader[0]);
                }
            }
        }
    }
}

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)