Automatic Transactions

Transactions are one of the best-known features of COM+. In fact, Microsoft Transaction Server (MTS), the predecessor to COM+, was even named after them. That said, a surprising amount of confusion surrounds exactly what a COM+ transaction is and what it is supposed to accomplish. This confusion leads many developers to implement COM+ transactions in places where they only slow performance.

A COM+ transaction isn't a replacement for client-initiated ADO.NET transactions or stored procedure transactions. These types of transactions are handled directly by the data source, which allows them to complete much more quickly. In the case of a transaction encapsulated by a stored procedure, the entire process can take place on the server without requiring any additional network calls. This provides excellent performance.

With a COM+ transaction, the COM+ runtime is in charge, along with the Distributed Transaction Coordinator (DTC) built into Windows. The first consequence of this fact is that if you want to use a COM+ transaction, the data source (or resource manager) must be able to work with COM+. Technically, it must support a protocol known as OLE Transactions. Quite a few data sources meet these requirements, including SQL Server (from version 6.5 later), Oracle 8i and 9i, MSMQ, and IBM DB2, just to name a few.

Using the Component Services utility, you can disable the DTC service, monitor a transaction underway, or study transaction statistics (as shown in Figure 9-12).

Figure 9-12. The DTC service

graphics/f09dp12.jpg

Although one DTC coordinates every COM+ transaction, multiple DTCs might be involved in the transaction. If you have data sources on different machines, the DTC service on multiple servers participates in managing the transaction, as shown in Figure 9-13.

Figure 9-13. Multiple DTCs in a transaction

graphics/f09dp13.jpg

COM+ uses a two-stage commit process. In the first stage, it asks all the involved resource managers, "If I asked you to commit the changes, could you?" If all the resource managers can apply the change, it uses the second stage to tell all the resource managers to go ahead and commit changes. If any one resource manager responds that it can't commit the changes, COM+ and the DTC instruct all the resource managers to roll back the changes.

Therefore, two of the key characteristics of COM+ transactions are as follows:

  • They are slower than database transactions. They also require more network traffic because of the interaction required between COM+ and the data source.

  • They require support for resource managers. Most database products will work, but a transaction that involves actions that modify the file system will not be reversible.

Of course, COM+ services provide some advantages:

  • They allow transactions to span multiple data sources and multiple servers. These are known as distributed transactions.

  • They allow transactions to be completed automatically. The component does not need to have any knowledge of the underlying data source and how it manages transactions.

  • They give the programmer the freedom to tie together different processes into a single transaction. The components don't require any knowledge of the scope of the transaction.

The first point represents the most common reason that COM+ transactions are used. If you need this ability or you need to tie together a set of processes in a number of varying processes, COM+ transactions just might be the ticket.

Objects and Transactions

Under COM+, transactions are not started and committed explicitly. Instead, you use attributes to identify transactional objects. A transaction is automatically created when a method is executed in a transactional object. If the object encounters an error, the transaction is rolled back and any changes made to a compatible resource manager are canceled.

COM+ transactions can be conceptually divided into two types: those that take place inside a single object (and span a single method call) and those that span multiple objects and multiple method calls. You specify the way that your object can be used in a transaction by adding the <Transaction> attribute to the class definition of the serviced component. This attribute generally indicates that your class requires a transaction or will optionally run inside the caller's transaction context, depending on the value you use from the Transaction­Option enumeration. Table 9-1 lists the TransactionOption values.

Table 9-1. Values from the TransactionOption Enumeration

Value

Description

Required

This object must run in a transaction. If the caller has already started a transaction, this object participates inside that transaction. Otherwise, a new transaction is created.

RequiresNew

This object must run in a transaction. If the caller has already started a transaction, a new transaction is created for this object. This isn't a nested transaction but a completely independent transaction. Therefore, you must be careful when using this setting to make sure that it can't violate the consistency of the system if it commits but the caller's transaction is rolled back.

Supported

This object can run in a transaction and is enlisted in the caller's transaction if it exists. Otherwise, it does not run in a transaction.

NotSupported

This object does not participate in a transaction. If the caller has a current transaction, this object can't vote on it. However, this object could generate an exception, which, if not handled by the caller, will end up rolling back the transaction.

Disabled

The object doesn't have any transaction requirements or contact a database. This is the default value. It is similar to NotSupported, but the object might execute faster because it might be activated in the caller's context, saving the overhead of cross-context calls.

