Section 7.9. Instance Management and Transactions


7.9. Instance Management and Transactions

Because the service instance goes through the trouble of retrieving its state and saving it on every method call, why wait till the end of the transaction to destroy the object? A per-call service would therefore be the most natural programming model for a transactional WCF service. In addition, the behavioral requirements for a state-aware transactional object and the requirements of a per-call object are the sameboth retrieve and save their state at the method boundaries (compare Example 4-3 with Example 7-15). In spite of the fact that the per-call instancing mode is the most congenial for transactions, WCF does support a per-session and even a singleton service, albeit with a considerably more complex programming model.

7.9.1. Per-Call Transactional Service

A far as a per-call service call is concerned, transactional programming is almost incidental. Every call on the service gets a new instance, and that call may or may not be in the same transaction as the previous call (see Figure 7-8).

Figure 7-8. Per-call service and transactions


Regardless of transactions, in every call the service gets and saves its state from a resource manager, so the methods are always guaranteed to operate on consistent state from the previous transaction or on the temporary yet well-isolated state of the current transaction in progress. A per-call service must vote and complete its transaction in every method call. In fact, a per-call service must always use auto-completion (have TRansactionAutoComplete set to trueits default).

From the client perspective, the same service proxy can participate in multiple transactions or in the same transactions. For example, in the following code snippet, every call will be in a different transaction:

 MyContractClient proxy = new MyContractClient( ); using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod(...);    scope.Complete( ); } using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod(...);    scope.Complete( ); } proxy.Close( ); 

Or, the client can use the same proxy multiple times in the same transaction, and even close the proxy independently of any transactions:

 MyContractClient proxy = new MyContractClient( ); using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod(...);    proxy.MyMethod(...);    scope.Complete( ); } proxy.Close( ); 

The call to Dispose( ) on a per-call service has no ambient transaction.


7.9.1.1. Transaction life cycle

When the per-call service is the root of a transaction (that is, when it is configured for Client/Service and there is no client transaction, or when it is configured for Service transaction) the transaction ends once the service instance is deactivated. As soon as the method returns, WCF completes and ends the transaction, even before Dispose( ) is called. When the client is the root of the transaction (or whenever the client's transaction flows to the service and the service joins it) the transaction ends when the client's transaction ends.

7.9.2. Per-Session Transactional Service

The default transaction configuration of WCF will turn any service, regardless of its instancing mode, into a per-call service. This behavior is geared toward consistency in the service state management, requiring the service to be state-aware. However, having a state-aware service negates the very need for a per-session service in the first place. WCF does allow you to maintain the session semantic with a transactional service. A per-session transactional service instance can be accessed by multiple transactions, or the instance can establish an affinity to a particular transaction, in which case, until it completes, only that transaction is allowed to access it. However, as you will see, this support harbors a disproportional cost in programming model complexity.

7.9.2.1. Releasing service instance

The life cycle of any non-per-call transactional service is controlled by the ReleaseServiceInstanceOnTransactionComplete Boolean property of the ServiceBehavior attribute:

 [AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : Attribute,... {    public bool ReleaseServiceInstanceOnTransactionComplete    {get;set;}    //More members } 

When ReleaseServiceInstanceOnTransactionComplete is set to true (the default value), it disposes of the service instance once the instance completes the transaction. Note that the release takes place once the instance completes the transaction, not necessarily when the transaction really completes (which could be much later). When ReleaseServiceInstanceOnTransactionComplete is true, the instance has two ways of completing the transaction and being released: at the method boundary if the method has transactionAutoComplete set to true, or when any method that has transactionAutoComplete set to false calls SetTransactionComplete( ).

ReleaseServiceInstanceOnTransactionComplete has two interesting interactions with other service and operation behavior properties. First, it cannot be set (to either TRue or false) unless at least one operation on the service has TRansactionScopeRequired set to true. This is validated at the service load time by the set accessor of the ReleaseServiceInstanceOnTransactionComplete property.

For example, this is a valid configuration:

 [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = true)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    [OperationBehavior(...)]    public void MyOtherMethod( )    {...} } 

What this constraint means is that even though the default of ReleaseServiceInstanceOnTransactionComplete is TRue, the following two definitions are not semantically equivalent, because the second one will throw an exception at the service load time:

 class MyService : IMyContract {    public void MyMethod( )    {...} } //Invalid definition [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = true)] class MyService : IMyContract {    public void MyMethod( )    {...} } 

The second constraint involved in using ReleaseServiceInstanceOnTransactionComplete relates to concurrent multithreaded access to the service instance.

Concurrency management is the subject of the next chapter. For now, the ConcurrencyMode property of the ServiceBehavior attribute controls concurrent access to the service instance:

 public enum ConcurrencyMode {    Single,    Reentrant,    Multiple } [AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : ... {    public ConcurrencyMode ConcurrencyMode    {get;set;}    //More members } 

The default value of ConcurrencyMode is ConcurrencyMode.Single.

WCF will verify at the service load time that if at least one operation on the service has TRansactionScopeRequired set to TRue when ReleaseServiceInstanceOnTransactionComplete is TRue (by default or explicitly), the service concurrency mode must be ConcurrencyMode.Single.

For example, given this contract:

 [ServiceContract] interface IMyContract {    [OperationContract]    [TransactionFlow(...)]    void MyMethod( );    [OperationContract]    [TransactionFlow(...)]    void MyOtherMethod( ); } 

the following two definitions are equivalent and valid:

 class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single,                  ReleaseServiceInstanceOnTransactionComplete = true)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } 

The following definition is also valid since no method requires a transaction scope even though ReleaseServiceInstanceOnTransactionComplete is true:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MyService : IMyContract {    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } 

In contrast, the following definition is invalid, because at least once method requires a transaction scope, ReleaseServiceInstanceOnTransactionComplete is TRue, and yet the concurrency mode is not ConcurrencyMode.Single.

 //Invalid configuration: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } 

The concurrency constraint applies to all instancing modes.


The ReleaseServiceInstanceOnTransactionComplete property can enable a transactional session interaction between the client and the service. By default it will have its value of true, which means that once the service instance completes the transaction (either declaratively or explicitly), the return of the method will deactivate the service instance as if it were a per-call service.

For example, the service in Example 7-16 behaves just like a per-call service.

Example 7-16. Per-session yet per-call transactional service

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract]    [TransactionFlow(...)]    void MyMethod( ); } class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...} } 

Every time the client calls MyMethod( ), the client will get a new service instance. The new client call may come in on a new transaction as well, and the service instance has no affinity to any transaction. The relationship between the service instances and the transactions is just as in Figure 7-8.

7.9.2.2. Disabling releasing the service instance

Obviously, a configuration such as Example 7-16 adds no value. To behave per-session, the service can set ReleaseServiceInstanceOnTransactionComplete to false, as in Example 7-17.

Example 7-17. Per-session transactional service

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract]    [TransactionFlow(...)]    void MyMethod( ); } [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...} } 

When ReleaseServiceInstanceOnTransactionComplete is false, the instance will not be disposed of once transactions complete, as shown in Figure 7-9.

Figure 7-9. Sessionful transactional instance and transactions


For example, the interaction of Figure 7-9 may be the result of the following client code, where all calls went to the same service instance:

 MyContractClient proxy = new MyContractClient( ); using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    scope.Complete( ); } using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    proxy.MyMethod( );    scope.Complete( ); } proxy.Close( ); 

7.9.2.3. State-aware per-session service

When ReleaseServiceInstanceOnTransactionComplete is false, WCF will stay out of the way, and will let the developer of the service worry about managing the state of the service instance in the face of transactions. Obviously, you have to somehow monitor transactions and roll back any changes made to the state of the instance if the transaction aborts. The per-session service still must equate method boundaries with transaction boundaries because every method may be in a different transaction. There are two possible programming models. The first is to be state-aware, but use the session ID as a state identifier. At the beginning of every method the service would get its state from a resource manager using the session ID as a key, and at the end of every method the service instance would save the state back to the resource manager, as shown in Example 7-18.

Example 7-18. State-aware, transactional per-session service

 [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)] class MyService : IMyContract,IDisposable {    readonly string m_StateIdentifier;    public MyService( )    {       InitializeState( );       m_StateIdentifier = OperationContext.Current.SessionId;       SaveState( );    }    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {       GetState( );       DoWork( );       SaveState( );    }    public void Dispose( )    {       RemoveState( );    }    //Helper methods    void InitializeState( )    {...}    void GetState( )    {       //Use m_StateIdentifier to get state       ...    }    void DoWork( )    {...}    void SaveState( )    {       //Use m_StateIdentifier to save state       ...    }    void RemoveState( )    {       // Use m_StateIdentifier to remove the state from the RM       ...    } } 

In Example 7-18, the constructor first initializes the state of the object, and then saves the state to a resource manager, so that any method can retrieve it. Note that the per-session object maintains the illusion of a stateful, sessionful interaction with its client. The client does not need to pass an explicit state identifier. The service must be disciplined, and retrieve and save the state in every operation call. When the session ends, the service purges its state from the resource manager in the Dispose( ) method.

7.9.2.4. Stateful per-session service

The second and more modern programming model is to use volatile resource managers for the service members (see the sidebar "Volatile Resource Managers"), as shown in Example 7-19.

Volatile Resource Managers

In the article "Volatile Resource Managers in .NET Bring Transactions to the Common Type" (MSDN Magazine, May 2005) I presented my technique for implementing a general-purpose volatile resource manager called transactional<T>:

 public class Transactional<T> : ... {    public Transactional(T value);    public Transactional( );    public T Value    {get;set;}    /* Conversion operators to and from T */ } 

By specifying any serializable type parameter such as an int or a string to transactional<T>, you turn that type into a full-blown volatile resource manager that auto-enlists in the ambient transaction, participates in the two-phase commit protocol, and isolates the current changes from all other transactions using my original transaction-based lock.

For example, in the following code snippet the scope is not completed. As a result the transaction aborts and the values of number and city revert to their pre-transaction state:

 Transactional<int> number = new Transactional<int>(3); Transactional<string> city                    = new Transactional<string>("New York"); using(TransactionScope scope = new TransactionScope( )) {    city.Value = "London";    number.Value = 4;    number.Value++;    Debug.Assert(number.Value == 5);    Debug.Assert(number == 5); } Debug.Assert(number == 3); Debug.Assert(city == "New York"); 

In addition to TRansactional<T> I also provided a transactional array called TRansactionalArray<T> and well as a transactional version for all of the collections in System.Collections.Generic, such as transactionalDictionary<K,T> and transactionalList<T>. The implementation of my volatile resource managers has nothing to do with WCF, and therefore I chose not to include it in this book. The implementation, however, makes intense use of the more advanced features of C# 2.0, System.Transactions, and .NET system programming, and it may be of interest for its own merit.


Example 7-19. Using volatile resource managers to achieve stateful per-session transactional service

 [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)] class MyService : IMyContract {    Transactional<string> m_Text =                          new Transactional<string>("Some initial value");    TransactionalArray<int> m_Numbers = new TransactionalArray<int>(3);    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {       m_Text.Value = "This value will roll back if the transaction aborts";       //These will roll back if the transaction aborts       m_Numbers[0] = 11;       m_Numbers[1] = 22;       m_Numbers[2] = 33;    } } 

Example 7-19 uses my transactional<T> and transactionalArray<T> volatile resource managers, which are available with the source code of this book. Using generics, transactional<T> can take any serializable type and provide transactional access to it. So, the per-session service can safely set ReleaseServiceInstanceOnTransactionComplete to false and yet freely access its members. The use of the volatile resource managers enables a stateful programming model, and the service instance simply accesses its state as if no transactions were involved. The volatile resource managers auto-enlist in the transaction and isolate that transaction from all other transactions. Any changes made to the state will commit or roll back with the transaction.

7.9.2.5. Transaction life cycle

When the per-session service is the root of the transaction, the transaction ends once the service completes the transaction, which is when the method returns. When the client is the root of transaction (or when a transaction flows to the service) the transaction ends when the client's transaction ends. If the per-session service provides an IDisposable implementation, the Dispose( ) method will not have any transaction regardless of the root.

7.9.2.6. Concurrent transactions

Because a per-session service can engage the same service instance in multiple client calls, it could also sustain multiple concurrent transactions. Given the service definition of Example 7-17, Example 7-20 shows some client code that launches concurrent transactions on the same instance. scope2 will use a new transaction separate from that of scope1, and yet access the same service instance in the same session.

Example 7-20. Launching concurrent transactions

 using(TransactionScope scope1 = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );    using(TransactionScope scope2                         = new TransactionScope(TransactionScopeOption.RequiresNew))    {       proxy.MyMethod( );       scope2.Complete( );    }    proxy.MyMethod( );    proxy.Close( );    scope1.Complete( ); } 

The resulting transactions of Example 7-20 are depicted in Figure 7-10.

Figure 7-10. Concurrent transactions


Code such as in Example 7-20 will almost certainly result in a transactional deadlock over the underlying resources the service accesses. The first transaction will obtain the resource lock. The second transaction will wait to own that lock while the first transaction waits for the second to complete.


7.9.2.7. Completing on session end

WCF offers yet another programming model for transactional per-session services, which is completely independent of ReleaseServiceInstanceOnTransactionComplete. This model is available for the case when the lifeline of the session is only a part of the transaction lifeline, meaning the entire session fits into a single transaction. The idea is that the service should not complete the transaction inside the session, because that is what causes WCF to release the service instance. To avoid completing the transaction, a per-session service can set TRansactionAutoComplete to false, as shown in Example 7-21.

Example 7-21. Setting TransactionAutoComplete to false

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract]    [TransactionFlow(...)]    void MyMethod1( );    [OperationContract]    [TransactionFlow(...)]    void MyMethod2( );    [OperationContract]    [TransactionFlow(...)]    void MyMethod3( ); } class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod1( )    {...}    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod2( )    {...}    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod3( )    {...} } 

Note that only a per-session service can set transactionAutoComplete to false, and that is verified at the service load time. The problem with Example 7-21 is that the transaction the service participates in will always abort because the service does not vote to commit it. If the lifetime of the session is completely included in a single transaction, the service should vote once the session ends. For that purpose the ServiceBehavior attribute provides the Boolean property transactionAutoCompleteOnSessionClose, defined as:

 [AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : Attribute,... {    public bool TransactionAutoCompleteOnSessionClose    {get;set;}    //More members } 

The default of TRansactionAutoCompleteOnSessionClose is false. However, when set to true, it will auto-complete all uncompleted methods in the session. If no exceptions occurred during the session, when transactionAutoCompleteOnSessionClose is true the service will vote to commit. For example, here is how to retrofit Example 7-21; Figure 7-11 shows the resulting instance and its session:

 [ServiceBehavior(TransactionAutoCompleteOnSessionClose = true)] class MyService : IMyContract {...} 

Figure 7-11. Setting TransactionAutoCompleteOnSessionClose to true


During the session, the instance can maintain and access its state in member variables, and there is no need for state awareness or volatile resource managers.

When joining the client's transaction and relying on auto-completion on session close, the service must avoid lengthy processing in Dispose( ) or, in practical terms, avoid implementing IDisposable altogether. The reason is the following race condition. Recall from Chapter 4, that Dispose() is called asynchronously at the end of the session. Auto-completion at session end takes place once the instance is disposed. If the client has control before the instance is disposed, the transaction will abort because the service did not complete it yet.


Note that using transactionAutoCompleteOnSessionClose is risky because it is always subjected to the transaction timeout. Sessions are by their very nature long-living entities, while well-designed transactions are short-lived. This programming model is available for the case when the vote decision requires information obtained by future calls throughout the session.

Because having TRansactionAutoCompleteOnSessionClose set to TRue equates the session's end with the transaction's end, it is required that when the client's transaction is used, that the client terminates the session within that transaction:

 using(TransactionScope scope = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );    proxy.MyMethod( );    proxy.Close( );    scope.Complete( ); } 

Failing to do so will abort the transaction. A side effect of this is that the client cannot easily stack the using statements of the transaction scope and the proxy because that may cause the proxy to be disposed after the transaction:

 //This always aborts: using(MyContractClient proxy = new MyContractClient( )) using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    proxy.MyMethod( );    scope.Complete( ); } 

In addition, because the proxy is basically good for only one-time use, there is little point in storing the proxy in member variables.

7.9.2.8. Transactional affinity

Setting transactionAutoComplete to false has a unique effect that nothing else in WCF provides: it creates an affinity between the service instance and the transaction, so that only that single transaction can ever access the service instance. The affinity is established once the first transaction accesses the service instance, and once established it is fixed for the life of the instance (until the session ends). Transactional affinity is only available for per-session services because only a per-session service can set transactionAutoComplete to false. Affinity is crucial because the service is not state-awareit uses normal members, and it must isolate access to them from any other transaction, in case the transaction it has an affinity to aborts. Affinity thus offers a crude form of transaction-based locking. With transaction affinity, code such as Example 7-20 is guaranteed to deadlock (and eventually abort due to timing out) because the second transaction is blocked (independently of any resources the service accesses) waiting for the first transaction to finish, while the first transaction is blocked waiting for the second.

7.9.2.9. Hybrid state management

WCF also supports a hybrid mode of the two programming models shown so far, combining both a state-aware and a stateful transactional per-session service. The hybrid mode is designed to allow the service instance to maintain in-memory state until it can complete the transaction and then recycle state using ReleaseServiceInstanceOnTransactionComplete. Consider the service in Example 7-22 that implements the contract from Example 7-21.

Example 7-22. Hybrid per-session service

 [ServiceBehavior(TransactionAutoCompleteOnSessionClose = true)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod1( )    {...}    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod2( )    {...}    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod3( )    {...} } 

The service uses the default of transactionAutoCompleteOnSessionClose (false) and yet it has two methods (MyMethod1( ) and MyMethod2( )) that do not complete the transaction and have TRansactionAutoComplete set to false, which creates an affinity to a particular transaction. The affinity isolates access to its members from any other transaction, in case the transaction it has an affinity to aborts. The problem now is that the service will always abort that transaction because it does not complete it. To compensate for that, the service offers MyMethod3( ), which does complete the transaction. Because the service uses the default of ReleaseServiceInstanceOnTransactionComplete (TRue), after calling MyMethod3( ), the transaction is completed and the instance is disposed of, as shown in Figure 7-12. Note that MyMethod3( ) could have instead used explicit voting via SetTransactionComplete( ). The important thing is that it completes the transaction.

Figure 7-12. Hybrid state management


The hybrid mode is inherently a brittle proposition. First, the service instance must complete the transaction before it times out. Since there is no telling when the client will call the completing method, you risk timing out before that. In addition, the service also prolongs holding on to any locks on resource managers it may access for the duration of the session. The longer the locks are held, the higher the likelihood of other transactions timing out or deadlocking with this service's transaction. Finally, the service is at the mercy of the client because the client must call the completing method to end the session. You can and should use demarcating operations to try to force this on the client:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract]    [TransactionFlow(...)]    void MyMethod1( );    [OperationContract(IsInitiating = false)]    [TransactionFlow(...)]    void MyMethod2( );    [OperationContract(IsInitiating = false,IsTerminating = true)]    [TransactionFlow(...)]    void MyMethod3( ); } 

7.9.2.10. Choosing per-session transactional service

Transactional sessions are available for situations when the transaction execution and subsequent voting decision requires information obtained throughout the session. Consider, for example, the following contract used for order processing:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IOrderManager {    [OperationContract]    [TransactionFlow(...)]    void SetCustomerId(int customerId);    [OperationContract(IsInitiating = false)]    [TransactionFlow(...)]    void AddItem(int itemId);    [OperationContract(IsInitiating = false,IsTerminating = true)]    [TransactionFlow(...)]    bool ProcessOrders( ); } 

The implementing service can only process the order once it has the customer ID and all its ordered items. However, relying on transactional sessions usually indicates poor design because of its inferior throughput and scalability implications. The service must maintain locks longer and risk deadlocks. I consider a transactional session a design aberration at best and an anathema at worst. The disproportional complexity of a transactional session resulting from the state management, transactional affinity, and transaction completion outweigh the perceived benefit of a session. It is usually better to factor the contract so that it does not rely on a session:

 [ServiceContract] interface IOrderManager {    [OperationContract]    [TransactionFlow(...)]    bool ProcessOrders(int customerId,int[] itemIds); } 

I prefer the simplicity of the per-call service and avoid transactional sessions.

7.9.3. Transactional Singleton

By default, a transactional singleton behaves like a per-call service. The reason is that by default ReleaseServiceInstanceOnTransactionComplete is true, and so after the singleton auto-completes a transaction, WCF disposes of the singleton, in the interest of state management and consistency. This in turn implies that the singleton must be state-aware, and proactively manage its state in every method call, in and out of a resource manager. The big difference compared to a per-call service is that WCF will enforce the semantic of the single instance, so at any point in time there is at most a single instance running. WCF uses concurrency management and instance deactivation to enforce this rule. Recall that when ReleaseServiceInstanceOnTransactionComplete is true, the concurrency mode must be ConcurrencyMode.Single to disallow concurrent calls. WCF keeps the singleton context and merely deactivates the instance hosted in the context, as discussed in Chapter 4. What this means is that even though the singleton needs be state-aware, it does not need an explicit state identifier to be provided by the client in every call. The singleton can use any type-level constant to identify its state in the state resource manager, as shown in Example 7-23.

Example 7-23. State-aware singleton

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] class MySingleton : IMyContract {    readonly static string m_StateIdentifier = typeof(MySingleton).GUID.ToString( );    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {       GetState( );       DoWork( );       SaveState( );    }    //Helper methods    void GetState( )    {       //Use m_StateIdentifier to get state    }    void DoWork( )    {}    public void SaveState( )    {       //Use m_StateIdentifier to save state    }    public void RemoveState( )    {       //Use m_StateIdentifier to remove the state from the resource manager    } } //Hosting code MySingleton singleton  = new MySingleton( ); singleton.SaveState( );//Create the initial state in the resource manager ServiceHost host = new ServiceHost(singleton); host.Open( ); /* Some blocking calls */ host.Close( ); singleton.RemoveState( ); 

In the example, the singleton uses the unique GUID associated with every type as a state identifier. At the beginning of every method the singleton reads its state, and at the end of each method its saves the state back to the resource manager. However, the first call on the first instance ever must also be able to bind to the state, so you must prime the resource manager with the state before the first call ever arrives. To that end, before launching the host, you need to create the singleton, save its state to the resource manager, and then provide the singleton instance to host to ServiceHost as explained in Chapter 4. After the host shuts down, make sure to remove the singleton state from the resource manager, as shown in Example 7-23. Note that you cannot create the initial state in the singleton constructor, because the constructor will be called for each operation on the singleton and override the previous state saved. While a state-aware singleton is certainly possible (as demonstrated in Example 7-23), the overall complexity involved makes it a technique to avoid. It is better to use a stateful transactional singleton as presented next.

7.9.3.1. Stateful singleton

By setting ReleaseServiceInstanceOnTransactionComplete to false, you regain the singleton semantic. The singleton will be created just once when the host is launched and the same single instance will be shared across all clients and transactions. The problem is of course how to manage the state of the singleton. The singleton has to have state; otherwise there is no point in making it a singleton in the first place. The solution as before with the stateful per-session service is to use volatile resource managers as member variables, as shown in Example 7-24.

Example 7-24. Achieving stateful singleton transactional service

 ////////////////// Service Side ////////////////////////////////////// [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,                  ReleaseServiceInstanceOnTransactionComplete = false)] class MySingleton : IMyContract {    Transactional<int> m_Counter = new Transactional<int>( );    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {       m_Counter.Value++;       Trace.WriteLine("Counter: " + m_Counter.Value);    } } ////////////////// Client Side ////////////////////////////////////// using(TransactionScope scope1 = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );    proxy.Close( );    scope1.Complete( ); } using(TransactionScope scope2 = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );    proxy.Close( ); } using(TransactionScope scope3 = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );    proxy.Close( );    scope3.Complete( ); } ////////////////// Output ////////////////////////////////////// Counter: 1 Counter: 2 Counter: 2 

In Example 7-24, a client creates three transactional scopes, each with its own new proxy to the singleton. In each call, the singleton increments a counter it maintains as a transactional<int> volatile resource manager. scope1 completes the transaction and commits the new value of the counter (1). In scope2, the client calls the singleton and temporarily increments the counter to 2. However, scope2 does not complete its transaction. The volatile resource manager rejects the increment and reverts to its previous value of 1. The call in scope3 then increments the counter again from 1 to 2, as shown in the trace output.

Note that when setting ReleaseServiceInstanceOnTransactionComplete, the singleton must have at least one method with transactionScopeRequired set to true.

In addition, the singleton must have transactionAutoComplete set to true on every method, which of course precludes any transactional affinity, and allows concurrent transactions. All calls and all transactions are routed to the same instance. For example, the following client code will result in the transaction diagram shown in Figure 7-13 using (MyContractClient proxy = new MyContractClient( )).

 using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    scope.Complete( ); } using(MyContractClient proxy = new MyContractClient( )) using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    proxy.MyMethod( );    scope.Complete( ); } 

