2

I have an action that handle a critical transaction and I am not sure what would be the best way to handle the transaction.

Here is a simplified example of what I would need to do:

[HttpPost]
public ActionResult BeginOrderProcess(Guid orderKey)
{
    // Not sure what isolation level I sould use here to start with...
    IsolationLevel isolationLevel = IsolationLevel.ReadCommitted;
    using(new TransactionScope(isolationLevel)){

        // Retreive the order
        var order = GetExistingOrder(orderKey);

        // Validate that the order can be processed
        var validationResult = ValidateOrder(order);

        if (!validationResult.Successful)
        {
            // Order cannot be processed, returning
            return View("ErrorOpeningOrder");
        }

        // Important stuff going on here, but I must be sure it 
        // will never be called twice for the same order
        BeginOrderProcess(order);

        return View("OrderedProcessedSuccessfully");
    }
}

First thing I would ask is: in this kind of operation, where we can have multiple requests at the same time for the same order (i.e.:quick requests from browser for same order), should I use pessimistic locking to really ensure one transaction at the time or there is a way to make sure BeginOrderProcess would never be called twice with two concurrent requests for the same order almost at the same time with optimistic locking (considering that it would probably be faster)?

Second thing: Am I doing it completely the wrong way and there is a better way to handle cases like this? In other words, how should I handle this? :)

Samuel Poirier
  • 1,240
  • 2
  • 15
  • 30

1 Answers1

1

Ok, after some research, I think I've found what I wanted.

For a case like this, it would be overkill to use pessimistic lock with nhibernate (by using session.Lock(order))

I've opted for optimistic lock simply because I didn't know how to use it before.

Here's what the code should look like:

[HttpPost]
public ActionResult BeginOrderProcess(Guid orderKey)
{
    // I confirm, here I really want ReadCommit since I need optimistic lock
    IsolationLevel isolationLevel = IsolationLevel.ReadCommitted;
    using(var tx = new TransactionScope(isolationLevel)){

        // Retreive the order
        var order = GetExistingOrder(orderKey);

        // Validate that the order can be processed
        var validationResult = ValidateOrder(order);

        if (!validationResult.Successful)
        {
            // Order cannot be processed, returning
            return View("ErrorOpeningOrder");
        }

        // Important stuff going on here, but I must be sure it 
        // will never be called twice for the same order
        BeginOrderProcess(order);

        // The main difference is here
        // I need to do an explicit commit here to catch the stale object exception
        // and handle it properly. Before that, 
        // I was handling the commit in the dispose of my TransactionScope
        // Since my transaction scope is in ReadCommit, no one but this request 
        // should be able to read the modified data whatever the changes are
        try{
            try{
                tx.Commit();
            }catch(Exception){
                tx.RollBack();
                throw;
            }            
        }catch(StaleObjectStateException){
            return View("OrderIsCurrentlyBeeingProcessedBySomeoneElse");
        }

        return View("OrderedProcessedSuccessfully");
    }
}

As my comment shows, the main difference is that I handle my commit manually and then handle the exception as it would. With this implementation, I wont worry about blocking other users requests and I can handle the exception as I need.

I am using fluent nhibernate and I've configured my entities to use version in my mappings:

OptimisticLock.Version();

Version(x => x.Version)
.Column("EntityVersion")
.Generated.Never()
.Default(0)
.UnsavedValue("null");

With this, when I am doing my commit, NHibernate will look at the version and throw a StaleObjectStateException if the commit doesn't match the right version.

Happy NHibernating :)

Samuel Poirier
  • 1,240
  • 2
  • 15
  • 30