If you're using COM+ to allow one object to perform a distributed transaction that updates multiple data sources, you will probably use TransactionOption.Required. Listing 9-12 shows an example.

Listing 9-12 A simple transactional class
 <Transaction(TransactionOption.Required)> _ Public Class TransactionTest     Inherits ServicedComponent     Public Sub CommitChanges()         Try             ' (Perform changes.)             ' No errors were encountered.             ' Vote to commit the transaction. 
             ContextUtil.MyTransactionVote = TransactionVote.Commit         Catch Err As Exception             ' An error was detected. Vote to abort the transaction.             ContextUtil.MyTransactionVote = TransactionVote.Abort         Finally             ' The object must be deactivated before COM+ will perform             ' the final commit or abort.             ContextUtil.DeactivateOnReturn = True         End Try     End Sub End Class 

Consider, for example, what will happen if a client uses the transactional component in Listing 9-12 as follows:

 Dim DBComponent As New TransactionTest() DBComponent.CommitChanges() 

When the CommitChanges method is invoked, several steps will happen:

  1. COM+ checks whether a transaction is in progress in the caller's context. Because one is not, it starts a new transaction.

  2. COM+ intercepts all the database operations and performs them in the scope of the new transaction (provided the data source supports OLE Transactions).

  3. If an error isn't encountered, the component votes to commit the transaction.

  4. The component sets itself to deactivate.

  5. When the component deactivates, COM+ checks to see whether the component voted to commit. If it has, COM+ attempts to apply the transaction using its two-stage commit process.

Listing 9-12 uses an explicit syntax to help make it clear exactly what is taking place. However, you can use the <AutoComplete> attribute to save a considerable amount of code. When you apply <AutoComplete> to a method, the component deactivates immediately when the method ends. Furthermore, the transaction is automatically committed, assuming no unhandled exceptions are encountered. If an unhandled exception is generated, the transaction is rolled back.

Listing 9-13 shows the equivalent code with <AutoComplete>.

Listing 9-13 A simple transactional class with AutoComplete
 <Transaction(TransactionOption.Required)> _ Public Class TransactionTest     Inherits ServicedComponent     <AutoComplete>     Public Sub CommitChanges()         ' (Perform changes.)         ' If any errors are encountered, the transaction will be         ' rolled back.         ' Otherwise, it will be committed when this method ends.     End Sub End Class 

Even when you use <AutoComplete>, you can still vote explicitly. In fact, you might need to. If you call another component that returns an error code instead of throwing an exception, for instance, you need to signal to COM+ that a problem has been encountered and the transaction should not continue.

Remember that the reason the CommitChanges method is implemented as a COM+ transaction is because it modifies more than one data source. If it modified only one data source, it could use an ADO.NET client-initiated transaction. If it performed all its work in a single stored procedure, it could use an even more efficient stored procedure transaction.

The next section examines how more than one transactional component can work together.

Note

You must be careful when mixing transactional and nontransactional components. If a transactional component calls a method in a nontransactional component and that method commits a change to a data source, that change is applied immediately. That means it won't be undone if the caller's transaction fails and is rolled back.


Rolling a Custom Transaction

If you're using transactions so a client has the option of tying several object methods together in a single atomic unit, you will probably use Transaction­Option.Supported, which is more flexible than TransactionOption.Required. For example, a service provider object might use TransactionOption.Supported to mark a method such as AddCustomer. This allows the client to call AddCustomer on its own, without incurring the overhead of a transaction. However, it also allows the client to bind together several methods in one transaction (such as AddCustomer and AddOrder), ensuring that they fail or succeed as a unit.

The simplest case occurs when one transactional component calls another TransactionOption.Supported or TransactionOption.Required component. In either case, the second component is enlisted in the first component's transaction. If either component votes to abort the transaction, COM+ rolls back all the operations made by both components.

Another possibility is that a nontransactional client calls several transactional components. By default, each transactional component is placed in a distinct transaction that is committed or aborted separately. To overcome this problem, you need to make use of an unmanaged DLL called comsvcs.dll. You can add a reference to this COM object easily in a .NET project (as shown in Figure 9-14). No RCW interop class needs to be generated because one is already provided with the .NET Framework. (Look for the file named Interop.ComsvcsLib.dll.)

Figure 9-14. Adding a reference to comsvcs.dll

graphics/f09dp14.jpg

Next, you import the COM+ services namespace:

 Imports COMSVCSLib 

Finally, with a little more work and the help of the TransactionContext class, you can create a new transaction and place multiple objects inside it, as shown in Listing 9-14.

Listing 9-14 Hosting a transaction with a nonserviced client
  ' Create the transaction. Dim Tran As New TransactionContext() ' Define the objects. Dim ObjA As TransactionalClassA Dim ObjB As TransactionalClassB ' Create the objects inside the transaction context. ObjA = Tran.CreateInstance("TestComponent.TransactionalClassA") ObjB = Tran.CreateInstance("TestComponent.TransactionalClassB") Try     ' Attempt to use the two classes in this transaction     ObjA.DoSomething()     ObjB.DoSomething()     ' No errors were encountered.     ' Commit the transaction.     Tran.Commit() Catch err As Exception     ' Roll back the transaction.     Tran.Abort() End Try 

Note

Chapter 10 introduces another way to look at combining objects in a transaction: with the facade pattern. This approach is less flexible (it won't support multiple data sources at once), but it performs better and doesn't require COM+ serviced components.


Isolation Levels

By default, COM+ transactions use an isolation level of Serializable. This is the best choice for data integrity, but it can also hamper performance because it requires stricter locks at the data source level. If your component is running under COM+ 1.5, it can take advantage of a configurable transaction isolation level:

 <Transaction(TransactionOption.Required, _  Isolation := TransactionIsolationLevel.Serializable> _ Public Class TransactionalClass 

Note that no constructor for the <Transaction> attribute accepts a TransactionIsolationLevel value, so you need to specify the property by name. You can set one of five values for the Isolation property, as described in Table 9-2. The list is ordered from least to greatest protection.

Table 9-2. Values from the TransactionIsolationLevel Enumeration

Value

Description

Any

The calling component's isolation level is used. If this is the root component, the isolation level used is Serializable.

ReadUncommitted

The transaction can read uncommitted updates left by another transaction (even though these changes might be rolled back).

ReadCommitted

The transaction will not read uncommitted data from another transaction, but another user can change the data that this transaction is using. This is the default isolation level that a SQL Server transaction uses.

RepeatableRead

Locks are placed on any data this transaction reads, ensuring that other users can't modify it. However, a row can be inserted that changes the results of your transaction. This is a subtle issue know as phantom inserts.

Serialized

Prevents updating or inserting until the transaction is complete. Therefore, no changes can be made that could change the outcome of your transaction while it is in progress.

Be careful when changing the TransactionIsolationLevel. The root component of a transaction can't call another component that requires a higher level of protection. (For example, a root ReadCommitted object can't enlist a RepeatableRead object in the same transaction.)

Also keep in mind that these transaction isolation levels are not absolute because they are not really in the control of COM+. Rather, they must be respected by the resource managers taking part in the transaction.

Web Method Transactions

Web methods have built-in support for COM+ transactions. However, this support comes with a significant limitation. Because of the stateless nature of the HTTP protocol, an XML Web service method can participate only as the root of a transaction. That means you can't enlist more than one Web method into a single transaction. You can create other transactional components, however, and have them take part in a Web method transaction.

To enable transaction support for a Web method, you use the TransactionOption property of the WebMethod attribute (as shown in Listing 9-15). Although this property accepts all the standard TransactionOption values, they don't have the expected meanings. For example, Disabled, NotSupported, and Supported all have the same effect: They disable transaction support. Similarly, Required and RequiresNew both enable transaction support and start a new transaction. I recommend that you use RequiresNew in your Web methods because this most clearly matches the actual behavior.

Inside your transactional Web method, you can perform a variety of tasks, such as updating one or more data sources. You also can create serviced components that will run inside this transaction (provided they are marked with TransactionOption.Required or TransactionOption.Supported).

Listing 9-15 A transactional Web method
 <WebMethod(TransactionOption := TransactionOption.RequiresNew)> _ Public Sub DoSomething()     ' (Create other serviced components if desired.)     ' (Attempt some tasks.)     ' This transaction will be committed when the method ends,      ' assuming no unhandled exceptions were encountered. End Sub 

Web methods behave as though you have applied the <AutoComplete> attribute. The Web method transaction is committed when the Web method completes, provided no exceptions are encountered. If an unhandled exception is thrown, the transaction is rolled back. The case study in Chapter 18 presents a good example of how a COM+ transaction can be used in a Web service.



Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net