The following is a brief excerpt from Delphi 4 Unleashed. It is part of a much longer chapter. I include this much here so that everyone can get up to speed on MTS as quickly as possible. The complete chapter takes you through MTS programming from the beginning, and gives you all the background you need to get up and running.
The key to building an MTS example that spans multiple servers is the CreateTransactionEx function. Consider the following method:
procedure TPDXRocketMTS.CreateStandardRocket;
var
Transaction: ITransactionContextEx;
begin
Transaction := CreateTransactionContextEx;
OleCheck(Transaction.CreateInstance(CLASS_PDXGadgetObject,
IPDXGadgetObject, FGadgetObject));
OleCheck(Transaction.CreateInstance(CLASS_PDXWidgetMTS,
IPDXWidgetMTS, FWidgetObject));
try
DeleteGadget;
DeleteWidget;
RocketTable.Open;
RocketTable.Append;
RocketTable.FieldByName('Name').Value := 'Standard Rocket';
RocketTable.FieldByName('Type').Value := 1; RocketTable.Post;
RocketTable.Close;
Transaction.Commit;
except
Transaction.Abort;
raise;
end;
end;
This code shows how to create a single transaction that spans three different servers. To understand this method, you first have to remember that is taking place within a server. In other words, a client program calls this method in order to begin the transaction. The first server is the current one, the one from which the method is being called. The other two servers are called GadgetObject and WidgetObject. All three servers work together to create a single transaction in which a Widget and a Gadget are combined to create a Rocket.
The code shown here first calls CreateTranscationContextEx to create a transaction context. This context will control all the other transactions that occur. In some senses, this is a bit like calling StartTransaction. The method returns an instance of ITransactionContextEx. This interfaces has three methods called CreateInstance, Commit and Abort. I will describe all three of these methods over the next few paragraphs.
The next two lines of code create instances of the GadgetObject and the WidgetObject:
OleCheck(Transaction.CreateInstance(CLASS_PDXGadgetObject, IPDXGadgetObject, FGadgetObject)); OleCheck(Transaction.CreateInstance(CLASS_PDXWidgetMTS, IPDXWidgetMTS, FWidgetObject));
You call these lines of code in lieu of using a TDCOMConnection object. In a typical MIDAS or MTS program, you would first drop a TDCOMConnection object on your form, and then hook it up to the servers. In this case, you don't use the TDCOMConnection at all, and instead call the CreateInstance method of the ITransactionContextEx interface. CreateInstance, takes three parameters:
The next step is to call the methods on the Gadget and Widget servers:
procedure TPDXRocketMTS.DeleteGadget; begin FGadgetObject.DeleteStandardGadget; end; procedure TPDXRocketMTS.DeleteWidget; begin FWidgetObject.DeleteStandardWidget; end;
Here I call two methods off of the objects returned to me by TransactionContextEx.CreateInstance. If both of these methods succeed, then my transaction is complete, and I can call the Commit method of ITransactionContextEx:
Transaction.Commit;
However, it is possible that there might be either no Gadgets or no Widgets. In such a case, you would want to abort the transaction:
Transaction.Abort;
If you abort the transaction, then all your work will be rolled back. In other words, if you successfully Delete a gadget, and then find that you can't delete a widget, then calling Transaction.Abort will automatically roll back the deletion of your gadget.
The code in the Gadget and Widget servers is very simple. For instance, here is what the DeleteStandardGadget method looks like:
procedure TPDXGadgetObject.DeleteStandardGadget;
begin
try
GadgetTable.Open;
GadgetTable.Delete;
GadgetTable.Close;
except
SetAbort;
raise;
end;
end;
The really important line of code here is the one that raises an exception. Delphi will automatically propogate this back to the CreateStandardRocket method in the Rocket server, thereby causing the Transaction.Abort method to be called:
try DeleteGadget; DeleteWidget; ... // Code ommitted here Transaction.Commit; except Transaction.Abort; raise; end;
In other words, if neither the Gadget or Widget server raise an exception, then Transaction.Commit gets called, and if either one raises an exception, then Transaction.Abort gets called.
Here are the two most important facts about this example: