Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Transaction in .NET Application

0.00/5 (No votes)
14 Oct 2014 2  
Transaction in .NET Application through Command Pattern

Introduction

Here, I will be discussing 'How to implement a Transaction in .NET application' but it is not to be confused with 'System.Transactions' namespace available in .NET Framework and 'Transaction in SQL Server'.

I will explain 'how to implement transaction in your class library'.

I will not touch transaction in SQL Server and this topic is not related to SQL Server Transaction in any way.

Background

Basic knowledge of Command Pattern is required.

Using the Code

First, what is Transaction?

In simplest terms, a Transaction must exhibit the following behaviour.

"Each transaction must succeed or fail as a complete unit; it can never be only partially complete."

Source: Wikipedia.

So to implement transaction in your class library, we will use the command pattern because it is this pattern which provides the way to implement not only the transaction, but also other features too like logging the operations, undo/redo, recovering from the crash, etc.

So let’s start, there is a sample solution attached, in which two examples are given, one is Console based, it explains the basic transaction and then WinForm application in which that transaction knowledge is applied into the real world scenario.

Let’s start.

Our First Interface in 'Transaction Enabled Architecture'

/// <summary>
/// Interface to be implemented by Receiver which is transaction aware
/// </summary>
public interface ITransactionHandler
{
    void Rollback();

    bool IsExecuted { get; }
}

This interface is to be implemented by Receiver which has to provide the transaction support so that it can participate in transaction because without touching the Receiver, a transaction cannot be supported.

Note: We can merge the 'ICommand' and 'ITransactionHandler' too in one interface of 'ICommand' but we have not done this because if it a very large application and our 'ICommand' is being used in other projects too but those project may not be using Transaction and may not need to provide the 'Rollback' implementation. Therefore it is separated into Transaction part and it provide more flexibility.

Our Second Interface

/// <summary>
/// Interface to be implemented by Receiver Command which will participate in Transaction
/// </summary>
public interface ITransaction
{
    bool Executed();

    void Rollback();
}

This interface is to be implemented by the ReceiverCommand so that TransactionService (explained below) can know that ReceiverCommand also supports Transaction not just Receiver.

/// <summary>
/// Main Transaction Interface Service which will provide transaction to .NET application
/// </summary>
public interface ITransactionService : IDisposable
{
    ICommand Command { set; }

    void Commit();

    void Rollback();
}

/// <summary>
/// Implemented class for ITransactionService
/// </summary>
public class TransactionService : ITransactionService
{
    private Queue<ITransaction> _commandQueue = new Queue<ITransaction>();
    private TransactionState IsTransactionCommitted = TransactionState.None;
    
    public ICommand Command
    {
        set
        {
            if (value is ITransaction)
            {
                this._commandQueue.Enqueue((ITransaction)value);
            }
            else
            {
                throw new NotSupportedException("Transaction is not supported");
            }
        }
    }

    public void Commit()
    {
        if (this.IsTransactionCommitted == TransactionState.Committed)
        {
            throw new Exception("Transaction is already committed");
        }
        else if (this.IsTransactionCommitted == TransactionState.Rollbacked)
        {
            throw new Exception("Transaction is already rollbacked");
        }
        else
        {
            this.IsTransactionCommitted = TransactionState.Committed; 
            this._commandQueue.Clear();
        }
    }

    public void Rollback()
    {
        if (this.IsTransactionCommitted == TransactionState.Committed)
        {
            throw new Exception("Transaction is already committed");
        }
        else if (this.IsTransactionCommitted == TransactionState.Rollbacked)
        {
            throw new Exception("Transaction is already rollbacked");
        }
        else
        {
            while (this._commandQueue.Count > 0)
            {
                ITransaction transaction  = this._commandQueue.Dequeue();
                if (transaction.Executed())
                {
                    transaction.Rollback();
                }
            }
           this.IsTransactionCommitted = TransactionState.Rollbacked;
        }
    }

    public void Dispose()
    {
        if (this.IsTransactionCommitted == TransactionState.None)
        {
            while (this._commandQueue.Count > 0)
            {
                ITransaction transaction = this._commandQueue.Dequeue();
                if (transaction.Executed())
                {
                    transaction.Rollback();
                }
            }
            this.IsTransactionCommitted = TransactionState.Rollbacked;
        }
        this._commandQueue = null;
    }

    private enum TransactionState
    {
        None,
        Committed,
        Rollbacked
    }
}

This interface and its implemented class is the heart of the transaction and it is used by calling application to create an instance of transaction object and perform commit operation on successful execution and call rollback method on failure of execution.

This is a calling application's code which calls commands in a transaction scope.

using (ITransactionService transactionService = new TransactionService())
    {
        ICommand firstCommand = new ReceiverCommand(new Receiver());
        ICommand secondCommand = new ReceiverCommand(new Receiver());

        transactionService.Command = firstCommand;
        transactionService.Command = secondCommand;

        try
        {
            Console.WriteLine("Starting Transaction...");

            firstCommand.Execute();
            
            if (input == 'Y' || input == 'y')
            {
                throw new Exception("Reason for failure to execute second command");
            }

            secondCommand.Execute();
            transactionService.Commit();

            Console.WriteLine();
            Console.WriteLine("Transaction Successful Executed...");
        }
        catch (Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine("Exception Occured: " + ex.Message);
            Console.WriteLine("Due to exception Transaction is rolledback");
            transactionService.Rollback();
        }
    }

First transaction object is created in using block so that its Dispose method can be called. Then two different receiver objects are created and we set these commands to transaction object so that they can participate in transaction. Once they are successfully executed, the transaction object is committed otherwise it is rolled back.

Note: The way TransactionService object is created in 'using' block and on successful execution commit must be called or if it is not called, then on Dispose RollBack will occur. I have tried to follow the 'Microsoft implementation of implicit transaction model'.

Screenshot for Console Based Example

To better understand the transaction, first go through the Console based application in which the core concept is explained and after understanding it, look into the example of WinForm based application.

Screenshot for WinForm Based Example

In WinForm based applications, when you execute the code, it will load the country name from XML file, when you double click any node in treeview, it adds the country name to cache list, and when you check the 'Should Exception Throw in TextBox' checkbox, then you click the 'Perform Operation' button then exception will occur 'TextBox Receiver' while performing the operation, if 'Is Transaction Enabled' checkbox is checked, then no operation will be succeeded otherwise you will see that first operation is executed successfully but not second operation. Please have a look into this code to better understand the transaction.

Points of Interest

How can I take this transaction architecture to support in distributed application ? This will be interesting to see.

I tried my best to explain the transaction with both the examples, but feedback is most welcome so that I can improve the architecture of transaction.

History

  • 13th October, 2014 - First draft version
  • 20th October, 2014 - Second draft version
    • Article content is updated.
    • 'ITransaction' interface method's name changed to reflect more what it does.
    • 'Dispose' method of TransactionService is changed.
    • Private Enum 'TransactionState' is added in TransactionService class

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here