More about transaction scopes

An older version of re-store required the programmer to discard the client transaction explicitly, thereby handing over the transaction and its domain object instances to the garbage collector. This is what the API for discarding a client transaction looks like:

myClientTransaction.Discard (); // discards my client transaction

Note that we have not used this method in the PhoneBook.Sample. Simple! However, the explicit use of the Discard() is strongly discouraged. There is a better, more modern way. In the old days (until 2007), if the programmer forgot to discard client transactions, they were piling up, clogging resources. This sensitivity to absent minds resembles memory leaks in languages without garbage collection. (malloc() without free() in C, for example, or new without delete in C++).

Enter transaction scopes.

A transaction scope is designed to look and feel like the scope of a method in C#. The scope of local variables of the method is limited to the space between the curly braces:

Transaction scopes work in a similar fashion. The programmer puts up a transaction scope when creating the client transaction. You have seen this already in the section with the PhoneBook report generators (PhoneBook.Sample, Enhancing and using the PhoneBook.Domain library). Remember the construction

using(ClientTransaction.CreateRootTransaction().EnterDiscardingScope())
{
        // lots of domain object code here
        ClientTransaction.Current.Commit();
}

This scope limits the life of the scoped transaction to the space between the curly braces. When the scope is left, the client transaction is discarded automatically, just like a local variable is removed from the call stack when the function (or method) is left. The most interesting detail here is that ClientTransaction.Current always holds the client transaction instance that has been created for the scope ClientTransaction.Current is in. The using and its delimiting braces limit the scope of the transaction created for the using:

The call chain

ClientTransaction.CreateRootTransaction().EnterDiscardingScope()

is the most typical way of creating transactions and a corresponding transaction scope:

  • ClientTransaction.CreateRootTransaction() creates a new client (root) transaction (just as the name suggests). It is a static method belonging to the class ClientTransaction.
  • Form this transaction, and FOR this transaction, a transaction scope is created and prepared for using by EnterDiscardingScope().
  • this scope is returned to the embracing using
  • the using's scope - and with it the root transaction - is delimited by the curly braces '}' and '{'.

A discarding scope is a transaction scope that behaves as explained here: it makes the transaction that belongs to the scope vanish without a trace as soon as execution of the program leaves the scope. (The alternative is FIXME.)

Note how easy it is to answer the following questions for the illustrated listing above:

  • Which transaction is referenced by ClientTransaction.Current? Answer: the one created for the braces delimiting the transaction scope.
  • When will the transaction be discarded? Answer: As soon as the code leaves the section delimited by the transaction scope's closing curly bracket '}'.

As explained in the section FIXME above, root transactions are not all there is to it. Sub-transactions are created with the method CreateSubTransaction. This method is not static, however, it is a member of the given client transaction instance. For spawning a sub-transaction from a given client transaction, just call its CreateSubTransaction() method. Call EnterDiscardingScope() like you do for a root transaction.
This works for arbitrarily nested transactions and sub-transactions. Here is a more complicated illustration:

  • A.) In this listing, the program creates a new root transaction.
  • 1.) This root transaction is valid for the entire outer scope, i.e. ClientTransaction.Current in the outer scope references contains a reference to the corresponding root creation for which is has been created.
  • B.) The sub transaction is spawned off from the root transaction,
    2.) i.e. the client transaction referenced in the outer scope's ClientTransaction.Current.
  • 3.) Within that inner scope, ClientTransaction.Current holds a reference to that sub-transaction for which it has been created. As soon as execution leaves the inner scope, the sub-transaction is forgotten and falls prey to .NET's garbage collector.
  • 4.) The value of ClientTransaction.Current is restored so that it references the outer scope's root transaction again.

Sub-transactions can be spawned and nested to arbitrary depth:

using (ClientTransaction.CreateRootTransaction().EnterDiscardingScope())
{
  using (ClientTransaction.Current.CreateSubTransaction().EnterDiscardingScope())
  {
    using (ClientTransaction.Current.CreateSubTransaction().EnterDiscardingScope())
    {
      using (ClientTransaction.Current.CreateSubTransaction().EnterDiscardingScope())
      {
        using (ClientTransaction.Current.CreateSubTransaction().EnterDiscardingScope())
        {
          ... more, if you want... 
        }
      }
    }
  }
}