Custom Resource Managers


One of the biggest advantages of the new transaction model is that it is relatively easy to create custom resource managers that participate in the transaction. A resource manager does not necessarily manage durable resources but can also manage volatile or in-memory resources; for example, a simple int and a generic list.

Figure 21-5 shows the relationship between a resource manager and transaction classes. The resource manager implements the interface IEnlistmentNotification that defines the methods Prepare(), InDoubt(), Commit(), and Rollback(). The resource manager implements this interface to manage transactions for a resource. To be part of a transaction, the resource manager must enlist with the Transaction class. Volatile resource managers invoke the method EnlistVolatile(); durable resource managers invoke EnlistDurable(). Depending on the transaction’s outcome, the transaction manager invokes the methods from the interface IEnlistmentNotification with the resource manager.

image from book
Figure 21-5

The next table explains the methods of the IEnlistmentNotification interface that you must implement with resource managers.

Tip 

Remember the active, prepared, and committing phases explained earlier in this chapter.

Open table as spreadsheet

IEnlistmentNotification Members

Description

Prepare()

The transaction manager invokes the Prepare method for preparation of the transaction. The resource manager completes the preparation by invoking the Prepared() method of the PreparingEnlistment parameter, which is passed to the Prepare() method. If the work cannot be done successfully, the resource manager informs the transaction manger by invoking the method ForceRollback().

A durable resource manager must write a log so that it can finish the transaction successfully after the prepare phase.

Commit()

When all resource managers successfully prepared for the transaction, the transaction manager invokes the Commit() method. The resource manager can now complete the work to make it visible outside the transaction and invoke the Done() method of the Enlistment parameter.

Rollback()

If one of the resources could not successfully prepare for the transaction, the transaction manager invokes the Rollback() method with all resource managers. After the state is returned to the state previous of the transaction, the resource manager invokes the Done() method of the Enlistment parameter.

InDoubt()

If there was a problem after the transaction manager invoked the Commit() method (and the resources didn’t return a completion information with the Done() method), the transaction manager invokes the InDoubt() method.

Transactional Resources

A transactional resource must keep the live value and a temporary value. The live value is read from outside the transaction and defines the valid state when the transaction rolls back. The temporary value defines the valid state of the transaction when the transaction commits.

To make nontransactional types transactional, the generic sample class Transactional<T> wraps a nongeneric type, so you can use it like this:

  Transactional<int> txInt = new Transactional<int>(); Transactional<string> txString = new Transactional<string>(); 

