Enterprise Services with the .NET Framework: Transaction Services
Transaction services are usually the main reason why Enterprise Services is used. So that you do not have to deal with transactions programmatically, Enterprise Services offers a way to use transactions by using attributes.
This chapter opens with an overview of transactions, and then examines how you can task transactions programmatically. With this knowledge, you will see the advantages of transaction services offered by .NET Enterprise Services. The focus of this chapter then turns to how you can use Enterprise Services transactions, and how you can access the new features offered with Windows Server 2003.
Specifically, this chapter covers the following topics:
Transactions with services without components
Transaction isolation levels
So that you can fully understand the transaction support offered by Enterprise Services, it is important for you to have an overall understanding of transactions.
Let’s start with an example of a course database and attendees registered for courses. If an attendee who is registered for the Programming ADO.NET course wants to change to the Introduction to .NET course because he does not have the necessary prerequisites for attending the first course, he will be removed from the attendee list of the first course, and added to the attendee list of the second one. If one of these operations fails, the other should not happen either. Here a single transaction is needed. Transactions ensure that data-oriented resources are not permanently updated unless all operations complete successfully.
A transaction is defined by a unit of operations that either all succeed or all fail. If all operations complete successfully inside a transaction, the transaction is committed, and the updated data is written permanently. If one operation fails, however, a rollback is done; as a result, the data exists as it was before the transaction started.
Transactions can be defined by four basic properties, easily remembered with the acronym ACID (atomicity, consistency, isolation, durability).
Atomicity ensures that either all updates occur or none at all. Because of the atomicity guaranteed by transactions, you do not have to write code to handle the situation where one update was successful, and another was not.
With the course example just mentioned, if removing the attendee from the first course succeeds, but adding him to the second course fails, the transaction is aborted, and the attendee is not removed from the first course.
Consistency means that the result of a transaction leaves the system in a consistent state. Before the transaction was started, the data is in a valid state, as it is after the transaction is finished.
Consistency can be described with the same scenario as discussed with atomicity, to move an attendee from one course to the other. If removing the attendee from the first course fails, but adding him to the second course succeeds, the attendee would be registered for two courses at the same time. This is not a valid scenario; the database would not be consistent. The consistency property says that the transaction must leave the database in a consistent state; so if one part of the transaction fails, all other parts must be undone.
Multiple users can access the same database simultaneously. With isolation, it is assured that it is not possible outside of a transaction to see data that is being changed inside a transaction before the transaction is committed. It is not possible to access some in-between state that might never happen if the transaction is aborted.
If you do a query to get the list of attendees for all courses, while at the same time a transaction is running to move an attendee from one course to another, the attendee will not be listed on both courses. Isolation guarantees that an interim state of the transaction from outside of the transaction cannot be seen.
Durability means that a consistent state is guaranteed even in case of a system crash. If the database system crashes, it must be guaranteed that transactions that have been committed are really written to the database.
Enterprise Services supports distributed transactions, where a transaction can cross multiple database systems. These database systems can also be from different vendors.
With distributed transaction resource managers, a transaction manager and a two-phase commit protocol are needed.
Resource managers, as is obvious from the term itself, manage resources. Examples of resources are databases, message queues, or simple files. SQL Server is a resource manager, as is the Oracle database, and the Message Queue server. Resource managers must support the two-phase commit protocol.
The two-phase commit consists of a prepare phase and a commit phase. When updating data, a prepare phase occurs first, during which all participants in the transaction must agree to complete the operations successfully. If one of the participants aborts the transaction, a rollback occurs with all other participants. Agreeing to the prepare phase, the database must also guarantee that the transaction can commit after a system crash. If all participants of a single transaction had a successful prepare phase, the commit phase is started, during which all data is permanently stored in the databases.
A distributed transaction must be coordinated by a transaction manager. For Enterprise Services, the distributed transaction coordinator (DTC) is such a transaction manager. The DTC is a transaction manager that supports the X/Open XA Specification for Distributed Transaction, and the OLE Transactions protocol. The X/Open XA protocol is not used natively by the DTC; it must be converted to OLE Transactions by the ODBC driver or OLE DB provider. The X/Open XA Specification is not only supported by Microsoft SQL Server, but also by Oracle. The DTC is a Windows service that is part of the Windows operating system.
The distributed transaction coordinator manages transactions to multiple databases and connections from multiple clients. To coordinate work from different systems in a single transaction, the DTCs of different systems communicate with each other. Connections to resource managers that should participate in a distributed transaction must be enlisted with the DTC. To enlist database connections, the ADO.NET data providers for SQL Server and OLE DB are aware of the DTC, and the connections are enlisted automatically if transactional attributes are used with serviced components.1
Figure 7-1 shows a single transaction that is running across multiple systems. Here the transaction is started from component A that is running on server A. Component A has a connection to the database on server C. The connection is enlisted with the DTC on the local system (in this case, the DTC of server A). Component A invokes a method in component B that itself requires a transaction. Component B has a connection to the database on server D. The database connection is enlisted in the DTC of server B. On the database systems C and D, we also have DTCs running that manage the transactional interaction with the resource managers of the dependent systems. Because all four DTCs coordinate the transaction, anyone participating in the transaction can abort it. For example, if the resource manager of server D cannot complete the prepare phase successfully, it will abort the transaction. The DTC of server D will tell all other DTCs participating in the transaction to do a rollback with the transaction. On the other hand, if all members of the transaction are happy with the prepare phase, the DTCs coordinate to commit the transaction.
Figure 7-1 Transactions with the distributed transaction coordinator.