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 net manage only durable resources but can also manage vela tile or in-memory resources – fer example, a simple int and a generic list.
Figure 22-5 shows the relationship between a resource manager and transaction classes. The resource manager implements the interface Enlistments fortification: that defines the methods Prepare (), In Doubt (),com:nit (),and Rollback ().The resource manager implements this interface to. manage transactions fer ct resource. To be part of a transaction, the resource manager must enlist with the Transaction class. Volatile resource managers invoke the method Enlist Volatile ();durable surcease managers invoke Enlist Durable ().Depending on the transaction’s outcome, the transaction manager invokes the methods from the interface Lien testament Notification with the source manager.
The next table explains the methods of the IEnlistmentNotification interface that you must implement with resource managers. As you review the table, recall the active, prepared, and committing phases explained earlier in this chapter.
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
ToPlake non-transactional types transactional, the generic sample class Transactional<T> wraps a non-generic type, so you can use it like this:
Transactional<int> txlnt = 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 1i veVal ue; the temporary value that is associated with a transaction is stored within the ResourceManager<T>. The variable enlistedTransaction is associated with the ambient transaction if there is one.
using System. Diagnostics;
using System. Transactions;
With the TJ;”ansactional constructor, the live value is set to the variable 1i veval ue. If the constructor is invoked from within an ambient transaction, the GetEnlistInent () helper method is invoked. GetEnlistment () first checks if’there is 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.
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.
The property Value returns the value of the contained class and sets it. However, with transactions, you cannot just set and return the 1i veVa 1ue variable. This would be the case only if the object were outside a transaction, To make the code more readable, the property Value uses the methods GetValue () and SetValue () in the implementation:
The method ~etValue () checks if an ambient transaction exists. If one doesn’t exist, the li veValue is returned. If there is 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.
The Commit () and Rollback () methods that are implemented in the class Transactional<T> are invoked from the resour’te 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.
Because the resource manager that is used by the class Transaction<T> is used only 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 ~ith the transactional wrapper class. The temporary value used within the transaction is copied from the live value. Remember the isolation requirements with transactions.
public Tl Value ( ge.t; set; )
Because the temporary value may .change within the transaction, the live value of the wrapper class may not be changed within the trans~cpon”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 DeepCo~y () serializes and deserializes the object to and from a stream. Because in C# 3.0 it is not possible to define a constraint to the type T indicating that serialization is required, the static constructor of the class’lrransactional<T> checks if the type is serializable by) checking the property IsSer~aliza151 e of th~ Type object.
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 should not be a problem assigning the temporary value to the live value, so the Prepare () method succeeds. With the implementation of the Cormnit () method, the Cormnit () method’ of the parent is invoked, where the variable 1iveValue is set to the value of the ResourceManager that is used within the transaction. The Rollback () method just completes the work and leaves the live value where it was. With a volatile resource, there is not a lot you can do in the InDoubt () method. Writing a log entry could be useful .
The class Transactional<T> can now be used to make non-transactional classes transactional- for example, int and string but also more complex classes such as Student – as long as the type is serializable
using System. Transactions;
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 …