Transaction Context

Up to now, we’ve let MTS decide whether or not the transaction was successful. For more sophisticated applications, we really need to make that decision ourselves. How do we do this? The answer is all bound up with the transaction context. The transaction context is a COM object that maintains the continuity of the transaction across the many possible components of the transaction. The interface IObjectContext is used to control that context, of which more anon. Let’s enhance our SimpleNegotiator object to take advantage of IObjectContext.

We’ll start off by cloning our project into a new one called TransNegotiator. The first thing that we have to do is include another header file into our Negotiator.cpp file: mtx.h. Next, we need to make a few changes to the DealWithKidnappers method:

STDMETHODIMP CNegotiator::DealWithKidnappers(int amount, VARIANT_BOOL* pbFree)
{
   GetObjectContext(&m_spObjectContext);

   HRESULT hResult = TakeCashFromAccount(amount, pbFree);

   if (FAILED(hResult))
   {
      m_spObjectContext->SetAbort();
      return hResult;
   }

   if (*pbFree == VARIANT_TRUE)
   {
      hResult = ReleaseHostage(amount, pbFree);

      if (FAILED(hResult))
      {
         m_spObjectContext->SetAbort();
         return hResult;
      }
   }

   if (*pbFree == VARIANT_TRUE)
      m_spObjectContext->SetComplete();
   else
      m_spObjectContext->SetAbort();

   return S_OK;
}

We start off by using the helper function GetObjectContext() to get hold of the object context interface. We pass in an IObjectContext pointer we add to Negotiator.h:

private:
   ...
   CComBSTR m_bstrHostage;
   CComBSTR m_bstrAccount;

   CComPtr<IObjectContext> m_spObjectContext;

Based on the result of our data manipulations, pbFree, we decide whether or not to commit the transaction, using those SetCommit() and SetAbort() methods. If we don’t do this, MTS will make the decision for us, depending on whether or not the method runs to completion.

What is the result of all this? If we register this version with MTS, make sure the Negotiator object is set to Requires a Transaction and run the negotiator client, we can see that the component is now genuinely transactional. If the hostage is not freed (for whatever reason), and IObjectContext::SetAbort() gets called, no changes are made to either database. If, however, IObjectContext::SetComplete() is called, both changes go through.

If Requires a transaction or Requires a new transaction is not set in MTS, however, it will work in exactly the same way as the previous SimpleManager example — IObjectContext is not automatically associated with a transaction.

But this seems too easy, and we've not really made use of the IObjectContext interface's methods. By the way, these are:

Method Description
SetComplete() Specifies that this object's transaction may complete and the object be deactivated, other things being equal.
SetAbort() Abort the transaction, when the transaction manager asks, and deactivate the MTS object.
DisableCommit() Your MTS object may maintain state while it's working; for instance, if it calls another object and waits for that to return, or if the client makes several calls during a transaction. The transaction mustn't commit in the meantime.

However, MTS may reuse an MTS object for a different client — by default it assumes that the object carries no state between method calls. DisableCommit() guarantees that the client will get the same MTS object during a transaction.

EnableCommit() Re-enables the MTS object to commit if asked.
CreateInstance() If your MTS object creates another MTS object as part of its work within a transaction, you don't want to lose that transaction's context information. If the second object fails, the calling object must know about it so that it rollback its changes.
IsCallerInRole() Allows your MTS objects to check that their client has the right security clearance to continue. We’ll be looking at MTS security in Chapter 9.

In the next section, we’ll take a look at how we can share context between two objects. But before that, there’s one more aspect of transaction context that I need to cover.

So far in this section, we have been talking about automatic transactions. In these transactions, it’s MTS that decides when to initiate a transaction, based on the settings for the objects involved (Requires a new transaction etc.) It’s also MTS that determines when the transaction is to be aborted or completed, based on our instructions to it via the object context (SetComplete() or SetAbort()). It’s also possible in MTS to have manual transactions. With a manual transaction, the transaction comes under control of the client. How does this work?

In order to create our own transactions, we instantiate a TransactionContextEx object. This has the ITransactionContextEx interface, with the following methods:

Method Description
Abort() Aborts the current transaction.
Commit() Commits the current transaction.
CreateInstance() Create another object within this transaction context.

Let’s imagine that our transaction involves a number of objects, each of which may commit or abort the transaction. We create all of our objects using the CreateInstance() method described here. Each of these objects can now get hold of their individual IObjectContext interfaces. (Notice the distinction between the two contexts: transaction context relates to the whole transaction, whereas object context just relates to a single object. Of course, that object might itself create instances of other objects within its own context.)

Each object then does all its work, and determines whether to invoke SetComplete() or SetAbort(). This, then determines whether or not the individual part of the transaction relating to that object is successful or not. Back at the top level, when we invoke our Commit() method on the transaction context, its success or otherwise depends on whether or not each of the individual components have committed or aborted. We won’t be looking at manual transactions any more here, but you can quite easily adapt the examples here to use them instead of automatic ones. All you’ll need to do is something like this in our Visual Basic application.

First of all, replace the line:

Set neg = New Negotiator

with,

Dim ctx as TransactionContext
Set ctx = CreateObject ("TxCtx.TransactionContext")

Set neg = ctx.CreateObject("Negotiator")

and then add this line at the end:

ctx.Commit

OK, let’s get back to our object context.

© 1998 by Wrox Press. All rights reserved.