Click here to Skip to main content
Click here to Skip to main content

A Transactional Repository Implementation in .NET

By , 26 Nov 2008
Rate this:
Please Sign up or sign in to vote.

Introduction

What transactional repositories do we know at the moment? Here is a list: SQL Server, MSMQ, file systems, and the Registry (in Windows Vista/Windows Server 2008). Are these enough? Do they cover all possible needs of enterprises? The transactional repository implementation described below displays the basic principles required for the implementation of your own transactional repository that can easily participate in ambient and explicit transactions in .NET. The given implementation is based on the Enterprise Library Caching Application Block. You could ask, why the caching application block? Does anybody in the world need transactional cache? Smile | :) No, personally I don't. The goal of the article is to provide developers with a basic idea of how to design and implement a custom transactional repository, be it XML file or anything else.

So, the idea is we can easily implement our own transactional resources which will easily fit into the transactional model introduced in .NET and WCF, in particular.

What are the main benefits of such a transactional repository? The main benefits are:

  1. It is transactional!!! Wink | ;)
  2. It can participate in a single transaction with other transactional resources like SQL Server and MSMQ.

Background

A new namespace System.Transactions was introduced within .NET 2.0. This namespace conveys the functionality for ambient transactions (the TransactionScope class), and supports distributed transactions. Windows Communication Foundation (as well as COM+) has out-of-the-box support for ambient transactions. In the samples below, I've used the TransactionScope class to demonstrate its work. More info on this can be obtained from the MSDN.

Using the code

First of all, I would like to present a sample code on how to use this black box which I call the Transactional Caching Application Block.

If you get an exception somewhere inside the using block, all transactions will be rolled back. And, your cache will still be in a consistent state. The good thing here is, the transactions are per-object key. It means that during transactions, only the relevant cache items are synchronized, but not the entire cache, which improves performance in multithreaded applications. Also, I would like to add that if there are concurrent transactions which try to access/modify the same object in the cache, then these transactions will be synchronized; in other words, a transaction will be blocked until another one completes. At this moment, I see here a huge field for possible deadlocks, but that's life - the client code should be careful regarding the objects it uses during transactions.

Before we proceed, I'd like to note some restrictions of the solution presented below:

  1. The current version does not support distributed transactions, only local transactions (I would gladly extend it if somebody pays for it Smile | :) - just kidding).
  2. The current version supports only the highest transaction isolation level - Serializable.
  3. You need to have the MS Enterprise Library 4.0 installed.
  4. All objects in the cache should be serializable.
  5. When you call the method Get during a transaction, you'll get only a copy of the object, but not the object itself.

Let's get back to the implementation. First of all, your transactional resource should implement the IEnlistmentNotification interface:

public class TransactionalCacheManager : ICacheManager, IEnlistmentNotification
{
    void IEnlistmentNotification.Commit(Enlistment enlistment)
    {
        foreach(var commandItem in CurrentTransactionalRepository.Values)
        {
            commandItem.Command.Invoke();
        }
        enlistment.Done();
    }

    void IEnlistmentNotification.InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }

    void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared();
    }

    void IEnlistmentNotification.Rollback(Enlistment enlistment)
    {
        enlistment.Done();
    }

The main method in my implementation is Commit (in your implementation, it can be Rollback). I've used used the Command pattern here: during the transaction execution, I collect all the actions performed by the client code into some temporary repository. Then, I just execute all these commands during Commit. But at the same time, my temporary repository is a bit tricky, and allows client code to see the changes performed within a transaction (during calls to the method Get). The temporary repository is implemented on a per-call basis:

[ThreadStatic]
private static ThreadSafeDictionary<string, CommandItem> transactionalRepository;

At the beginning of the transaction, I set it up, and at the end of the transaction, I do release memory. Therefore, this repository will exist only in the scope of a given transaction within a particular thread.

Here is the code snippet which notifies the transaction manager that I would like to participate in a transaction:

void Enlist(string key, Operation operation, object value, Action command)
{
    if(Transaction.Current != null)
    {
        Debug.Assert(Transaction.Current.TransactionInformation.Status == 
                     TransactionStatus.Active);
        //notify transaction manager that we want to participate in transaction
        Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
        CurrentTransactionalRepository[key] = 
               new CommandItem(operation, value, command);
    }
    else
    {
        command.Invoke();
    }
}

Enlist is a private method. I do call this method from such methods like Add and Remove. The last part I would like to describe here is a small custom class I use for thread synchronization. Thread synchronization is not in the scope of this article, I just want to present the interesting technique of how to use the Monitor.Pulse/Monitor.Wait methods. Here is my class:

private class Synchronizer
{
    private object lockObject = new object();
    private bool canProceed = false;

    public void Wait()
    {
        lock (lockObject)
        {
            while (!canProceed) Monitor.Wait(lockObject);
            canProceed = true;
        }
    }

    public void Set()
    {
        lock (lockObject)
        {
            canProceed = true;
            Monitor.Pulse(lockObject);
        }
    }
}

That's all. You can find out more from the source code available for download. You are welcome for feedbacks. Looking forward to any suggestions.

License

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

About the Author

Vitaliy Liptchinsky
Technical Lead bwin Interactive Entertainment AG
Austria Austria
The views expressed in my articles are mine and do not necessarily reflect the views of my employer.
 
if(youWantToContactMe)
{
SendMessage(string.Format("{0}@{1}.com", "liptchinski_vit", "yahoo"));
}
 
More info in my LinkedIn profile:
http://www.linkedin.com/in/vitaliyliptchinsky

Comments and Discussions

 
QuestionEsception during Commit phase doesn't call Rollback(); [modified] PinmemberOkramAlel3-Jun-13 10:19 
QuestionHow can we use it ? Pinmemberlovedota21-Sep-11 3:30 
It would be five star if you give us an example about how to use it. Connect to Northwind database and use transactions is good example for us. btw thanks !
AnswerRe: How can we use it ? PinmemberVitaliy Liptchinsky22-Sep-11 4:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 26 Nov 2008
Article Copyright 2008 by Vitaliy Liptchinsky
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid