The COM Transaction Programming Model

   

The COM+ Transaction Programming Model

One of the greatest things about the COM+ transaction programming model is that most things are automatic, and those that aren't are quite easy to code. Once again, COM+ comes through with a very complex set of services in an easy-to-use package; however, certain rules remain invariant no matter how easy the programming task becomes. In fact, it's very easy to build a really bad COM+ component. In light of this fact, take a hard look at design issues as you work your way through the programming features and the design-specific section at the end of the chapter.

The basic design premise to consider when designing your components is that transactions are expensive. Avoid them if you can. If you can't avoid transactions, keep your transactions as fine-grained as possible. Reserving expensive resources for extended periods of time only exacerbates the transactional overhead. This means that you should complete all transactions as quickly as you can.

Here's a sketch of a typical COM+ transaction sequence:

  1. Client calls transactional COM+ object method.

  2. COM+ receives call and allocates a DTC transaction to attach to the object's context, then forwards the call to the object, creating and activating the object if necessary.

  3. Object method performs behavior, invoking the services of those other objects and resource managers that are automatically enlisted in the transaction.

  4. Upon completion, the object assesses the results of the various functions and either commits or aborts the transaction. (Resource managers subsequently make the transaction permanent or roll back the transactional changes.)

  5. Object returns status to the client and COM+ deactivates the object.

This execution sequence demonstrates one of the most powerful features of component-based transactions. Each of the objects involved in the transaction performs its task in a well-encapsulated way. The calling component does not need details regarding the implementation of the objects it calls. Should the called object make transactional updates to a resource manager, those updates are automatically enlisted in the existing transaction. This automatic transaction propagation ensures that fragments of the overall operation fail or succeed with the whole, regardless of the base object's knowledge of the subordinate object's specific behavior.

Creating Transactions

Creating transactions in COM+ is most often a declarative process. You can generate transactions procedurally, but this is not generally necessary. Transactions are automatically created by COM+ in much the same way that transactions automatically propagate from object to object and from object to resource manager. Usually the only manual part of the process is the final step, in which the object chooses to commit or abort the operation.

You use the custom attributes in Microsoft.ComServices to configure transactions. Using the AutoComplete attribute is an easy way out of having to call a SetComplete or SetAbort within the code. However, as in COM+, a component's interaction with transactions can be described with code. Access to the equivalent of the COM+ transaction interfaces is obtained through the ContextUtil class in Microsoft.ComServices. This class has numerous static methods and properties, so voting on the outcome of a transaction is a one-line statement, as follows :

 ConTextUtil.SetComplete(); 

Other familiar transaction- and context- related functionality in ContextUtil includes DisableCommit , DeactivateOnReturn , TransactionID , ActivityID , and more.

Object Pooling and Constructor Strings

Object pooling and constructor strings for managed classes follow similar rules as in COM+. The ObjectPooling and ContructionEnabled attributes enable these services on a class. The class ServicedComponent has virtual methods that may be overridden to perform the equivalent of the COM+ interfaces IObjectControl , IObjectConstruct , and IObjectConstructString . Although a default constructor string value can be coded, it is still possible to administratively override this value in the COM+ catalog. The following code uses C#; however, the runtime enables any language to make use of these services.

 [ObjectPooling(MinPoolSize=2, MaxPoolSize=50, CreationTimeOut=20)]  [ConstructionEnabled(Default="Hello world")]  public class DbAccount : ServicedComponent  {     DbAccount()     {        // First to get called     }     public override void Construct(string constructString)     {        // Called after constructor        // The default value for constructString is "Hello world"     }     public bool Perform ()     {          // .. update the database     }     public override void Activate()     {         // .. handle the Activate message     }     public override void Deactivate()     {         // .. handle the Deactivate message     }     public override bool CanBePooled()     {           // .. handle the CanBe Pooled message        return true; // or false;     }  } 

Shared Properties

In COM+, you manage shared transient state for objects in a single process by using the Shared Property Manager (SPM) resource dispenser .

Using the SPM in managed code involves manipulating instances of SharedPropertyGroupManager , SharedPropertyGroup , and SharedProperty . An application would create instances of only the SharedPropertyGroupManager class. The class SharedPropertyGroupManager is used to create and contain SharedPropertyGroup objects. A SharedPropertyGroup object creates and maintains a collection of SharedProperty objects. The following code creates a group manager, adds one group , and manipulates the salary property:

 SharedPropertyGroupManager groups = new SharedPropertyGroupManager();  SharedPropertyGroup group1 = groups.CreatePropertyGroup("Group1",        PropertyLockMode.LockSetGet,        PropertyReleaseMode.Standard,        alreadyExists);  groups.Group("Group1").Item("salary").Value = 100000000;  group1.PropertyByPosition(1).Value = 100000000;  for each SharedPropertyGroup group in groups     group.Property("salary").Value = 1000;  next group 
Automatic Transactions

COM+ creates or propagates automatic transactions any time a method of a transactional object is invoked. The one exception to this rule occurs when a transactional object retains a transaction for more than one method call. In this case, successive calls use the existing transaction. Objects can be configured to use transactions through the Component Management Console. Simply right-click the class in question and choose Properties. The Transaction tab of the Class Properties dialog box supplies four transaction settings:

  • Disabled

  • Not Supported

  • Supported

  • Required

  • Requires New

Disabled

This option eliminates transaction-related overhead that will never access a resource manager. This simulates the transaction behavior of an unconfigured COM object.

Not Supported

The next setting that's available, Not Supported, is the default for any standard COM object installed into a COM+ application. Objects of this type will never maintain a transaction in their context; neither will they pass any prior transactions on to objects that they create. Objects of this type are generally legacy components not specifically designed for COM+.

Supported

The Supported setting is for the whimsical classes that don't really care whether there's a transaction around or not, but will pass one along to the next object if need be. If an object of this type is called by a client that has a transaction, then the context of the current object is set with the client's transaction. In this way, the current object can pass the transaction along to other objects that it creates and that might need the transaction, even though the current object itself really has no interest in the transaction. If an object with this setting is created by a transactionless client, then the current object is also transactionless.

Required

The most common setting by far is Required. This setting ensures that all method calls (outside IUnknown ) operate within the protection of a transaction. If the object calling the current object already has a transaction, the current object inherits the existing transaction. This effectively blankets all the various objects invoked, directly or indirectly, from a base transactional object in the same transaction.

Requires New

The Requires New Transaction setting is used for components that must operate in an isolated transaction of their own. For example, if you have created an OrderEntry object for a sweater factory, a side effect of placing an order might be that you order new boxes when the supply gets low. You need a transaction for your box order, but you don't want the existing customer order to fail just because your box order has problems. This scenario could be managed by creating a separate class to handle the box order and setting it to require a new transaction. Now when the OrderEntry object calls the BoxOrder object, the OrderEntry object's transaction does not propagate to the BoxOrder object. MTS instead provides the BoxOrder object with a brand new transaction.

ITransactionContextEx

The TransactionContextEx interface provides access to the three external methods of the TransactionContextEx object. Perhaps you won't be surprised to hear that all TransactionContextEx objects require a new transaction. This provides client and server objects alike with an easy way to partition the transaction space or create a transaction where there was none before. You must include the txctx.h header to use the ITransactionContextEx interface. Table 24.1 lists the methods of the ITransactionContextEx interface.

Table 24.1. ITransactionContextEx Methods

Method

What It Does

Abort

Rolls back all transactional operations performed in the current transaction on return from the current method.

Commit

Attempts to commit all transactional operations performed in the current transaction on return from the current method. (If any of the objects participating in the transaction have called SetAbort or DisableCommit , the transaction is aborted.)

CreateInstance

Creates a new object within the TransactionContextEx object's transaction. (If the object class created does not support transactions, the object is still created, but it does not inherit the transaction.)

A transactionless object could easily construct a TransactionContextEx object (thus creating a transaction) and then use the CreateInstance() method of the ITransactionContextEx interface to create other objects within the new transaction. This is an important feature, because an object without a transaction cannot directly create an object that requires a transaction without creating a new transaction every time. Using the TransactionContextEx object, a client could create a pair of transaction-required savings and checking objects within the same transaction with the ITransactionContextEx::CreateInstance() method. A single transaction protecting a transfer between the two objects would then be possible.

The Commit() and Abort() methods of the ITransactionContextEx interface enable the client to vote to commit or roll back the effects of the transaction upon task completion. All three methods are actually just slight variations of the SetAbort() , SetComplete(), and CreateInstance() methods of the IObjectContext interfaces discussed in detail later in this chapter.

Completing Transactions

Transactional components must explicitly commit or abort an outstanding transaction. Failing to do so leaves the transaction up in the air, in which case it will commit if the object is released by the client within the transaction timeout. If the object is not released and the transaction times out, then the transaction is aborted. Leaving things to chance is hardly appropriate behavior for a mission-critical server component, and for this reason all correctly designed transactional components explicitly commit or abort their transactions. Keep in mind that all objects involved in a transaction are deactivated at the completion of the transaction. Transactions are managed through an object's ObjectContext .

ObjectContext

Every COM+ object has an ObjectContext associated with it. The ObjectContext maintains the implicit COM+ state information associated with the object. Of particular interest here is the fact that the ObjectContext tracks information related to the current transaction, if any exists. A COM+ object can manipulate its ObjectContext by calling the COM+ API function GetObjectContext() , which returns an IObjectContext interface pointer.

An object's ObjectContext should never be accessed in the object's constructor, destructor, or during any IUnknown method. In general, this is because COM+ has either not created the context or has already destroyed it during these object calls. It is important to note that calling an object's IUnknown methods does not activate it. ObjectContext references (interface pointers to IObjectContext ) should never be passed to other objects because they are generally invalid outside the owning object.

IObjectContext Revisited

This section focuses on the transactional features of IObjectContext . Security features of the IObjectContext interface are discussed in Chapter 18, "Security." Most of the methods available through IObjectContext are transaction-related. Table 24.2 shows the IObjectContext methods.

Table 24.2. IObjectContext Methods

Method

What It Does

CreateInstance

Creates an object within the current context.

DisableCommit

Declares that the object's transactional updates are in an inconsistent state. (The object retains its state across method calls.)

EnableCommit

Declares that the object's transactional updates are in a consistent state. (The object retains its state across method calls.)

IsCallerInRole

Indicates whether the object's caller is in a specified role.

IsInTransaction

Indicates whether the object is executing within a transaction.

IsSecurityEnabled

Indicates whether security is enabled.

SetAbort

Aborts the current transaction. (The object can be deactivated upon return.)

SetComplete

Declares that the object's transactional updates can be committed. (The object can be deactivated upon return.)

The CreateInstance method is the preferred way to construct new objects within MTS. With IObjectContext::CreateInstance() , newly created objects that support or require transactions inherit the current transaction. Calling CoCreateInstance() to create objects passes none of the current context information on to the new object. For this reason, CoCreateInstance() should be used only when you explicitly create objects outside the current COM+ activity.

DisableCommit is a useful call that enables a method to safeguard against accidental transaction commit behavior. For example, an object method that begins a transactional operation and then errantly or intentionally returns to the client before completing the transactional operation has its transaction committed if the client then releases the object within the transaction timeout. Object shutdown defaults to commit. DisableCommit disables the transaction and only an explicit call to the SetComplete method can commit the operation. Thus, should a client ever release an object maintaining a disabled transaction, MTS has no choice but to abort the transaction. Should the client make another method call to the object to complete the transaction, the object can call EnableCommit to re-enable the transaction or simply call SetComplete , which overrides the disable call and commits the transaction if possible.

EnableCommit re-enables a transaction, enabling the transaction to complete implicitly. This is the default state of a newly issued transaction. I don't know about you, but I have little use for implicit behavior in my mission-critical server components. Disabling the current transaction at the top of every external method call until the transaction is explicitly committed with SetComplete has stood me in good stead.

IsCallerInRole can be used to determine whether the direct caller of the currently executing method is associated with a specific role. A role is a symbolic name that represents a user or group of users who have specific access permissions to all components in a given package. Developers define roles when they create a component, and roles are mapped to individual users or groups at deployment time.

IsInTransaction simply returns True or False , depending on whether an active transaction is in context. This is handy for aborting in cases where a transaction is required but none exists.

IsSecurityEnabled is important because MTS security is enabled only if an object is running in a server process. This could be either because the object's component was configured to run in a client's process, or because the component and the client are in the same package. If the object is running in the client's process, there is no security checking and IsSecurityEnabled will always return false.

SetAbort is the method used by objects that want to abort the current transaction. This is a no- nonsense call that ensures the rollback of all transactional modifications to any resource managers involved. Objects voting to abort the current transaction can return any value they choose to the client application, and they are generally deactivated immediately after returning.

SetComplete is the method objects use when they are happy with the various operations they have performed and would like to commit changes to any resource managers involved in the transaction. Object methods invoking SetComplete should return S_OK to indicate success. Unfortunately, calling SetComplete is just the object's vote to complete the transaction and should any one resource manager vote to abort, the transaction fails completely. In this case, COM+ replaces the root object method's return value (if it's a success code) with CONTEXT_E_ABORTED . An object method calling SetComplete is a signal to MTS that the object can be deactivated. Stateful objects should never call SetComplete unless they can safely be destroyed.

Listing 24.1 provides an example of the one and only method for the transactional OrderEntry object. It shows how you can find out if code is part of a transaction, and then how to complete or abort the transaction.

Listing 24.1 PlaceOrder Method for the OrderEntry Object
 public int PlaceOrder( long AccountNumber,      long ProductID,      long Quantity)  {      //Ensure that we have a transaction      if ( ! m_ObjectContext.IsInTransaction() )          return CONTEXT_E_TMNOTAVAILABLE;      //Disable commit to keep an accidental      //  return in the body of code from      //  committing the transaction on client      //  object shutdown      m_ObjectContext.DisableCommit();      //Compute total price and deduct account balance within      //  the current transaction (all sweaters are )      double lfTotalPrice = Quantity * 20.0;      int hr = ChargeAccount( AccountNumber, lfTotalPrice );      if ( hr < 0 )      {          //Abort transaction and return error          m_ObjectContext.SetAbort();          return hr;      }      //Reduce inventory within the current transaction      hr = ReduceInventory( Quantity );      if ( hr < 0 )      {          //Abort transaction and return error          m_ObjectContext.SetAbort();          return hr;  }      //Commit transaction and return success      m_ObjectContext.SetComplete();      return S_OK;  } 

This code includes several points of interest. The most important is the fact that, aside from some sanity checking and defensive programming at the top of the routine, and the complete or abort calls tied to the returns, the code is devoid of cryptic transaction instructions. Note that the two routines ChargeAccount() and ReduceInventory() could directly modify a database or create a new component and have it do so. It matters little because the transaction automatically propagates to any new objects or resource managers that support transactions. Note that it is not a problem if you charge the account and then find that you're out of stock, because the transaction aborts when ReduceInventory() returns a failure code. This returns the account in question to its state prior to the attempted transactional updates.

Because you wouldn't want to try this kind of operation without a transaction, the first line of code in the routine makes sure that a transaction is available. If you find that you are without a transaction, you return the MTS code indicating that the transaction manager (TM) is not available. It is important to note that if you are operating outside MTS, the IObjectControl methods that you rely on to acquire and release your IObjectContext pointer are never invoked, which causes bad things any time you try to use the Active Template Library (ATL) provided m_spObjectContext . Checking this pointer before using it is not a bad idea, especially if you continually switch your component between MTS and straight COM.

The sample routine immediately disables implicit transaction commitment. This allows you the security of knowing that, no matter what happens, if you don't call SetComplete() , the transaction aborts. This is generally the idea with transactional methods, although not always. Finally, if the gauntlet of SetAbort() calls is survived, you assume everything went well and call SetComplete() to vote for committing the transaction.

At this point, the client receives one of three values: S_OK , CONTEXT_E_ABORTED , or possibly an application-specific return code. It's the "something else" that's bothering you isn't it? Well, here's the rotten truth of things: Anything can fail. If all's well, the client gets S_OK ; if the transaction couldn't be committed the client gets CONTEXT_E_ABORTED or an application-defined error; and if either of the two previous values are returned, but there's a network failure in your DCOM call, your client gets a network related HRESULT . Thus it's possible for the transaction to commit and yet have the client receive an obscure COM communications error. If this is not satisfactory, you have to come up with an application-specific way for the client to discover the result of a transactional call suffering just such a failure. No hints here. It's as ugly as it sounds. The best bet is to build clients that don't care about the transactions result, if at all possible. You always have the reassurance that the call either worked or it didn't.

The preceding example uses two subroutines: ReduceInventory() and ChargeAccount() . Both manipulate COM+-compliant resource managers (SQL Server in this case) to perform their functions. This brings us to the topic of data access under COM+.

   


Special Edition Using ASP. NET
Special Edition Using ASP.Net
ISBN: 0789725606
EAN: 2147483647
Year: 2002
Pages: 233

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