Transaction class

Modified on 2009/02/16 00:53 by helen — Categorized as: SignumEngine

Introduction

Transaction class is the most common class using Scope pattern to handle transactions in a clean and easy nestable way.

It uses the same strategy as Microsoft's TransactionScope from the client code point of view: Implicit transaction in the transaction scope defined by the using statement.

Internally, however, it has some differences, let's take a look:

How to use it

Some time ago this code tended to be very cumbersome. Implicit transactions, however, made a big step forward, creating a easy model to handle transactions writing less code:

private static void FixBugs()
{
  var wrongBugs = from b in Database.Query<BugDN>()
                  where b.Status == Status.Fixed && !b.End.HasValue
                  select b.ToLazy();

foreach (var lazyBug in wrongBugs) { BugDN bug = lazyBug.Retrieve(); bug.Description += "- Fix it!"; bug.Save(); } }

This simple code contains a lot of intensive work with the database: the query, retrieving each Bug, and saving it. Currently all of these are independent operations, so we could have some problems:


In Signum Engine, every atomic operation, like retrieving an object, executing a query, or saving an object graph is transactional without you having to do anything.

Sometimes, however, you want to glue together operations in the same transaction, so you have a consistent view of the database (not influenced by concurrency) and you don't commit partial modifications if something goes wrong in the middle.

This is as easy as surrounding the code with a suing Transaction block, like this:

private static void FixBugs()
{
    using (Transaction tr = new Transaction())
    {
        var wrongBugs = from b in Database.Query<BugDN>()
                        where b.Status == Status.Fixed && !b.End.HasValue
                        select b.ToLazy();

foreach (var lazyBug in wrongBugs) { BugDN bug = lazyBug.Retrieve(); bug.End = bug.Start.AddDays(7); bug.Save(); }

tr.Commit(); } }

What we do is to create a transaction, do our work, and at the end Commit it. When the transaction is disposed, if it hasn't been committed, it gets roll backed automatically, so the code gets much more simple to read.

Be careful with return statements, remember to commit the transaction before exit the method!

The nice thing is that all the old code doesn't need to be updated with the new declared Transaction tr, because transactions are handled implicitly.

Also, if you call FixBigs from a method like this:

private static void FixTheWorld()
{
    FixBugs();

FixCustomers();

FixDevelopers(); }

You cold also glue together all the inner transactions creating a new, wider one:

private static void FixTheWorld()
{
    using (Transaction tr = new Transaction())
    {
        FixBugs();

FixCustomers();

FixDevelopers();

tr.Commit(); } }

The end result is that the inner transactions become silent without you having to change anything, so the code is much more easy to combine.

There are some options for useful overloadings of Transaction constructor:

//Creates a nestable transaction
public Transaction()

//If forceNew == true, the new transaction is independent from the parent public Transaction(bool forceNew) //Allows to change the IsolationLevel of the transaction (only if ends up being the parent trnasaction) public Transaction(IsolationLevel isolationLevel)

//The two above combined public Transaction(bool forceNew, IsolationLevel? isolationLevel)
.

How it works (AdvancedTopic)

There are some things that are important to know in advanced scenarios:





Probably you where expecting Connection to handle SqlConnection, but Connection is more like a potential connection than an actual one, and it's shared between threads. Transaction has better knowledge about when to start the connection and how long to keep it open.