This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|Working with MTS, ASP, and Visual Basic |
|Ken Spencer |
Code for this article: Serving0400.exe (34KB)
| here are lots of potential roadblocks to deal with when creating an application. The more pieces you need to put together, the truer this becomes. When building a high-performance Web application, you usually put together many technologies and components, such as ASP, Visual BasicÂ®, COM, and MicrosoftÂ® Transaction Services (MTS), plus a database and even XML. Using these technologies together can lead to many complex questions.|
This month, Iï¿½m going to dive into the programming side of MTS so you can better understand what can and canï¿½t be done in your Web applications. This column is built from some recent experiences I have had with MTS and the research that those experiences led me to conduct.
Back to Basics When youï¿½re using MTS with Visual Basic, the first thing you usually do with a component that youï¿½ve written is compile it and drop it into an MTS package. You can put the component into the MTS package by dropping it into the Packages Components folder using WindowsÂ® Explorer. Then you can open the properties for the component and set its transaction type. This has been covered in lots of articles, so I wonï¿½t belabor the point. Basically, you just drop the component in and it runs in MTS. Pretty simple, so far.
Since most of you are calling MTS components from ASP scripts, you may be using MTS features in your ASP code. You can make your ASP script transactional and have it participate in the transactions with the COM objects it calls, or you can do all the database work in the COM objects and let your ASP script work as it normally does without worrying about transactions. ASP works best with transactions if MTS and Internet Information Services are on the same box. Each transaction requires two calls to the MTS box, and this doesnï¿½t scale well.
In order to make an ASP script transactional, add the @TRANSACTION directive to the top of the page. When you declare a page transactional, any script commands and objects used on the page are run under the same transaction context. MTS handles the details of creating the transaction and determining whether the transaction succeeds (commits) or fails (aborts). An ASP script transaction cannot span multiple ASP pages. If a transaction requires objects from several transactional components, you should group operations that use those objects into one ASP page.
There are two MTS methods you can call in your ASP script: SetComplete and Abort. SetComplete declares that the script is not aware of any reason for the transaction not to complete. If all components participating in the transaction also call SetComplete, the transaction will complete. The SetAbort method declares that the transaction initiated by the script has not completed or has had a failure and should not be committed. Transactions complete automatically when the script in a page finishes its execution. If SetAbort is executed in a page, this will terminate the transaction and cause it to fail. The ASP application does not need to call either of these statements. If it doesnï¿½t, then the transaction will either complete successfully or will abort when the entire page finishes processing. Calling either statement can, of course, change this outcome.
Your ASP script can also respond to the firing of two MTS events: OnTransactionCommit and OnTransactionAbort. The former fires when the transaction is committed, while the latter fires when the transaction aborts.
Now, what happens when you call your MTS COM component from the ASP page? Well, the MTS component may execute in the same transaction as the ASP page, or it may not. Howï¿½s that? If you have the transaction support property of the component set to "Requires a transaction" or "Supports transactions," then the component will run in the same transaction context as the ASP page. If you have the transaction support property set to "Requires new transaction," then the component will run in its own transaction. In short, use care when setting the transaction support property to make sure the component does what you want.
You can do several things to your component to make it work more effectively with MTS. First, you should create a project reference in your Visual Basic project to the Microsoft Transaction Services Type Library. Then, you can begin to modify your code. Next, open each class module that will have MTS support. You can set the MTSTransactionMode property of the class to the type of transaction support you require. This puts the property directly in the compiled component and locks it in. If you move the component to a different server, this property will always retain this setting. This step is highly recommended.
Next, you can begin to add support for MTS directly to the class. You really need three things. First, when your code is going to do any work and return (as in a function call that updates a database), you should let MTS know what is going on. You do this by gaining access to the MTS context object:
Next, you can let MTS know that your code has completed and that things are OK:
If your code detects any type of problem, you can also let MTS know that it should abort the transaction:
Most discussions of MTS stop here. But letï¿½s explore what the two Setxxx methods are really doing. Neither of these statements causes MTS to perform an action. Instead, they simply mark the transaction as either successful or unsuccessful to that point. The transaction does not really complete until the root component (the component that started the transaction) completes.
Dim oContext As ObjectContext
set oContext = GetObjectContext()
MTS handles the problem of object instantiation with two features called just-in-time (JIT) activation and as soon as possible (ASAP) deactivation. JIT activation means that objects that are created as required by the client load at any given time. When the object calls SetComplete, the server knows that the object doesnï¿½t need to hold on to any state data and will deactivate the object ASAP. The server can then reclaim any resources the object held.
In this way, the client can get a reference to the resource and hold it as long as possible while conserving server resources. When the client references any method or resource in the object again, MTS recreates the object. The newly instantiated object is different from the one the client last used, but MTS makes sure the clientï¿½s reference still works so that the client does not care that the object is different.
You can call DisableCommit to suspend the committing of a transaction. This allows you to verify that a transaction isnï¿½t committed between method calls to an object. Any attempt to commit the object before EnableCommit or SetComplete is called will abort the transaction. EnableCommit notifies MTS that while the work the method is doing is not yet complete, all work related to the currently executing transaction can be committed. This allows MTS to commit the transaction at the proper time.
I still havenï¿½t touched on the most interesting MTS method. Letï¿½s discuss what happens when you create an instance of an object that is used by MTS. When a client application like an ASP script calls a method in an MTS object, it never directly receives an interface to that MTS object. The MTS Executive wraps every object, and the client gets an interface to the context wrapper. Once the method call has completed, the wrapper will release all interfaces to the object and it will destroy the object itself. The client never knows that it now has an interface to a deactivated object. The next time the client invokes a method on the object, the wrapper will create a new instance of the component to fulfill the request. This is how the JIT features work.
This brings up a discussion I had at TechEd last year. One of the COM+ program managers was discussing how COM+ and MTS handled objects. He mentioned that because MTS was layered onto Windows NTÂ® 4.0, MTS objects were sort of second-class COM citizens. This is why you need to use a method called CreateInstance to instantiate COM objects within an MTS object, instead of using CreateObject. CreateInstance allows the new object to inherit the current context object.
If the newly created object is an MTS object executing within a transaction, any object that is created from the componentï¿½s context object will be enlisted in the current transaction automatically, under two conditions: the object to be created must be marked as either "Supports transactions" or "Requires a transaction."
So, when you create an MTS object in your ASP script, you can use CreateObject. But when that object creates another object you should use CreateInstance.
This works just like CreateObject, except Book is now running in the same context as the object that initiated it. Note that the same program manager pointed out that CreateInstance will work in COM+, but it is no longer necessary.
Set oBook = Context.CreateInstance("Pubs.Book")
Another issue will come up when you try to pass around a pointer to an MTS object. There are often times when a client will require a reference to an object created from a secondary call within a method. Because MTS stores context information about each instance of an active object, it must be informed that a reference is being returned to a client.
When a component wants to pass a self-reference to a client or to another object, it should always call SafeRef first and then pass the reference returned by this call. SafeRef is used by an object to obtain a reference to itself that can be passed safely outside its context. The syntax looks like this:
Using SafeRef ensures that the object will pass through the MTS runtime environment. This allows MTS to handle cases where you pass the interface outside the current MTS context wrapper. This is also unnecessary in COM+. This method of passing objects is particularly useful for callbacks, allowing the object to be executed independently of any particular client reference.
Set safeobject = SafeRef(Me)
Working with MTS Issues Knowledge Base article Q234218 (http://support.microsoft.com/support/kb/articles/Q234/2/18.asp) says that you shouldnï¿½t use ActiveXÂ® Data Objects (ADO) as shown in the DoStuffBad method in Figure 1. Instead of just taking the word of the Knowledge Base article, I tested the code shown in Figure 1. This code has been adjusted to use a correct connection string; the Insert statement points to a real database table in the Pubs database.
According to the Knowledge Base article, the problem with the code is that it starts out using the Connection object with a recordset. The same code then goes on to reuse the Connection object for another task. That is, after all, one of the beauties of ADO in the first place. According to the article, this technique does not work reliably in MTS and should be avoided. The article makes these specific recommendations for using ADO Connection objects with multiple actions:
According to the article, the code in DoStuffBad should blow up with the error code Unspecified Error [-2147467259 / 80004005]. I ran the code in DoStuffBad several times and it never failed. So chalk one up for ADO and MTS and zero for the Knowledge Base article. (To be fair, the article says you might get this message from the code shown.) So, be careful when reusing Connection and Command objects inside of MTS objectsâ"if you donï¿½t set the Recordset object to Nothing, the server-side cursor may still be tied to it, depending on the underlying OLE DB provider.
- All open recordsets should be either disconnected or closed, and set to the value Nothing before executing other operations.
- All Command objects that are not used should be set to Nothing.
When you use a SQL statement in your application, Microsoft SQL Serverâ¢ may generate a temporary stored procedure for it and store it in the tempdb database. An interesting Knowledge Base article, Q197809 (http://support.microsoft.com/support/kb/articles/Q197/8/09.asp), discusses the generation of temporary stored procedures for prepared SQL statements and MTS. However, the article only applies to versions before SQL Server 7.0.
The generation of temporary stored procedures is controlled by one of two methods. You can turn it on or off (it is on by default) using the ODBC Driver Manager in Control Panel. Just open the ODBC Driver Manager and uncheck the checkbox on the configuration page for the connection. You can also turn this off by adding "UseProcForPrepare=No" to your DSN-less connection string.
The problem with temporary stored procedures is really in tempdb. Since MTS handles connection pooling for the database, temporary stored procedures may not be cleaned up for a long time because a connection may not go away immediately. This can cause tempdb to fill up, resulting in all types of problems. Tempdb can also have locking problems when SQL Server tries to create the temporary stored procedure.
Itï¿½s best to build your application with stored procedures and not let SQL Server create temporary stored procedures anyway. SQL Server 7.0 apparently does not have this problem, since it creates temporary stored procedures internally instead of using tempdb (unless you force it to for some reason).
MTS and State-aware Components Another issue comes up when designing components for MTS. You should always design both the methods in a class and the class itself as stateless. This means that the class does not maintain any state information between executions of its methods. Usually this involves simply passing arguments to a method and returning a return value from the method when it completes, making sure there is no data left in the object that the application may need when it calls another method in the object. This allows MTS to discard the object when SetComplete is called, then reinstantiate it later without any leftover data.
This discussion sounds trivial, but it may be more complex in your particular application. It makes sense for you to take a look at old components as they are migrated into MTS. If you are designing new components, make them stateless from the start. In fact, when you are designing components for the Web, it makes sense to make each method stateless and as lightly coupled as possible (just like the Web itself is). This makes reusing the methods in many places much easier.
Conclusion Thereï¿½s lots more ground to cover regarding MTS, Visual Basic, ASP, and the Web. Believe it or not, this column just scratches the surface. I did not even touch on the Activate and Deactivate methods in the ObjectControl class that you can implement. These methods allow you to run code when the object is instantiated or shuts down. I also did not discuss real object hierarchies and activating objects across packages.
However, I hope I was able to illustrate some useful tips for using MTS and Visual Basic with your ASP applications. These tools and technologies go together like bread and butter, but it is important to understand all your options. If you donï¿½t, it will be like trying to eat the middle part of the bread without eating the crust first. It can be done, but itï¿½s not pretty!
Ken Spencer works for the 32X Tech Corporation (http://www.32X.com), which produces a line of high-quality developer courseware. Ken also spends much of his time consulting or teaching private courses.
From the April 2000 issue of MSDN Magazine.