Let’s look at the implementation of the class Transactional<T>. The live value of the managed resource has the variable liveValue, the temporary value that is associated with a transaction is stored within the ResoureManager<T>. The variable enlistedTransaction is associated with the ambient transaction if there’s one.

  using System; using System.Diagnostics; using System.Transactions; using System.Collections.Generic; namespace Wrox.ProCSharp.Transactions {    public partial class Transactional<T>    {       private T liveValue;       private ResourceManager<T> enlistment;       private Transaction enlistedTransaction; 

With the Transactional constructor, the live value is set to the variable liveValue. If the constructor is invoked from within an ambient transaction, the GetEnlistment() helper method is invoked. GetEnlistment() first checks if there’s an ambient transaction and asserts if there is none. If the transaction is not already enlisted, the ResourceManager<T> helper class is instantiated, and the resource manager is enlisted with the transaction by invoking the method EnlistVolatile(). Also, the variable enlistedTransaction is set to the ambient transaction.

If the ambient transaction is different from the enlisted transaction, an exception is thrown. The implementation does not support changing the same value from within two different transactions. If you have this requirement, you can create a lock and wait for the lock to be released from one transaction before changing it within another transaction.

  public Transactional(T value) {    if (Transaction.Current == null)    {       this.liveValue = value;    }    else    {       this.liveValue = default(T);       GetEnlistment().Value = value;    } } public Transactional()       : this(default(T)) {} private ResourceManager<T> GetEnlistment() {    Transaction tx = Transaction.Current;    Trace.Assert(tx != null, "Must be invoked with ambient transaction");    if (enlistedTransaction == null)    {       enlistment = new ResourceManager<T>(this, tx);       tx.EnlistVolatile(enlistment, EnlistmentOptions.None);       enlistedTransaction = tx;       return enlistment;    }    else if (enlistedTransaction == Transaction.Current)    {       return enlistment;    }    else    {       throw new TransactionException(             "This class only supports enlisting with one transaction");    } } 

The property Value returns the value of the contained class and sets it. However, with transactions you cannot just set and return the liveValue variable. This would only be the case if the object were outside a transaction. To make the code more readable, the property Value uses the method GetValue() and SetValue() in the implementation.

  public T Value {    get { return GetValue(); }    set { SetValue(value); } } 

The method GetValue() checks if an ambient transaction exists. If this is not the case, the liveValue is returned. If there’s an ambient transaction, the GetEnlistment() method shown earlier returns the resource manager, and with the Value property the temporary value for the contained object within the transaction is returned.

The method SetValue() is very similar to GetValue(); the difference is that it changes the live or temporary value.

  protected virtual T GetValue() {    if (Transaction.Current == null)    {       return liveValue;    }    else    {       return GetEnlistment().Value;    } } protected virtual void SetValue(T value) {    if (Transaction.Current == null)    {       liveValue = value;    }    else    {       GetEnlistment().Value = value;    } } 

The Commit() and Rollback() methods that are implemented in the class Transactional<T> are invoked from the resource manager. The Commit() method sets the live value from the temporary value received with the first argument and nullifies the variable enlistedTransaction as the transaction is completed. With the Rollback() method, the transaction is completed as well, but here the temporary value is ignored and the live value is kept in use.

     internal void Commit(T value, Transaction tx)    {       liveValue = value;       enlistedTransaction = null;    }       internal void Rollback(Transaction tx)    {       enlistedTransaction = null;    } } 

Because the resource manager that is used by the class Transactional<T> is only used within the Transactional<T> class itself, it is implemented as an inner class. With the constructor, the parent variable is set to have an association with the transactional wrapper class. The temporary value used within the transaction is copied from the live value. Remember the isolation requirements with transactions.

  using System; using System.Transactions; using System.Diagnostics; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace Wrox.ProCSharp.Transactions {    public partial class Transactional<T>    {       internal class ResourceManager<T1> : IEnlistmentNotification       {          private Transactional<T1> parent;          private Transaction currentTransaction;          private T1 tempValue;          internal ResourceManager(Transactional<T1> parent, Transaction tx)          {             this.parent = parent;             tempValue = DeepCopy(parent.liveValue);             currentTransaction = tx;          }          public T1 Value          {             get { return tempValue; }             set { tempValue = value; }          } 

Because the temporary value may change within the transaction, the live value of the wrapper class may not be changed within the transaction. When creating a copy with some classes, it is possible to invoke the Clone() method that is defined with the ICloneable interface. However, as the Clone() method is defined, it allows implementations to create either a shallow or a deep copy. If type T contains reference types and implements a shallow copy, changing the temporary value would also change the original value. This would be in conflict with the isolation and consistency features of transactions. Here a deep copy is required.

To do a deep copy, the method DeepCopy() serializes and deserializes the object to and from a stream. Because in C# 2.0 it is not possible to define a constraint to the type T indicating that serialization is required, the static constructor of the class Transactional<T> checks if the type is serializable by checking the property IsSerializable of the Type object.

  static ResourceManager() {    Type t = typeof(T1);    Trace.Assert(t.IsSerializable, "Type " + t.Name + " is not serializable"); } private T1 DeepCopy(T1 value) {    using (MemoryStream stream = new MemoryStream())    {       BinaryFormatter formatter = new BinaryFormatter();       formatter.Serialize(stream, value);       stream.Flush();       stream.Seek(0, SeekOrigin.Begin);       return (T1)formatter.Deserialize(stream);    } } 

The interface IEnlistmentNotification is implemented by the class ResourceManager<T>. This is the requirement for enlisting with transactions.

The implementation of the Prepare() method just answers by invoking Prepared() with preparingEnlistment. There shouldn’t be a problem assigning the temporary value to the live value, so the Prepare() method succeeds. With the implementation of the Commit() method, the Commit() method of the parent is invoked, where the variable liveValue is set to the value of tempValue. The Rollback() method just completes the work and leaves the live value where it was. With a volatile resource there’s not a lot you can do in the InDoubt() method. Writing a log entry could be useful.

           public void Prepare(PreparingEnlistment preparingEnlistment)          {             preparingEnlistment.Prepared();          }                    public void Commit(Enlistment enlistment)          {             parent.Commit(tempValue, currentTransaction);             enlistment.Done();          }          public void Rollback(Enlistment enlistment)          {             parent.Rollback(currentTransaction);             enlistment.Done();          }          public void InDoubt(Enlistment enlistment)          {             enlistment.Done();          }       }    } } 

The class Transactional<T> can now be used to make nontransactional classes transactional; for example, int and string but also more complex classes such as Student - as long as the type is serializable:

  using System; using System.Transactions; namespace Wrox.ProCSharp.Transactions {    class Program    {       static void Main()       {          Transactional<int> intVal = new Transactional<int>(1);          Transactional<Student> student1 = new Transactional<Student>(new Student());          student1.Value.Firstname = "Andrew";          student1.Value.Lastname = "Wilson";          Console.WriteLine("before the transaction, value: {0}", intVal.Value);          Console.WriteLine("before the transaction, student: {0}", student1.Value);          using (TransactionScope scope = new TransactionScope())          {             intVal.Value = 2;             Console.WriteLine("inside transaction, value: {0}", intVal.Value);             student1.Value.Firstname = "Ten";             student1.Value.Lastname = "Sixty-Nine";             if (!Utilities.AbortTx())                scope.Complete();          }          Console.WriteLine("outside of transaction, value: {0}", intVal.Value);          Console.WriteLine("outside of transaction, student: {0}", student1.Value);       }    } } 

The following console output shows a run of the application with a committed transaction:

 before the transaction, value: 1 before the transaction: student: Andrew Wilson inside transaction, value: 2 Abort the Transaction (y/n)? n outside of transaction, value: 2 outside of transaction, student: Ten Sixty-Nine Press any key to continue . . .




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net