Nontransactional Clients

Consider the situation in which a nontransactional client creates a few transactional objects, all configured to require transactions. The client would like to scope all its interactions with the objects it creates under one transaction—in essence, to function like the root of that transaction. The problem is that the client is not configured to require transactions (maybe it is a legacy component or maybe it is not even a component, such as a form or a script client), so it cannot have a transaction to include the objects it creates. On the other hand, the objects require transaction support to operate properly, so for every object the client creates, COM+ creates a transaction. As a result, even if the client intended to combine the work of multiple COM+ objects into a single transaction, the net result would be multiple transactions (see Figure 4-11). The real problem now is that each transaction can commit or abort independently. The operations the client performs on the system (using the objects) are no longer atomic, so the client jeopardizes system consistency. Furthermore, even if all objects were under one transaction, how would the client vote to commit or abort that transaction?

A nontransactional client ends up with multiple transactions instead of one

Figure 4-11.  A nontransactional client ends up with multiple transactions instead of one

There is an elegant and simple solution to this predicament. The solution is to introduce a middleman—a transactional component that creates the objects on behalf of the client. The middleman creates the objects and returns interface pointers back to the client. The middleman objects also should provide the client with ability to commit or abort the transaction.

These middleman requirements are generic. Therefore, COM+ provides a readymade middleman called the transaction context component. As part of the COM+ Utilities application, COM+ provides two components (one for VB 6.0 and one for C++), each supporting a slightly different interface. A VB 6.0 client should use the ITransactionContext interface, creatable via the prog-ID TxCtx.TransactionContext (or the class name TransactionContext). A C++ client should use the ITransactionContextEx interface, which is creatable via the class ID CLSID_TransactionContextEx.

The two interfaces are defined as:

interface ITransactionContext : IDispatch 
{
   HRESULT CreateInstance([in]BSTR pszProgId,[out, retval]VARIANT* pObject);
   HRESULT Commit(  );
   HRESULT Abort(  );
};
interface ITransactionContextEx : IUnknown 
{
   HRESULT CreateInstance([in]GUID* rclsid,[in]IID* riid,
                          [out,retval]void** pObject);
   HRESULT Commit(  );
   HRESULT Abort(  );
};

These interfaces allow the client to create new component instances and commit or abort the transaction. The two transaction context components are configured to require a new transaction, so they are always the root of that transaction. This configuration also prevents you from misusing the transaction context objects by enlisting them in an existing transaction. All objects created by the transaction context object share the same transaction (see Figure 4-12).

Using a middleman, a nontransactional client ends up with one transaction

Figure 4-12. Using a middleman, a nontransactional client ends up with one transaction

All the client has to do is create the transaction context object, and then use it to create the other objects via the CreateInstance( ) method. If the client wants to commit the transaction, it must explicitly call the Commit( ) method. Once the client calls the Commit( ) method, the transaction ends on return from the Commit( ) method. If one of the internal objects votes to abort the transaction before the client calls Commit( ), the client’s call to Commit( ) returns with the error code of CONTEXT_E_ABORT , indicating that the transaction was already aborted. The client can chose to start a new transaction or handle the error in some other manner.

If the client does not call Commit( ), the transaction is aborted, even if all the participating objects voted to commit. This abortion is intentional, to force the client to voice its opinion on the work done by the objects it created. Only the client knows whether their combined work was consistent and legitimate. Apparently, when the client creates the transaction context object, the transaction context object sets the consistency bit to FALSE and never deactivates itself. As a result, the transaction is doomed unless the client calls Commit( ), which causes the transaction context object to change the bit back to TRUE and deactivate itself, thus ending the transaction.

The client can abort the combined transaction by calling the Abort( ) method. The transaction ends on return from the Abort( ) method. The client is also responsible for releasing the references it has on the internal objects created by the transaction context object. It is a good practice to do so even though these objects are released when the transaction ends.

Example 4-6 shows how to use the transaction context object. In the example, the client creates the transaction context object and then uses it to create three transactional objects (as in Figure 4-12). The client votes to commit or abort the transaction, based on the combined success of the method invocations on the three objects.

Example 4-6. Using the transaction context object to create three transactional objects

HRESULT hres1 = S_OK;
HRESULT hres2 = S_OK;
HRESULT hres3 = S_OK;

IMyInterface* pObj1= NULL;
IMyInterface* pObj2= NULL;
IMyInterface* pObj3= NULL;
ITransactionContextEx* pTransContext = NULL;

::CoCreateInstance(CLSID_TransactionContextEx,NULL,CLSCTX_ALL,
                   IID_ITransactionContextEx,(void**)&pTransContext);

pTransContext->CreateInstance(CLSID_MyComponent,IID_IMyInterface,(void**)&pObj1);
pTransContext->CreateInstance(CLSID_MyComponent,IID_IMyInterface,(void**)&pObj2);
pTransContext->CreateInstance(CLSID_MyComponent,IID_IMyInterface,(void**)&pObj3);

hres1 = pObj1->MyMethod(  );
hres2 = pObj2->MyMethod(  );
hres3 = pObj3->MyMethod(  );

if(S_OK == hres1 && S_OK == hres2 && S_OK == hres3)
   pTransContext->Commit(  );
else
   pTransContext->Abort(  );

pObj1->Release(  );
pObj2->Release(  );
pObj3->Release(  );
pTransContext->Release(  );

Get COM & .NET Component Services now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.