Figure 7-13. Stateful transactional singleton


7.9.4. Instancing Modes and Transactions

As you can see from the discussion so far, the moment you configure your transactional service for anything besides per-call instancing mode, you incur a disproportional increase in the complexity of the programming model. While WCF is powerful and extensible enough to support the widest range of configuration permutations, I recommend that you stick with the per-call service. A transactional singleton using a volatile resource manager is agreeable, as long as you can tolerate the singleton in the first place. To summarize the topic of instance management modes and transactions, Table 7-3 lists the configurations discussed so far. Note that Table 7-3 only lists the possible configurations and their resulting effects. Other combinations may be technically allowed but are nonsensical, or are plainly disallowed by WCF.

Table 7-3. Instancing mode, configurations and transactions

Configured instance mode

Auto complete

Release on complete

Complete on session end

Resulting instance mode

State management

Transaction affinity

Per call

True

True/false

True/false

Per call

State-aware

Call

Session

False

True/false

True

Session

Stateful

Instance

Session

True

False

True/false

Session

Stateful (State-aware, VRM)

Call

Session

True

True

True/false

Per call

State-aware

Call

Session

Hybrid

True

True/false

Hybrid

Hybrid

Instance

Singleton

True

True

True/false

Per call

State-aware

Call

Singleton

True

False

True/false

Singleton

Stateful (State-aware, VRM)

Call





Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

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