ne of the best features of developing COM+ based applications in standard three-tier presentation, business, and data access (DAL) layers is the simplicity of managing transactions distributed between the application's layers. I'm not referring to the canonical definition of "distributed transactions"that is "distributed between different databases," but to "distributed between a single application's different logical layers and methods."
Developing a transactional business process (such as inserting a new order, updating a user's data, activating a new account, etc.) in the COM+ environment is disarmingly linear. It requires:
- A method in the business layer that implicitly begins a transaction.
- Calling all the DAL or business layer's methods required to define the operation's flow.
- Committing or rolling back the transaction initiated by the starting method.
The intermediate methods involved in the transaction (in general all DAL methods) are limited to calling DisableCommit
on the execution's context, while only the starting method (the transaction's manager) may call a commit, if all methods involved in the transaction succeed or an abort if logical or runtime errors occur.
Unfortunately, you'll probably recall this clear and simple-to-implement three-part procedure with nostalgia when you switch to .NET. Yes, you can use the .NET Enterprise Services for transactions, but I think that using Enterprise Services adds complications that are worse than the benefits, so I prefer to use only ADO.NET resources.
ADO.NET does provide a good resource with its Transaction object, but by itself that doesn't permit you to implement cross-layer transactions in a simple fashion. That's not because it lacks functionality (if you try, you can approach the functionality of the steps described above), but because to achieve the goal you must create (and duplicate) similar code in every method involved in the transactionin fact, in every business process layer and DAL method), which is a boring, expensive, and ultimately unaffordable solution.
To circumvent such problems this article describes a small transactional-support framework that you can use to write .NET code using the same three-step process described above that you formerly used in the COM+ environmentbut without using Enterprise Services. The resulting applications require minimal code to manage transactions distributed between layers, and let you reuse methods to define different operations and flows that would otherwise prove expensive to maintain.
This framework is based on the idea that it's possible to create a "DistributedTransaction" .NET object that you can use in the same manner as in classic COM+. This DistributedTransaction object is a wrapper built around the ADO.NET Transaction object, which exposes the fundamental functionality. But the new object results in simpler access to data, and makes distributed transactions available across all modules in a three-tier application.
The only formal difference when implementing solutions based on this framework as compared with COM+, is that now the business layer's method must always explicitly instantiate the transaction, and you must pass the DistributedTransaction object through calls that clearly are not executed inside a uniform context managed by an external environment, as in COM+. Otherwise, it's just as simple as using COM+; DAL methods work autonomously, and they can optionally call DisableCommit
when a problem occurs. The business layer's methods may call Commit
if all the operations succeed, or SetAbort
, if logical or runtime errors occurred during flow's execution.
A Typical COM+ Application
Before analyzing the framework, here's a simple typical COM+ application, written in VB6, that defines a distributed transaction.
The DAL's method has its MTSTransactionMode
property set to UsesTransaction
, depending on its logical characteristics, and it doesn't contain explicit code to manage transactions. To add error handling in the method, you can call GetObjectContext.DisableCommit
; but if you're satisfied with having runtime errors handled by the caller, and not interested in registering the method's internal status (this is generally acceptable), you can simply ignore error-handling, and ignore any distributed transaction problems altogether.
The following business layer method initiates and completes the transaction. Its MTSTransactionMode
property would be set to either RequiresNewTransaction
. The implementation inserts two code elements to manage the distributed transaction: a call to GetObjectContext.SetComplete
that executes to commit the transaction, and a general error handler, that calls GetObjectContext.SetAbort
to rollback the transaction if errors occur.
Public Function DoSomething(
ByVal inputData As Object) As Integer
On Error GoTo ErrorHandler
'' [...] Executes business operations
'' - Checks for business rules
If rulesValidationSuccess = False Then
'' -- sets an error code to return
DoSomething = -20
' -- rollback transaction
' - Commits transaction
' [...] Releases objects and/or peforms non-transactional
' operations (eg. sends an emails)
' -- manages global errors
' -- rolls back transaction
'[...] Releases resources, logs error, etc.
With just these few lines of code you can define all the business flows you need, using similar business layer or DAL methods. This structure is a lot simpler than the equivalent version made with standard .NET and ADO.NET base objects.
The framework consists of an object that implements its logic and a pattern you can use to write business and data layer methods that exploit its benefits. But before defining that object, remember that a COM+ transaction's state is defined by two bits, called happy
. The happy
state represents a transaction that has not yet been committedeven if a method involved in the transaction has called DisableCommit
, but no rollback has yet been executed. The done
state represents a transaction that has been closed (either committed or rolled back), and is therefore no longer usable. Indeed, committing or rolling back a transaction releases all
resources on the underlying database, and ends its lifecycle. In other words, if you still need to call other commands, you need to create and work with a new transaction context. With that in mind, here's how to create the DistributedTransaction wrapper object.