Understanding and Using Enterprise Services

   


Although you know how to create and consume a serviced component, there's not much point to this unless you use some of the automatic services provided by COM+. In this section, you will learn how some of the key COM+ component services work and how to use them in an application. In particular, I'll discuss the following COM+ services:

  • Object Pooling

  • Just-In-Time Activation

  • Object Construction

  • Automatic Transaction Management

  • Queued Components

Object Pooling

When you request to create an object on the server, the server creates a process space, instantiates the object, and performs necessary initialization. For some large and complex objects, the amount of resources consumed in creating them might be significantly high. An application might perform poorly if expensive-to-create objects are frequently created.

Wouldn't it be nice if you could maintain a pool of already created objects and then efficiently reuse them repeatedly without creating one from scratch? That's what the object pooling service of COM+ does. The object pooling service allows you to increase the scalability and performance of an application by minimizing the time and resources required in creating objects repeatedly.

Configuring a Serviced Component to Use the Object Pooling Service

You can configure a class to use the object pooling service by applying the ObjectPooling attribute on the class. Table 7.9 lists various properties of the ObjectPooling attribute that you can use to configure the way object pooling works for the class.

Table 7.9. Properties of the ObjectPooling Attribute

Property

Description

CreationTimeout

Specifies a length of time (in milliseconds ) to wait for an object to become available in the pool before throwing an exception.

Enabled

Specifies whether object pooling is enabled for a component; the default value for this property is True .

MaxPoolSize

Specifies the maximum number of pooled objects that should be created for a component.

MinPoolSize

Specifies the minimum number of objects that should be available to a component at all times.

In addition to using the ObjectPooling attribute, an object-pooled class also overrides the CanBePooled() method of the ServicedComponent class. The overridden version of this method should return either True or False . You'll see in the following section that an object is only pooled if the CanBePooled() method returns True .

How Object Pooling Works

At a conceptual level, you can envision that the COM+ places a Pooling Manager between the client and the server, as shown in Figure 7.20.

Figure 7.20. The Pooling Manager intercepts any requests for object creation and provides the object pooling service.

The Pooling Manager is responsible for maintaining and controlling the object pool. All client requests to the server for an object are intercepted and instead processed by the Pooling Manager. The Pooling Manager follows an internal logic as shown in Figure 7.21.

Figure 7.21. The Pooling Manager maintains the object pool based on the specified attributes.

The functionality of the Pooling Manager can be summarized in the following list:

  • When the COM+ application is started, a MinPoolSize number of objects is created and thereafter maintained in the pool at all times when the application is running.

  • Each time that the pooling manager receives a request to create an object, the Pooling Manager checks to see whether the object is available in the pool. If the object is available, the Pooling Manager provides an already created object from the pool.

  • If no objects are currently available in the pool, Pooling Manager checks to see if the number of objects currently in the pool has reached the MaxPoolSize . If not, the Pooling Manager creates new objects to fulfill the request. The Pooling Manager tends to create as many objects as needed to keep the available objects at the level of MinPoolSize while not exceeding the MaxPoolSize .

  • If no object is available and no new object can be created because of the size restriction of the pool, the client requests are queued to receive the first available object from the pool. If an object cannot be made available within the time specified in the CreationTimeOut property, an exception is thrown.

  • When the client is done with an object, it invokes a Dispose() method on the object. The Pooling Manager intercepts this request and calls the CanBePooled() method on the object to check if the object is to be pooled. If the method returns True, the object is stored in the object pool. On the other hand, if the CanBePooled() method returns False, the object is destroyed forever.

  • The Pooling Manager ensures that an optimum number of objects are always available in the object pool. If the number of available objects in the pool drops below the specified minimum, new objects are created to meet any outstanding object requests and refill the pool. If the number of available objects in the pool is greater than the minimum number, those surplus objects are destroyed during a cleanup cycle.

EXAM TIP

To Pool or not to Pool? Using the object pooling service with every application might not be a good idea. Although object pooling has benefits, it also has its share of overhead. You should use object pooling in those applications in which the benefits of object pooling exceed the overheads. Some of the scenarios suitable for object pooling are

  • When the cost of creating an object is relatively high

  • When usage costs are relatively low

  • When an object will be reused often

  • When you want to limit the number of object instances

Some scenarios in which object pooling might not be useful are

  • When an object is inexpensive to create

  • When the object does not maintain any state

  • When the object must be activated in the caller's context

  • When you do not need to restrict the number of object instances


Creating an Object-Pooled Serviced Component

Step By Step 7.8 shows how to create an object-pooled serviced component by applying the ObjectPooling attribute and overriding the CanBePooled() method. In addition, the serviced component in Step By Step 7.8 also overrides the Activate() and DeActivate() methods of the ServicedComponent class. Recall from Table 7.2 that the Activate() method is invoked by enterprise services either when an object is created afresh or when an object is activated from the pool. The Deactivate() method is called just before an object is deactivated and returned to the pool or destroyed.

In Step By Step 7.8, I use the constructor, Activate() , and Deactivate() methods to write messages to the Windows event log. This helps you monitor how object activation and deactivation is being performed by the system.

WARNING

Don't Pool Objects with Client-Specific States When a client program repeatedly requests an instance of an object pooled serviced component, there is no guarantee that the client will receive exactly the same instance of the object that it received in the earlier requests. Any object that needs to maintain client-specific state should not be pooled.


STEP BY STEP

7.8 Creating an Object-Pooled Serviced Component

  1. Add a new Visual Basic .NET Class library named StepByStep7-8 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-8 and select Add Reference from the context menu to add a reference to the System.EnterpriseServices component.

  3. In the Solution Explorer, rename the default Class1.vb to NorthwindSC.vb .

  4. Open the NorthwindSC.vb and replace the code with the following code:

     Imports System Imports System.Data Imports System.Data.SqlClient Imports System.EnterpriseServices Imports System.Runtime.InteropServices Imports System.Diagnostics Public Interface INorthwind     Function ExecuteQuery(ByVal strQuery As String) _      As DataSet     Function UpdateData(ByVal ds As DataSet) As Integer End Interface <ObjectPooling(True, 2, 4), _  ClassInterface(ClassInterfaceType.None)> _  Public Class NorthwindSC     Inherits ServicedComponent     Implements INorthwind     Private sqlcnn As SqlConnection     Private sqlda As SqlDataAdapter     Private ds As DataSet     Private el As EventLog     Public Sub New()         ' Perform expensive startup operations         ' Create a connection to the         ' Northwind SQL Server database         sqlcnn = New SqlConnection(_          "data source=(local);" & _          "initial catalog=Northwind;" & _          "integrated security=SSPI")         ' Create an EventLog object         ' and assign its source         el = New EventLog()         el.Source = "NorthwindPool"         ' Write an entry to the event log         el.WriteEntry(_          "NorthwindSC object is created" & _         " and added to the object pool")     End Sub     Protected Overrides Function CanBePooled() _      As Boolean         CanBePooled = True     End Function     Protected Overrides Sub Activate()         el.WriteEntry(_          "A NorthwindSC object is activated" & _          " from the object pool")     End Sub     Protected Overrides Sub Deactivate()         el.WriteEntry(_          "A NorthwindSC object is deactivated" & _          " and is returned to the object pool")     End Sub     ' This method executes a SELECT query and     ' returns the results in a DataSet object     Public Function ExecuteQuery(_      ByVal strQuery As String) As DataSet _      Implements INorthwind.ExecuteQuery         ' Create a SqlDataAdapter object to         ' talk to the database         sqlda = New SqlDataAdapter(_          strQuery, sqlcnn)         ' Create a DataSet object         ' to hold the results         ds = New DataSet()         ' Fill the DataSet object         sqlda.Fill(ds, "Results")         ExecuteQuery = ds     End Function     ' This method updates the database with     ' the changes in a DataSet object     Public Function UpdateData(_      ByVal ds As DataSet) As Integer _      Implements INorthwind.UpdateData         ' Update the database         ' and return the result         Dim sqlcb As SqlCommandBuilder = _          New SqlCommandBuilder(sqlda)         UpdateData = sqlda.Update(_          ds.Tables("Results"))     End Function End Class 
  5. Select Tools, Create GUID to open the Create GUID dialog box. Select the Registry Format option in the dialog box and click the Copy button. Paste the copied GUID to the Guid attribute of the NorthwindSC class (after removing the curly brackets), as shown here:

     <ObjectPooling(True, 2, 4), _  ClassInterface(ClassInterfaceType.None), _  Guid("CE1F97FD-607E-4cee-8BC9-4B3883DE5D3B")> _  Public Class NorthwindSC 

    Note that you will get a different GUID from the Create GUID dialog box.

  6. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  7. Add the following assembly level attributes to the AssemblyInfo.cs file:

     <Assembly: ApplicationName("Northwind Data " & _  "Application with Object Pooling")> <Assembly: Description("Retrieve and Update data " & _  "from the Northwind database")> <Assembly: ApplicationActivation(_  ActivationOption.Server)> 
  8. Change the AssemblyVersion and AssemblyKeyFile attribute in the AssemblyInfo.vb file, as shown here:

     <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  9. Build the project. A StepByStep7-8.dll is generated, and a strong name is assigned to the file based on the specified key file.

  10. Launch Visual Studio .NET command prompt and change the directory to the folder where the DLL file generated in step 9 resides. Issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs StepByStep7-8.dll 
  11. In the command prompt, issue the following command to install the assembly to the GAC:

     gacutil /i StepByStep7-8.dll 

In Step By Step 7.8, I selected the ActivationOption to be Server instead of Library because I wish to create an object in the context of the server and not that of the client.

I also registered the assembly as a COM+ application. Now when you use the Component Services administrative tool to access the properties of the NorthwindSC component in this application, you get the options to configure the object pooling parameters, as shown in Figure 7.22.

Figure 7.22. You can configure the object pooling parameters for a component at runtime using the Component Services administrative tool.

Using an Object-Pooled Serviced Component

In this section, I'll demonstrate how to use an object-pooled serviced component from a client program. I'll show you two different ways to use a serviced componentthe greedy approach and a non-greedy approach. You'll understand what each of these approaches are and why you should use one over the other as you proceed. I'll first start with an example of the greedy approach to call a serviced component in Step By Step 7.9.

STEP BY STEP

7.9 Using an Object-Pooled Serviced Component: Greedy Approach

  1. Add a new Visual Basic .NET Windows application named StepByStep7-9 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-9 and select Add Reference from the context menu to add references to the System.EnterpriseServices and StepByStep7-8 components.

  3. In the Solution Explorer, copy the NorthwindSCClient.vb file from the StepByStep7-7 project to the current project. Open the form in code view and change all occurrences of NorthwindSCClient to refer to NorthwindSCGreedyClient instead. Also, change the namespace name to StepByStep7_9 and the Imports directive to refer to StepByStep7_8 instead of StepByStep7_5 . Delete the default Form1.vb .

  4. Modify the btnUpdate_Click() method as shown here:

     Private Sub btnUpdate_Click(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnUpdate.Click     Try         ' Call the UpdateData() method of the         ' NorthwindSC serviced component to update         ' the database and display the number         ' of updated rows in the database         Dim intRows As Integer = nsc.UpdateData(_         CType(dgResults.DataSource, DataSet))         MessageBox.Show(String.Format(_           "{0} row(s) updated successfully", intRows), _           "Row(s) Updated", MessageBoxButtons.OK, _           MessageBoxIcon.Information)         ' Load the updates and bind the grid         ' with the updates         dgResults.DataSource = _             nsc.ExecuteQuery(txtQuery.Text)         dgResults.DataMember = "Results"     Catch ex As Exception         MessageBox.Show(ex.Message, "Update Failed", _           MessageBoxButtons.OK, MessageBoxIcon.Error)     End Try End Sub 
  5. Set the form as the startup object for the project. Build the project. Set the project StepByStep7-9 as the startup project.

  6. Select Debug, Start Without Debugging to launch the client form. Open the Component Services administrative tool; use the tree view on the left to navigate to the Event Viewer node. View the Application Log; you should notice that three messages from NorthwindPool source are logged. When the application starts, the number of objects created equals the minimum size specified for the object pool. In this case, two objects are created, and each of them logs a message to the event log when executing the constructor. The third message is generated from the Activate() method because the client program activates an object from the pool.

  7. Select Debug, Start Without Debugging to launch another client form. View the messages in the event log; you see that one new message is added to the event log. This message is generated from the Activate() method because the second serviced component object is already created and is just activated from the object pool. Now launch a third form and view the messages in the event log. You should notice that this adds two more messages to the event log; one is added because the third serviced component object is created, and the other is added because the client also activates the object. Repeat the same process and launch a fourth form; you see that two more messages are added to the event log because the fourth object is created and then activated from the pool. Now try launching a fifth form; a fifth form will not be created because the maximum size of the pool is four, and four objects are already created. If you wait long enough (that is, the default timeout value of 60,000 milliseconds), you will get a connection timed out error. If instead you close one of the four open forms while the fifth form is waiting, the fifth form is launched immediately. View Event log and you notice two messagesone from the Deactivate() method because you closed a form and another from the Activate() method because the fifth form does not create an object from scratch but instead fetches one from the object pool.

When the form in Step By Step 7.9 is loaded, it creates an object on the server and then holds the reference to this object until the form is closed. The client is holding the resource for more time than it actually needs; therefore, I call this program a greedy client. You can also note from Step By Step 7.9 that the solution involving greedy clients does not scale well with an increasing number of clients .

An alternative to the greedy client is to create clients that are not greedy, which means that the client should allocate the server resource as late as possible and release it as early as possible. This scheme will ensure that the resources at the server are occupied only for the period of time that they are actually used. As soon as a client frees a resource, the resource can be used by another client that might be waiting for it. Step by Step 7.10 shows one such solution.

STEP BY STEP

7.10 Using an Object-Pooled Serviced Component: Non-greedy Approach

  1. Add a new Visual Basic .NET Windows Application project named StepByStep7-10 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-10 and select Add Reference from the context menu to add references to the System.EnterpriseServices and StepByStep7-8 components.

  3. In the Solution Explorer, rename the default Form1.vb to NorthwindSCNonGreedyClient.vb . Open the form in code view and change all occurrences of Form1 to refer to NorthwindSCNonGreedyClient instead.

  4. Add the following Imports directives:

     Imports System.Data.SqlClient Imports StepByStep7_8 
  5. Place two Label controls, a TextBox control ( txtQuery ), two Button controls ( btnExecute and btnUpdate ), and a DataGrid control ( dgResults ) on the form (refer to Figure 7.17). Set the MultiLine property of the TextBox control to True .

  6. Double-click the Button controls and add the following code to their Click event handlers:

     Private Sub btnExecute_Click(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnExecute.Click     ' Declare and Instantiate the serviced component     Dim nsc As NorthwindSC = New NorthwindSC()     Try         ' Call the ExecuteQuery() method of the         ' NorthwindSC serviced component to execute         ' query and bind the results to the data grid     dgResults.DataSource = _         nsc.ExecuteQuery(txtQuery.Text)         dgResults.DataMember = "Results"     Catch ex As Exception         MessageBox.Show(ex.Message, "Invalid Query", _          MessageBoxButtons.OK, MessageBoxIcon.Error)     Finally         nsc.Dispose()     End Try End Sub Private Sub btnUpdate_Click(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnUpdate.Click     ' Declare and Instantiate the serviced component     Dim nsc As NorthwindSC = New NorthwindSC()     Try         ' Call the UpdateData() method of the         ' NorthwindSC serviced component to update         ' the database and display the number         ' of updated rows in the database         Dim intRows As Integer = nsc.UpdateData(_          CType(dgResults.DataSource, DataSet))         MessageBox.Show(String.Format(_          "{0} row(s) updated successfully", intRows), _          "Row(s) Updated", MessageBoxButtons.OK, _          MessageBoxIcon.Information)         ' Load the updates and bind the grid         ' with the updates         dgResults.DataSource = _          nsc.ExecuteQuery(txtQuery.Text)         dgResults.DataMember = "Results"     Catch ex As Exception         MessageBox.Show(ex.Message, "Update Failed", _          MessageBoxButtons.OK, MessageBoxIcon.Error)     Finally         nsc.Dispose()     End Try End Sub 
  7. Set the form as the startup object for the project. Build the project. Set the project StepByStep7-10 as the startup project.

  8. Open the Component Services Administrative tool. If Northwind Data Application with Object Pooling is still running, shut down the application. This will clear any existing object pool. Select Debug, Start Without Debugging to launch the client form. Open the Event Viewer and view the Application Log; you should not see any messages added to the event log because in this version of the client the object is created only when a method is invoked.

  9. Enter a query in the text box and click the Execute Query button. View the Application Event log; you should notice four messages from the NorthwindPool source. The first two messages are generated by the constructor because two is the minimum size of the pool and when the application starts, that many new objects are created. The third message comes from the Activate() method when the client program activates an object from the pool; just before the ExecuteQuery() method finishes, the method calls a Dispose() method on the serviced component object and that's why you see the fourth message, which is generated by the Deactivate() method because the object is being sent back to the pool.

  10. Launch four more client forms. You will notice that the fifth form is also launched even though the maximum size of the pool is four. The reason being that the object is not created when the form loads; instead, the object is created when you fire the method, and then the object is destroyed. Execute queries in the forms; you notice that only messages from the Activate() and Deactivate() methods are added to the event log, which means that no new objects are created. The existing two objects (minimum size of the pool) in the pool are capable of processing all calls because they are tied with a client only for a small duration when a method is invoked. The object is returned to the pool as soon as the method returns. However, if you call more than two methods on the object simultaneously , the third object is created. However, the maximum size of the pool is still not reached, and the clients don't have to wait for a server resource.

In Step by Step 7.10, I am creating the object each time within a method call and destroying it as soon as the method is completed. This ensures that the client holds a reference to the server object only for the period when they are actually using the object.

From the perspective of conventional programming, this approach might look inefficient because if the client is repeatedly creating objects, it will be slower as compared to a client that creates an object only once and then holds onto it. However, in Step By Step 7.9 you can see that the benefits of scalability surpass the difference in speed. That is very important for enterprise applications, which must scale with an increasing number of users.

In Step By Step 7.9, it is important for the client to call the Dispose() method on server objects as soon as the objects are not needed; if the client programs don't do so, they keep holding the server resources for their lifetime.

Ideally, in this scenario, a server application should automatically dispose of server objects as soon as the client is done using them. So, what is the solution? I'll tell you about a server-side solution to deal with the dispose problem in a forthcoming section, "Just-In-Time Activation."

NOTE

Using Object Pooling to Control Licensing Using object pooling, you can set the upper limit for the consumption of a server resource. This feature of object pooling can help you scale your application as the number of licensed users increase. Moreover, because of the declarative nature of COM+ services, it is easy to configure this setting at runtime with little administrative effort.


Monitoring Statistics of a Serviced Component

When an object writes messages to the event log, that's a way to monitor how an object is functioning. However, you might also want to aggregate information about all objects that are currently instantiated and monitor how a component as a whole is working. In this section, you'll learn how to use the Component Services administrative tool to monitor the usage statistics for a serviced component.

STEP BY STEP

7.11 Monitoring Object Statistics of a Serviced Component

  1. Open the Component Services administrative tool. Using the left pane, navigate to the StepByStep7_8.NorthwindSC component within the Northwind Data Application with Object Pooling COM+ application.

  2. Right-click on the component; you see the Properties dialog box for the StepByStep7_8.NorthwindSC component. Select the Activation tab and check the option named Component Supports Event and Statistics, as shown in Figure 7.23. Click OK.

    Figure 7.23. Select the Component Supports Event and Statistics option to monitor usage statistics for a component.

  3. Shut down the Northwind Data Application with Object Pooling COM+ application. Then start the application.

  4. Using the left pane, navigate to the Components node within the Northwind Data Application with Object Pooling COM+ application. Select Status View from the View menu.

  5. Launch several instances of the resource log program StepByStep7_9.exe ; note that as you instantiate and work with the program, you see the statistics of the component in the right pane of the Component Services administrative tool as shown in Figure 7.24.

    Figure 7.24. The Status view of the component allows you to view the component's statistics.

Table 7.10 shows what each of the status view columns in the right pane of the Component Services administrative tool mean.

NOTE

Fine Tuning an Application The initial estimated settings of the MinPoolSize , MaxPoolSize , and CreationTimeOut properties might not be optimum for a specific application and its environment. Usually, administrators monitor the behavior of an application and then fine-tune its configuration settings to suit the environment. An administrator might have to experiment with several different values to reach the desired performance level. Such fine-tuning of an application can be easily performed using the Component Services administrative tool. To safely change the pool size, you should shut down the running application first.


Table 7.10. What Each Status View Column Represents

Column

Description

ProgID

Identifies a specific component.

Objects

Shows the number of objects that are currently held by the client programs.

Activated

Shows the number of objects that are currently activated.

Pooled

Shows the total number of objects created by the pooling manager. This number is the sum of objects that are in use and the objects that are deactivated.

In Call

Shows the number of objects that are currently executing some client request.

Call Time (ms)

Shows the average call duration (in milliseconds) of method calls made in the last 20 seconds (up to 20 calls).

In Step by Step 7.11, you checked the Component Supports Events and Statistics check box to specify that you are interested in recording the statistics for a serviced component. If you want your components to always install in the COM+ catalog with this option turned on, you should apply the EventTrackingEnabled attribute to your class and set its Value property to true.

Just-In-Time Activation

In the "Object Pooling" section, I used two different approaches for designing the client program:

  • In the first approach, a client creates an object and holds onto it until the client no longer needs it. The advantage of this approach is that it's faster because clients need not create objects repeatedly. The disadvantage is that this approach can be expensive in terms of server resources in a large-scale application.

  • In the second approach, a client can create, use, and release an object. The next time it needs the object, it creates it again. The advantage to this technique is that it conserves server resources. The disadvantage is that as your application scales up, your performance slows down. If the object is on a remote computer, each time an object is created, there must be a network round-trip, which negatively affects performance.

Although either of these approaches might be fine for a small-scale application, as your application scales up, they're both inefficient. Moreover, in an enterprise application, the server scalability should not be a factor of how the clients are designed. The just-in-time activation service of COM+ provides a server-side solution that includes advantages of both of the above approaches while avoiding the disadvantages of each.

How Just-In-Time Activation Works

Just-in-time activation is an automatic service provided by COM+. To use this service in a class, all you need to do is to mark the class with the JustInTimeActivation attribute set to true .

Figure 7.25 shows how the just-in-time activation service works. I have also summarized the process in the following list:

  • When the client requests an object from the server, COM+ intercepts that request, creates a proxy, and returns the proxy to the client. The client maintains a long-lived reference to the proxy, thinking that it is a reference to the actual object. This way, the client does not spend time repeatedly creating the object. The server also saves resources because it can delay creating the object until the client invokes a method on it.

  • When the client invokes a method on the object (using the proxy), COM+ actually creates the object, calls the method, return the results, and then destroys the object. Because the objects are short lived, server resources are consumed only for a small period, and the server is readily available to serve other waiting clients.

Figure 7.25. The just-in-time activation service saves server resources by creating objects just-in-time when they are required.

As shown in Figure 7.25, any JIT activated object will be created in its own context. The context maintains a "done bit" to specify when the object will be deactivated. The interception mechanism checks the done bit after each method call finishes. If the value of the done bit is true, the object is deactivated; otherwise , the object continues to exist. The default value of the done bit is False. Nevertheless, you can programmatically set the value of the done bit using any of the following techniques:

  • The ContextUtil.SetComplete() or ContextUtil.SetAbort() Methods Usually these methods are used to vote for the success or failure of a transaction, but they additionally also set the done bit to True.

  • The ContextUtil.DeactivateOnReturn Property When you set this property to True in a method, the property sets the done bit to True.

  • The AutoComplete Attribute When you always want to deactivate an object when the method call returns, you can apply this attribute to the method definition. This attribute automatically calls ContextUtil.SetComplete() if the method completes successfully. If the method throws an exception, the ContextUtil.SetAbort() method is invoked. In both cases, the done bit is set to True.

After understanding how the JIT activation works, you can appreciate how JIT activation enables scalable applications. However, the server still has a lot of work to do. If the client is frequently calling methods, the server frequently creates and destroys the object. The benefits of JIT activation are lowered when the cost of object creation is significantly high. However, you already know a COM+ service that saves the cost on object creationobject pooling. What will happen if you combine both JIT activation and object pooling services together for a serviced component? You will create a recipe for high throughput.

Using Just-In-Time Activation with Object PoolingA Recipe for High Throughput

Throughput is a measure of the processing done by an application during a given time. Often, as the number of users increases , so does the competition for a server's resources. This normally results in an overall decrease in throughput.

The JIT activation and object pooling services complement each other's features. When these services are used in combination, they can maximize the throughput for an application by providing the following benefits:

  • JIT activation enables clients to hold long-lived references to a server object (through a proxy) without consuming server resources.

  • JIT activation enables the server objects to be destroyed as soon as their work is over in order to minimize the resource consumption on the server.

  • Object pooling caches already created objects and saves time by activating and deactivating the objects from the pool instead of re-creating them from scratch.

Design Considerations for Using Just-In-Time Activation and Object Pooling

When using JIT activation in your programs, you need to consider the following points:

  • The lifetime of the objects is controlled by the server instead of the client. Therefore, you need not call the Dispose() method on the server object from the client. In fact, if you do so, the object will be re-created on the server so that it can be disposed of.

  • The server does not automatically deactivate an object. You need to set the done bit to True for COM+ to destroy an object after the current method call has completed. You can use any of the techniques mentioned in the previous section to set the done bit to True. You can also configure a method administratively to control this behavior.

  • The objects are created and destroyed after each method call. Therefore, you should consider the server object as stateless. JIT activation is not suitable for objects that need to maintain state across method calls.

Creating a JIT-Activated Object-Pooled Serviced Component

In this section, you'll learn how to use just-in-time activation and object pooling to create an application that is more scalable and that will have improved performance. In Step By Step 7.12, you will see how a few simple changes to a serviced component enable it to scale efficiently and support a large number of clients without depending on the client to call the Dispose() method on the server objects.

STEP BY STEP

7.12 Creating a JIT-Activated Serviced Component

  1. Add a new Visual Basic .NET Class library project named StepByStep7-12 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-12 and select Add Reference from the context menu to add references to the System.EnterpriseServices component.

  3. In the Solution Explorer, copy the NorthwindSC.vb file from the StepByStep7-8 project to the current project. Delete the default Class1.vb .

  4. Apply the JustInTimeActivation and EventTrackingEnabled attributes on the NorthWindSC class and change the GUID in the Guid attribute to a newly-generated GUID:

     <ObjectPooling(True, 2, 4), _  ClassInterface(ClassInterfaceType.None), _  Guid("A86537CE-B2BA-4b4f-8A54-D14AF7D04C0B"), _  EventTrackingEnabled(True), _  JustInTimeActivation(True)> _  Public Class NorthwindSC     Inherits ServicedComponent     Implements INorthwind   ... 
  5. Modify the ExecuteQuery() and UpdateData() methods as shown here:

     ' This method executes a SELECT query and ' returns the results in a DataSet object Public Function ExecuteQuery(_  ByVal strQuery As String) As DataSet _  Implements INorthwind.ExecuteQuery     ' Create a SqlDataAdapter object to     ' talk to the database     sqlda = New SqlDataAdapter(_      strQuery, sqlcnn)     ' Create a DataSet object     ' to hold the results     ds = New DataSet()     ' Fill the DataSet object     sqlda.Fill(ds, "Results")     ' Deactivate the object     ' when the method returns     ContextUtil.DeactivateOnReturn = True     ExecuteQuery = ds End Function ' This method updates the database with ' the changes in a DataSet object <AutoComplete()> _ Public Function UpdateData(_  ByVal ds As DataSet) As Integer _  Implements INorthwind.UpdateData     ' Update the database     ' and return the result     Dim sqlcb As SqlCommandBuilder = _      New SqlCommandBuilder(sqlda)     UpdateData = sqlda.Update(_      ds.Tables("Results")) End Function 
  6. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  7. Add the following assembly level attributes in the AssemblyInfo.vb file:

     <Assembly: ApplicationName("Northwind Data " & _  "Application with JIT Activation")> <Assembly: Description("Retrieve and Update data " & _  "from the Northwind database")> <Assembly: ApplicationActivation(_  ActivationOption.Server)> 
  8. Change the AssemblyVersion and AssemblyKeyFile attribute in the AssemblyInfo.vb file as shown here:

     <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  9. Build the project. A StepByStep7-12.dll is generated, and a strong name is assigned to the file based on the specified key file.

  10. Launch Visual Studio .NET command prompt and change the directory to the folder where the DLL file generated in step 9 resides. Issue the following command to install the assembly to the GAC:

     gacutil /i StepByStep7-12.dll 
  11. In the command prompt, issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs StepByStep7-12.dll 

In Step By Step 7.12, I have applied the JustInTimeActivation attributed to the NorthwindSC class. This attribute instructs the runtime to check the value of the done bit after each method call. The object is deactivated if the done bit evaluates to True after a method call.

I use two different ways to set the done bit in this program. In the ExecuteQuery() method, I set the DeactivateOnReturn property of the ContextUtil class to True, whereas in the UpdateData() method, I apply the AutoComplete attribute. The AutoComplete attribute always deactivates the object after the method call is completed, whereas the ContextUtil.DeactivateOnReturn property is more flexible because it can be set to True or False depending on a condition.

You can also configure the Just-In-Time Activation service using the Component Services administrative tool. To enable JIT activation for a component, you need to check the Enable Just-In-Time Activation check box in the component properties dialog box, as shown in Figure 7.26.

Figure 7.26. You can easily configure just-in-time activation for a serviced component using the Component Services administrative tool.

To set the done bit after a method completes, you need to configure a method's property, as shown in Figure 7.27.

Figure 7.27. Using the Component Services administrative tool, you can access properties for a method and configure it to deactivate the object when the method call returns.

Using a JIT-Activated Object-Pooled Serviced Component

Step By Step 7.13 demonstrates how to use a JIT-activated object-pooled serviced component. This program is similar to that of Step By Step 7.10but this time it's simpler because the server automatically takes care of disposing the server objects, and thus the client is not required to call Dispose() on the server objects.

STEP BY STEP

7.13 Using a JIT-Activated Serviced Component

  1. Add a new Visual Basic .NET Windows Application project named StepByStep7-13 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-13 and select Add Reference from the context menu to add references to the System.EnterpriseServices and StepByStep7-12 components.

  3. In the Solution Explorer, copy the form NorthwindSCNonGreedyClient.vb from the StepByStep7-10 project to the current project. Delete the default Form1.vb . Open the form in code view and change Imports StepByStep7_8 to Imports StepByStep7_12 instead.

  4. Remove the finally block from the btnExecute_Click() and btnUpdate_Click() methods.

  5. Set the form as the startup object for the project. Build the project. Set the project StepByStep7-13 as the startup project.

Run the project as you did in Step By Step 7.10. You'll note that results are the same despite the fact that the client program is not calling Dispose() on the server objects.

Object Construction

The object construction service of COM+ allows you to specify initialization information for a serviced component. The advantage of using the construction string service is that it enables the string to be configured externally using tools such as the Component Services administrative tool.

To use the object construction service in a serviced component, you need to do the following:

  • Apply the ConstructionEnabled Attribute You need to apply the ConstructionEnabled attribute on a class that uses the object construction service. This attribute has two properties: Enabled and Default . The default values of these properties are True and empty string, respectively. The Default property specifies the constructor string.

  • Override the Construct() Method To receive the currently configured constructor string from the COM+ catalog, an object must override the Construct() method of the ServicedComponent class. When the ConstructionEnabled attribute is true, the enterprise service calls this method automatically after the constructor of the component has been executed. The Construct() method receives the currently configured construction string as its only parameter.

NOTE

Using Object Construction with Object Pooling You can pair object construction with object pooling to have more control of how the clients reuse resources. For example, you can design a generic serviced component that exposes resources to the clients based on the constructor string. You can install this component several times in a COM+ application (ensuring that each component has a different CLSID) and then assign each of these components with a distinct constructor string. The COM+ application now has distinct pools of objectseach usable by a distinct groups of clients.


Once the ConstructionEnabled attribute is applied, you can configure the constructor string using the component's property sheet in the Component Services administrative tool.

REVIEW BREAK

  • The COM+ object pooling service allows you to maintain already created instances of a component that are ready to be used by any client that requests the component.

  • To use the object pooling service, a class must override the CanBePooled() method of the ServicedComponent class. In addition, you need to apply the ObjectPooling attribute on the class or configure the component via the Component Services administrative tool to use object pooling.

  • The COM+ just-in-time activation service allows you to minimize the period of time an object lives and consumes resources on the server.

  • You can enable the just-in-time activation service declaratively in your programs by using the JustInTimeActivation attribute or administratively via the Component Services administrative tool. You must however, tell COM+ whether an object should be deactivated after a method call. You can do this by setting the done bit to true.

  • The COM+ object construction service allows you to specify initialization information for a serviced component. It is possible to use the Component Services administrative tool and configure the initialization information for a component to change the way it behaves.

GUIDED PRACTICE EXERCISE 7.2

You need to modify the serviced component created in Step By Step 7.10 in such a way that system administrators should be able to configure the database connection string for the component. In order to do this, there should not be a need to recompile the application. You do not want to log events to the event log from the serviced component.

How would you create such a serviced component and then configure the component using the Component Services administrative tool?

This exercise helps you practice creating serviced components that use COM+ services. You should try working through this problem on your own first. If you are stuck, or if you would like to see one possible solution, follow these steps:

  1. Add a new Visual Basic .NET Class library named GuidedPracticeExercise7-2 to the solution.

  2. In the Solution Explorer, right-click the project GuidedPracticeExercise7-2 and select Add Reference from the context menu to add references to the System.EnterpriseServices component.

  3. In the Solution Explorer, rename the default Class1.vb to NorthwindSC.vb .

  4. Open the NorthwindSC.vb class and replace the code with the following code. You should generate your own GUID in place of the one shown here:

     Imports System Imports System.Data Imports System.Data.SqlClient Imports System.EnterpriseServices Imports System.Runtime.InteropServices Public Interface INorthwind     Function ExecuteQuery(ByVal strQuery As String) _      As DataSet     Function UpdateData(ByVal ds As DataSet) As Integer End Interface ' Default value for the Construction ' string specified here <ConstructionEnabled(_  Default:="data source=(local);" & _  "initial catalog=Northwind;" & _  "integrated security=SSPI"), _  ObjectPooling(True, 2, 4), _  ClassInterface(ClassInterfaceType.None), _  Guid("1E8795DF-2B23-4304-8D8F-B2D7A8C8BBF0"), _  EventTrackingEnabled(True), _  JustInTimeActivation(True)> _  Public Class NorthwindSC Inherits ServicedComponent     Implements INorthwind     Private sqlcnn As SqlConnection     Private sqlda As SqlDataAdapter     Private ds As DataSet     Public Sub New()     End Sub     Protected Overrides Sub Construct(_      ByVal s As String)         ' Open a connection to the specified         ' sample SQL Server database         sqlcnn = New SqlConnection(s)     End Sub     Protected Overrides Function CanBePooled() _      As Boolean         CanBePooled = True     End Function     ' This method executes a SELECT query and     ' returns the results in a DataSet object     Public Function ExecuteQuery(_      ByVal strQuery As String) As DataSet _      Implements INorthwind.ExecuteQuery         ' Create a SqlDataAdapter object to         ' talk to the database         sqlda = New SqlDataAdapter(_          strQuery, sqlcnn)         ' Create a DataSet object         ' to hold the results         ds = New DataSet()         ' Fill the DataSet object         sqlda.Fill(ds, "Results")         ' Deactivate the object         ' when the method returns         ContextUtil.DeactivateOnReturn = True         ExecuteQuery = ds     End Function     ' This method updates the database with     ' the changes in a DataSet object     <AutoComplete()> _     Public Function UpdateData(_      ByVal ds As DataSet) As Integer _      Implements INorthwind.UpdateData         ' Update the database         ' and return the result         Dim sqlcb As SqlCommandBuilder = _          New SqlCommandBuilder(sqlda)         UpdateData = sqlda.Update(_          ds.Tables("Results"))     End Function End Class 
  5. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  6. Add the following attributes to the AssemblyInfo.vb file:

     <Assembly: ApplicationName("Northwind Data " & _  "Application with Object Construction")> <Assembly: Description("Retrieve and Update data " & _  "from the Northwind database")> <Assembly: ApplicationActivation(_  ActivationOption.Server)> <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  7. Build the project. A GuidedPracticeExercise7-2.dll is generated, and a strong name is assigned to the file based on the specified key file.

  8. 8. Launch Visual Studio .NET command prompt, change to the directory of the GuidedPracticeExercise7_2.dll file, and issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs GuidedPracticeExercise7-2.dll 
  9. Open the Component Services Administrative tool by selecting Administrative Tools, Component Services, Computers, My Computer, COM+ Applications from the Windows Control Panel. You should be able to view the Northwind Data Application with Object Construction added to the COM+ Catalog.

  10. Right-click the GuidedPracticeExercise7_2.NorthwindSC serviced component and select properties from the context menu. Click the Activation tab; notice that the Enable Object Construction check box is selected and the Constructor string text box contains the connection string supplied to the ConnectionEnabled attribute in the NorthwindSC.vb file, as shown in Figure 7.28.

    Figure 7.28. You can easily configure the object construction for a serviced component using the Component Services administrative tool.

Guided Practice Exercise 7.2 registers the serviced component in the COM+ catalog. Before you can use the component from a client application, you should deploy the component to the GAC. The design of the client application is unaffected by the application of the ConstructionEnabled attribute. You can easily modify the client program in Step By Step 7.13 to work with this serviced component.

If you have difficulty following this exercise, review the sections "Object Pooling," "Just-In-Time Activation," and "Object Construction," earlier in this chapter. After doing that review, try this exercise again.

Automatic Transaction Processing

As discussed earlier in the section "Microsoft Transaction Server (MTS)," a transaction is a series of operations performed as a single unit. A transaction is successful only when all of the operations in that transaction succeed.

For example, consider a banking application that needs to perform a balance transfer from account A to account B for amount X. In this transaction, two atomic operations are involved. First, the balance in account A should be decreased by amount X, and second, the balance in account B should be increased by amount X. For balance transfer action to succeed, both of these atomic operations should succeed. If one of the actions fails, the system is in inconsistent state (with wrong account balances ) and the complete operation should be undone (rolled back). Instead, if both the atomic operations succeed, the balance transfer action also succeeds and the changes can be made permanent (committed).

Using transactions to ensure the reliability of applications is not a new concept. Transaction processing has been part of database management systems long before the concept of a transaction came to business objects. The transaction processing mechanism of COM+ provides two significant advantages over traditional transaction processing techniques:

  • Distributed Transactions A local transaction is one whose scope is limited to a single transactional resource, such as a SQL Server database. On the other hand, a distributed transaction can span over several transaction-aware resources. The transaction-aware resources in a distributed transaction might be heterogeneous (such as SQL Server database, Oracle database, Microsoft Messaging Queue) and might involve multiple computers over a network.

  • Automatic Transactions A typical database transaction, such as the one implemented using Transact SQL code or ADO.NET code, is manual in nature. With manual transactions, you explicitly begin and end transactions and implement all the necessary logic to take care of the commit and rollback process. However, COM+ provides automatic transaction services that you can use in your program without writing any additional code. COM+ implements this with help of a Windows service called Microsoft Distributed Transaction Coordinator (MSDTC).

In this section, I discuss how COM+ automatic transaction processing service works and how to use this service to implement transactions across a single transaction-aware resource as well as multiple transaction-aware resources residing in separate processes.

Using Automatic Transaction Service for Local Transactions

Before I go any further, let me give you a first-hand exposure on using automatic transaction processing for a local transaction in Step By Step 7.14.

In this example, I use the serviced component created in Step By Step 7.12 and demonstrate how you can use COM+ automatic transaction services without writing even a single line of additional code.

STEP BY STEP

7.14 Using a Local Automatic Transaction Processing Service

  1. Start the Windows Application StepByStep7_13.exe . This is the client application that uses an object-pooled and just-in-timeenabled serviced component (StepByStep7_12.NorthwindSC). Together, they allow you to query and update data from the Northwind sample database.

  2. In the Windows form, type the following query and click on the Execute Query button:

     Select * from customers 
  3. You see that a list of customers is displayed. Change the ContactName for a customer (such as, change "Maria Anders" to "Maria Anderson"). In the next record, change the CustomerID to any string of your choice (such as, change "ANATR" to "ANAT"). Click on the Update button. You get an error message because the CustomerID field has a check for referential integrity and the database won't allow you to change that.

  4. Click on the Execute Query button. The fresh result shows that although you got an error in the previous execution, the database saved the record in which you modified the ContactName while it didn't save the erroneous record in which you modified the CustomerID. You now decide that both the database update operations performed in the previous step should be part of a transaction. If both of them succeed, the database should be updated with changes; otherwise, the records should be rolled back to their old values.

  5. Open the Component Services administrative tool. Open the property sheet for StepByStep7_12.NorthwindSC serviced component. Select the Transactions tab and select the Required option in the Transaction support group box, as shown in Figure 7.29.

    Figure 7.29. You can configure a serviced component using the Component Services administrative tool to use the COM+ automatic transaction processing service.

  6. Switch to the Windows form and click on the Execute Query button for the same query. Now try updating the ContactName in one row and CustomerID in another row and click on the Update button. You get the same error message that you got in step 3. To verify the changes to the database, click on the Execute Query button and observe the results. You see that both ContactName and CustomerID are unchanged. This happened because when you got an error while updating data, COM+ rolled back the complete operation, leaving the database in its original shape.

In Step By Step 7.14, you use the Component Services administrative tool to configure the automatic transaction service for a component. Like the other COM+ services, you can also accomplish this by writing declarative attributes in your programs. You'll learn about various attributes related to transaction processing in the next few sections.

Elements of Transaction Processing

The System.Enterprise service namespace provides various classes to work with transactions in your programs. These classes hide the complexity of automatic transaction processing and provide most of the functionality with declarative attributes. Under the covers, these classes negotiate with COM+ and MSDTC services to implement the transaction.

In this section, you'll learn how these classes work together to provide automatic transaction services.

The Transaction Attribute

Applying the System.EnterpriseService.Transaction attribute to a class is the equivalent of enabling a transaction through the Component Services administrative tool. The benefit of applying an attribute in the program is that the component can execute in a preconfigured state right out of the box. The Transaction attribute takes a value from the TransactionOption enumeration to specify how a component participates in a transaction. The values of the TransactionOption enumeration are listed in Table 7.11.

Table 7.11. Members of the TransactionOption Enumeration

Member

Description

Disabled

Specifies that the component's ability to participate with COM+ in managing transactions has been disabled. This setting is for compatibility reasons only.

NotSupported

Specifies that the component will never participate in transactions.

Required

Specifies that the component uses transactional resources such as databases and will participate in a transaction if one already exists; otherwise, a new transaction must be created.

RequiresNew

Specifies that the component requires a new transaction to be created even if a transaction already exists.

Supported

Specifies that the component will participate in a transaction if one already exists. This setting is mostly used by the components that do not themselves use any transactional resources.

For example, ComponentA might request a new transaction to be created by using the following code:

 <Transaction(TransactionOption.RequiresNew)> _ Public Class ComponentA     Inherits ServicedComponent    ... 

When a transaction is created, it is uniquely identified by a transaction ID. Several components can share a transaction; for example, when ComponentA calls a method on ComponentB , which has been defined as follows:

 <Transaction(TransactionOption.Supported)> _ Public Class ComponentA     Inherits ServicedComponent    ... 

Then, ComponentB shares the transaction started by ComponentA . In this case, the tasks accomplished by ComponentA and ComponentB belong to the same transaction. As a result, these tasks fail or succeed together.

EXAM TIP

JIT Activation Is Required with Automatic Transaction Processing To preserve the consistency of a transaction, a component must not carry state from one transaction to another. To enforce statelessness for all transactional components, COM+ uses JIT activation. JIT activation forces an object to deactivate and lose state before the object can be activated in another transaction.

For a component, if you apply the Transaction attribute and set its value to TransactionOption.Supported , TransactionOption.Required , or TransactionOption.RequiresNew , COM+ automatically sets the JustInTimeActivation attribute to true .


Context and Transaction

Each component that participates in a transaction has its own context. The context stores various flags that specify the state of an object. Two such flags are the done bit and the consistent bit. In addition to objects, the transaction itself also has a context. The context of a transaction maintains an abort bit. The purpose of the abort bit is to determine whether the transaction as a whole failed or succeeded. I have summarized these bits and their influence on the outcome of a transaction in Table 7.12.

Table 7.12. The Abort, Done, and Consistent Bits and Their Effects on the Transaction Outcome

Bit

Scope

Description

Effect on the Transaction Outcome

Abort

Entire transaction

This bit is also called the doomed bit. COM+ sets this bit to False when creating a new transaction. If this bit is set to True in a transaction lifetime, it cannot be changed back.

If the abort bit is set to True , the transaction is aborted.

Consistent

Each context

This bit is also called the happy bit. COM+ sets this bit to True when creating an object. A programmer can choose to set this bit to True or False , depending on the program logic to indicate that the object is either consistent or inconsistent.

If the consistent bit in any of the contexts is set to False , the transaction is aborted.

If the consistent bit in all the contexts is set to True , the transaction is committed.

Done

Each context

Each COM+ object that participates in a transaction must also support just-in-time activation and therefore must maintain a done bit. When a method call begins, the done bit is set to False . When a method call finishes, COM+ checks the status of the Done bit. If the bit is true, the active object is deactivated.

When exiting a method, if the done bit is set to True and the consistent bit is set to False , then the Abort bit is set to True .

The .NET enterprise services library provides the ContextUtil class to work with the context of an object. Table 7.13 shows those methods of the ContextUtil class, which influence the done bit and the consistent bit of an object.

Table 7.13. How the Methods of the ContextUtil Class Affect the Consistent Bit and the Done Bit

Method

Effect on Consistent Bit

Effect on Done Bit

DisableCommit()

False

False

EnableCommit()

True

False

SetAbort()

False

True

SetComplete()

True

True

How the Automatic Transaction Service Works

In this section, you learn how the COM+ automatic transaction service works in a distributed scenario. Consider a scenario as shown in Figure 7.30. You have an order processing application that is divided into four layers :

  • Sales representatives use a Windows application to enter the orders that they receive over the telephone.

  • The client Windows application interacts with the ordering application for order fulfillment. The ordering application works as a service provider and interacts with other applications to fulfill an order. The main objective of this application is to keep the client from knowing how an order is processed. This scheme gives you flexibility in changing processes at the server without making any changes to the client program.

  • The shipping application knows how to ship an order and the billing application knows how to bill customers. These applications can reside on the same computer as the ordering application, or they might reside on different computers. If the applications are on different computers, they can communicate with the ordering application using technologies such as remoting or XML Web services.

  • The shipping and the billing applications maintain their own set of data independent of each other. The databases themselves might reside on different servers.

Figure 7.30. Distributed processing environments like the one shown here are common in enterprises .

In this scenario, the need for transactions is clear. When a customer places an order, the order should be both billed and shipped as an integrated unit of work. Just billing the customer without shipping anything or vice versa is not what most organizations want to do.

Let us now see how the automatic transaction service works in this scenario. For the sake of simplicity, I assume that each of the ordering, billing, and shipping applications have just one component. The name of the component is same as the name of application.

Figure 7.31 shows how these components interact with each other to create a transaction. The process in Figure 7.31 is explained in the following steps:

  1. When the client instantiates the ordering component, an object context is created. The done bit is set to False , whereas the consistent bit is set to True . COM+ intercepts object invocation to check whether transaction services are needed. The ordering component has a Transaction attribute set to the TransactionOption.RequiresNew value. COM+ creates a new transaction and sets the abort bit to False . The ordering component is designated as the root object of the transaction. A root object coordinates with all other objects in a transaction. The client calls a method on the ordering component.

  2. When the ordering component instantiates the billing component, COM+ intercepts to check whether transaction services are needed. The billing component has a Transaction attribute set to the TransactionOption.Supported value. COM+ determines that the billing component can support the transaction started by ordering component and extends the scope of the transaction to cover the billing component. The context of the billing object is initialized with the consistent bit set to True and the done bit set to True .

    NOTE

    MSDTC and Two-phase Commit In a transaction, MSDTC works in two phases. The first phase is the prepare phase in which MSDTC interacts with resource managers for resources such as databases, message queues, and so on and asks them to record new and old values in a durable place. Based on the resource manager's feedback, MSDTC determines the success or failure of an operation.

    The second phase is the commit phase. In this phase, MSDTC asks all the individual resource managers to perform a commit operation on their resources. MSDTC collects the votes ; if all the commit operations are successful, MSDTC instructs all resource managers to make the changes permanent. If any of the commits failed, MSDTC instructs all the resource managers to roll back their operation based on the information that they collected in the prepare phase.

  3. The ordering component calls a method on the billing component. The billing object interacts with a SQL Server database to update a table of confirmed shipments. COM+ uses MSDTC to record any changes made to the database so that the changes can be rolled back at a later stage. If the update is successful, the consistent bit is set to True , but if there were any errors, the consistent bit is set to False . If the billing object wants to deactivate itself after the method call, it sets the done bit to True ; otherwise, the done bit remains set to False . If the done bit is True and the consistent bit is False , the abort bit of the transaction is set to True and the control transfers to step 6.

  4. When the method returns from the billing object, COM+ intercepts the call and records the status of the consistent bit. If the done bit is True , the billing object is deactivated.

  5. The control comes back to the ordering object. The ordering component now repeats steps 24 to instantiate the shipping component and invoke a method on its object.

  6. The control comes to the ordering object. COM+ checks the status of the abort bit. If the abort bit is True , the entire transaction is aborted and COM+ requests DTC to roll back any changes that were made to the databases. If the abort bit is False , the status of all the consistent bits is checked. If any of these bits is False , the transaction is aborted and rollback is performed. If all the consistent bits are True , COM+ requests DTC to finally commit all the changes to the databases.

  7. Finally, the root object sets its done bit to True and returns from the method that was invoked by the client. COM+ intercepts the call to deactivate the root object and destroys the transaction. The control is transferred to the client.

Figure 7.31. You can use serviced components with automatic transactions to manage distributed application data.

In the preceding steps, if you programmatically want to control the success or failure of an operation, you can do so by calling the methods of the ContextUtil class (see Table 7.5). However, a common choice is to apply the AutoComplete attribute on the method call. This attribute automatically calls ContextUtil.SetComplete() if the methods complete successfully. Otherwise, ContextUtil.SetAbort() is called to abort the transaction.

Using the Automatic Transaction Service for Distributed Transactions

Now that you are familiar with how transactions work, it's time to write a program that makes use of automatic transaction services in a distributed application. In this section, I'll use the distributed ordering application discussed previously and write a shipping component, a billing component, an ordering component, and a client application.

Step By Step 7.15 shows how to create a billing component that supports transactions and updates a database table with billing records.

STEP BY STEP

7.15 Using Distributed Transactions: Creating a Shipping Component

  1. Create a new table named Shipping in the Northwind database with the structure listed as shown in Table 7.14.

    Table 7.14. Design of the Shipping Table

    Column Name

    Data Type

    Length

    Allow Nulls

    Identity

    ShippingID

    Int

    4

    No

    Yes

    CustomerID

    Nchar

    5

    No

    No

    ProductID

    Int

    4

    No

    No

    DateTime

    Datetime

    8

    No

    No

  2. Add a new Visual Basic .NET Class library named StepByStep7-15 to the solution.

  3. In the Solution Explorer, right-click project StepByStep7-15 and select Add Reference from the context menu to add a reference to the System.EnterpriseServices component.

  4. In the Solution Explorer, rename the default Class1.vb to Shipping.vb .

  5. Open Shipping.vb and replace the code with the following code, changing the GUID in the Guid attribute to a newly-generated GUID:

     Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Data.SqlTypes Imports System.EnterpriseServices Imports System.Runtime.InteropServices Public Interface IShipping     Sub ShipItem(ByVal customerID As String, _      ByVal productID As Integer) End Interface <Transaction(TransactionOption.Supported), _  ClassInterface(ClassInterfaceType.None), _  Guid("5247F524-9AB6-4b37-8D63-E5BA2D60DC2E")> _ Public Class Shipping     Inherits ServicedComponent     Implements IShipping     Dim sqlconn As SqlConnection     Public Sub New()         sqlconn = New SqlConnection(_           "data source=(local);" & _           "initial catalog=Northwind;" & _            "integrated security=SSPI")     End Sub     <AutoComplete(True)> _     Public Sub ShipItem(ByVal customerID As String, _      ByVal productID As Integer) _      Implements IShipping.ShipItem         Dim sqlCmd As SqlCommand = New SqlCommand()         Dim dt As SqlDateTime = _          New SqlDateTime(DateTime.Now)         sqlCmd.CommandText = String.Format(_          "insert into Shipping (CustomerID, " & _          "ProductID, DateTime) values " & _          "('{0}', {1}, '{2}')", _          customerID, productID, dt)         sqlCmd.Connection = sqlconn         sqlconn.Open()         sqlCmd.ExecuteNonQuery()     End Sub End Class 
  6. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  7. Add the following assembly level attributes to the AssemblyInfo.vb file:

     <Assembly: ApplicationName("Shipping Application")> <Assembly: Description("Ship Orders")> <Assembly: ApplicationActivation(_     ActivationOption.Server)> 
  8. Change the AssemblyVersion and AssemblyKeyFile attributes in the AssemblyInfo.vb file as shown here:

     <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  9. Build the project. A StepByStep7-15.dll is generated, and a strong name is assigned to the file based on the specified key file.

  10. Launch the Visual Studio .NET command prompt and change the directory to the folder where the DLL file generated in step 9 resides. Issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs StepByStep7-15.dll 
  11. In the command prompt, issue the following command to install the assembly to the GAC:

     gacutil /i StepByStep7-15.dll 

Note that in Step By Step 7.15, I used the TransactionOption.Supported option because the shipping component is not invoked on its own. Instead, this component is invoked by the ordering application as part of the order fulfillment process, and so will join the preexisting transaction.

Step By Step 7.16 shows how to create a billing component that supports transactions and updates a database table with billing records.

STEP BY STEP

7.16 Using Distributed Transactions: Creating a Billing Component

  1. Create a new table named Billing in the Northwind database with the structure listed as shown in Table 7.15.

    Table 7.15. Design of the Billing Table

    Column Name

    Data Type

    Length

    Allow Nulls

    Identity

    BillingID

    Int

    4

    No

    Yes

    CustomerID

    Nchar

    5

    No

    No

    ProductID

    Int

    4

    No

    No

    DateTime

    Datetime

    8

    No

    No

  2. Add a new Visual Basic .NET Class library named StepByStep7-16 to the solution.

  3. In the Solution Explorer, right-click project StepByStep7-16 and select Add Reference from the context menu to add references to the System.EnterpriseServices component.

  4. In the Solution Explorer, rename the default Class1.vb to Billing.vb .

  5. Open Billing.vb and replace the code with the following code, changing the GUID in the Guid attribute to a newly generated GUID:

     Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Data.SqlTypes Imports System.EnterpriseServices Imports System.Runtime.InteropServices Public Interface IBilling     Sub BillCustomer(ByVal customerID As String, _      ByVal productID As Integer) End Interface <Transaction(TransactionOption.Supported), _  ClassInterface(ClassInterfaceType.None), _  Guid("4EAFC8F1-F9E3-4a44-AA69-88F5E8AF768F")> _ Public Class Billing     Inherits ServicedComponent     Implements IBilling     Dim sqlconn As SqlConnection     Public Sub New()         sqlconn = New SqlConnection(_          "data source=(local);" & _          "initial catalog=Northwind;" & _          "integrated security=SSPI")     End Sub     <AutoComplete(True)> _     Public Sub BillCustomer(_      ByVal customerID As String, _      ByVal productID As Integer) _      Implements IBilling.BillCustomer         Dim sqlCmd As SqlCommand = New SqlCommand()         Dim dt As SqlDateTime = _          New SqlDateTime(DateTime.Now)         sqlCmd.CommandText = String.Format(_          "insert into Billing (CustomerID, " & _          "ProductID, DateTime) values " & _          "('{0}', {1}, '{2}')", _          customerID, productID, dt)         sqlCmd.Connection = sqlconn         sqlconn.Open()         sqlCmd.ExecuteNonQuery()     End Sub End Class 
  6. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  7. Add the following assembly level attributes to the AssemblyInfo.vb file:

     <Assembly: ApplicationName("Billing Application")> <Assembly: Description("Bill Customers")> <Assembly: ApplicationActivation(_  ActivationOption.Server)> 
  8. Change the AssemblyVersion and AssemblyKeyFile attribute in the AssemblyInfo.vb file as shown here:

     <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  9. Build the project. A StepByStep7-16.dll is generated, and a strong name is assigned to the file based on the specified key file.

  10. Launch Visual Studio .NET command prompt and change the directory to the folder where the DLL file generated in step 9 resides. Issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs StepByStep7-16.dll 
  11. In the command prompt, issue the following command to install the assembly to the GAC:

     gacutil /i StepByStep7-16.dll 

Note that in Step By Step 7.16, I used the TransactionOption.Supported option because the Billing component is not invoked on its own. Instead, this component is invoked by the ordering component as part of the order fulfillment process. Step By Step 7.17 shows how to create the ordering component.

STEP BY STEP

7.17 Using Distributed Transactions: Creating an Ordering Component

  1. Add a new Visual Basic .NET Class library named StepByStep7-17 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-17 and select Add Reference from the context menu to add references to the System.EnterpriseServices component and projects StepByStep7-15 and StepByStep7-16 .

  3. In the Solution Explorer, rename the default Class1.vb to Ordering.vb .

  4. Open Ordering.vb and replace the code with the following code, changing the GUID in the Guid attribute to a newly generated GUID:

     Imports System Imports System.EnterpriseServices Imports System.Runtime.InteropServices Imports StepByStep7_15 Imports StepByStep7_16 Public Interface IOrdering     Sub PlaceOrder(ByVal customerID As String, _      ByVal productiD As Integer) End Interface <Transaction(TransactionOption.RequiresNew), _  ClassInterface(ClassInterfaceType.None), _  Guid("F4BB597A-2622-4cc2-A074-364927E54992")> _ Public Class Ordering     Inherits ServicedComponent     Implements IOrdering     Public Sub New()     End Sub     <AutoComplete(True)> _      Public Sub PlaceOrder(_      ByVal customerID As String, _       ByVal productID As Integer) _       Implements IOrdering.PlaceOrder         Dim b As Billing = New Billing()         b.BillCustomer(_          customerID, productID)         Dim s As Shipping = New Shipping()         s.ShipItem(_          customerID, productID)     End Sub End Class 
  5. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  6. Add the following assembly level attributes to the AssemblyInfo.vb file:

     <Assembly: ApplicationName("Ordering Application")> <Assembly: Description("Places an order")> <Assembly: ApplicationActivation(_  ActivationOption.Server)> 
  7. Change the AssemblyVersion and AssemblyKeyFile attributes in the AssemblyInfo.vb file as shown here:

     <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  8. Build the project. A StepByStep7-17.dll is generated, and a strong name is assigned to the file based on the specified key file.

  9. Launch the Visual Studio .NET command prompt and change the directory to the folder where the DLL file generated in step 8 resides. Issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs StepByStep7-17.dll 
  10. In the command prompt, issue the following command to install the assembly to the GAC:

     gacutil /i StepByStep7-17.dll 

The ordering component needs to work as a root object for a transaction. For this reason, the Transaction attribute in Step By Step 7.17 uses the value TransactionOption.RequiresNew .

Now you have all the server-side components ready. In Step By Step 7.18, I'll create a client application that calls the ordering component. I have created the client application as a Windows application; however, creating the client program as a Web application is not much different.

STEP BY STEP

7.18 Using Distributed Transactions: Creating a Client Order Form

  1. Add a new Visual Basic .NET Windows application named StepByStep7-18 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-18 and select Add Reference from the context menu to add references to the System.EnterpriseServices and StepByStep7-17 components.

  3. In the Solution Explorer, rename the default Form1.vb to OrderForm.vb . Open the form in code view and change all occurrences of Form1 to refer to OrderForm instead.

  4. Add the following Imports directives:

     Imports System.Data.SqlClient Imports StepByStep7_17 
  5. Place two Label controls, two ComboBox controls ( cboCustomers and cboProducts ), and one Button control ( btnPlaceOrder ) on the form. Arrange the controls as shown in Figure 7.32.

    Figure 7.32. The design of a form that uses the Ordering serviced component.

  6. Open Server Explorer and drag the Customers and Products tables from the Northwind data connection node to the form. A SqlConnection object and two SqlDataAdapter objects will be created on the form.

  7. Right-click the first SqlDataAdapter object and select Generate DataSet from the context menu. In the Generate DataSet dialog box, choose the New radio button and name the new DataSet dsOrders . Select the Customers table and click OK.

  8. Right-click the second SqlDataAdapter object and select Generate DataSet from the context menu. In the Generate DataSet dialog box, choose the existing DataSet dsOrders . Select the Products table and click OK.

  9. Set the DataSource property to dsOrders1.Customers , DisplayMember to ContactName , ValueMember to CustomerID , and DropDownStyle to DropDownList for the cboCustomers combo box. Set the DataSource property to dsOrders1.Products , DisplayMember to ProductName , ValueMember to ProductID , and DropDownStyle to DropDownList for the cboProducts combo box.

  10. Double-click the form and add the following code in the Load event handler:

     Private Sub OrderForm_Load(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles MyBase.Load     SqlDataAdapter1.Fill(DsOrders1, "Customers")     SqlDataAdapter2.Fill(DsOrders1, "Products") End Sub 
  11. Double-click the Button control and add the following code to the Click event handler:

     Private Sub btnPlaceOrder_Click(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) _  Handles btnPlaceOrder.Click     Try         Dim O As Ordering = New Ordering()         O.PlaceOrder(_          cboCustomers.SelectedValue.ToString(), _          CInt(cboProducts.SelectedValue))         MessageBox.Show("Order placed successfully")     Catch ex As Exception         MessageBox.Show(ex.Message)     End Try End Sub 
  12. Build the project. Set the project StepByStep7-18 as the startup project.

  13. Run the solution. Select a Customer and Product from the customers and products combo box and click the Place Order button. You see a message box confirming that the order is successfully placed. You can retrieve records from the Billing and the Shipping tables to find that one new record is added to each of them. This is the case when everything was as expected and the transaction was successfully committed.

  14. Now consider a case in which the billing component is working fine, but there is a problem with the shipping component. To simulate this, open the Component Service administrative tool, right-click on the Shipping application, and select Delete from the context menu. With COM+ 1.5 (Windows XP and Windows .NET), there's a less destructive way: You can select Disable from the context menu. Now run the client program. You get an error message showing that the Shipping component is not available. However, if you retrieve the data from the Billing and Shipping tables, you note that neither the Billing nor Shipping table contains the record for sales that presented you with an error. In case of error, the transaction was automatically rolled back.

The client application created in Step By Step 7.18 does not itself take part in the transaction, but various server components that the client application uses do take part in a transaction. Automatic transaction processing increases the reliability of applications without putting a lot of pain in programming.

In the last step of Step By Step 7.18, I simulated a scenario that an enterprise application should always be ready to deal with. This is a scenario in which one or more of an application's components are unavailable. When the Shipping component was unavailable, you were not able to record orders in the system. For many applications, availability is the number one priority. Availability becomes a major challenge when applications are distributed because the points of failure are now increased and some are out of your control.

In the next section, I'll discuss how another COM+ servicequeued components solves the problem of availability.

Queued Components

From the perspective of a client, a q ueued component is a serviced component that can be invoked and executed asynchronously. The queued components are based on the Microsoft Message Queuing (MSMQ) technology, which is part of the Windows operating system.

How Queued Components Work

Communication between a client and a queued component involves four basic components between the client and server, as shown in Figure 7.33. These components are

  • Recorder The recorder kicks in when a client makes a call to the queued component. The recorder records the call, packages it as a message, and stores the message in the message queue.

  • Queue The queue is a repository of messages. Each COM+ application has a set of queues assigned to it. There is one primary queue, five retry queues, and one dead-letter queue. When the message arrives from the recorder, the message waits in the primary queue to be picked up by the queued component. If there is an error in processing, the message is sent to the first retry queue; if the message processing fails in the first retry, the message is moved to the second retry queue; and so on. The retry queues differ from each other by the frequency with which they retry a message. The first retry queue is the most frequent, whereas the fifth one is slowest. If there is an error-processing message in the fifth queue, the message is finally moved to a dead-letter queue where no further retries are made. Occasionally, you might want to check the dead-letter queue and custom process any failed message.

  • Listener The listener's role is to poll the queue for incoming messages and when there is one to pass the message to the player.

  • Player The player unpacks a message and invokes the methods that were recorded by the client on the queued component.

Figure 7.33. With queued components, messages are recorded in a message queue for later retrieval.

Creating a Queued Component

Creating a queued component is just like creating any other serviced component. To configure a component to work as a queued component, you need to apply the following two attributes:

  • ApplicationQueuing Attribute You apply this attribute at the assembly level to enable queuing support for an application. If a client will call queued components, the QueueListenerEnabled property must be set to true.

     <Assembly: ApplicationQueuing(_  Enabled:=true, QueueListenerEnabled:=true)> 
  • InterfaceQueueing Attribute You apply this attribute at the component level to specify the interface through which COM+ allows the calls on the component to be recorded and stored in the queue. For example,

     <InterfaceQueuing(Enabled:=true, _  Interface:="IOrdering")> 

The execution lifetime of a queued component and the client application might be different. Therefore, when creating a queued component, you must implement the following guidelines:

  • Methods should not return any value or reference parameters.

  • All calls made by a client should be self sufficient. The queued component has no way to generate a callback to the client program if more information is needed.

  • Methods should not throw any application-specific exceptions because the client might not be available to respond to the exceptions.

Step By Step 7.19 shows how to use a queued component that is capable of listening to a message queue. When a message arrives, the component receives orders and calls other components to process them.

STEP BY STEP

7.19 Using Queued Components: Creating an Ordering Component

  1. Add a new Visual Basic .NET Class library named StepByStep7-19 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-19 and select Add Reference from the context menu to add references to the System.EnterpriseServices component and projects StepByStep7-15 and StepByStep7-16 .

  3. In the Solution Explorer, copy the Ordering.vb file from the StepByStep7-17 project to the current project. Delete the default Class1.vb .

  4. Apply the InterfaceQueuing attribute to the Ordering class and change the GUID in the Guid attribute:

     <InterfaceQueuing(_  Enabled:=True, Interface:="IOrdering"), _  Transaction(TransactionOption.RequiresNew), _  ClassInterface(ClassInterfaceType.None), _  Guid("C8666D55-AF3F-4c31-B00A-372C39242174")> _ Public Class Ordering   ... 
  5. Open the AssemblyInfo.vb file in the project and add the following Imports directive:

     Imports System.EnterpriseServices 
  6. Add the following assembly level attributes to the AssemblyInfo.vb file:

     <Assembly: ApplicationQueuing(_  Enabled:=True, QueueListenerEnabled:=True)> <Assembly: ApplicationName(_ "Ordering Application With Queued Components")> <Assembly: Description("Places an order")> <Assembly: ApplicationActivation(_  ActivationOption.Server)> 

    Note that you have to add an ApplicationQueuing attribute to enable queuing for the application.

  7. Change the AssemblyVersion and AssemblyKeyFile attributes in the AssemblyInfo.cs file as shown here:

     <Assembly: AssemblyVersion("1.0.0")> <Assembly: AssemblyKeyFile("..\..\..310.snk")> 
  8. Build the project. A StepByStep7-19.dll is generated, and a strong name is assigned to the file based on the specified key file.

  9. Launch the Visual Studio .NET command prompt and change the directory to the folder where the DLL file generated in step 8 resides. Issue the following command to install the service component assembly to the COM+ Catalog:

     regsvcs StepByStep7-19.dll 
  10. In the command prompt, issue the following command to install the assembly to the GAC:

     gacutil /i StepByStep7-19.dll 

After you have registered a serviced component in the COM+ catalog, as you did in Step By Step 7.19, access the properties window of the COM+ application via the Component Services administrative tool. In the Queuing tab, you see that the options for queuing and listening are already configured, as shown in Figure 7.34.

Figure 7.34. You can configure a COM+ application to support queuing through the Component services administrative tool.

You can also configure an interface to support queuing through its property window, as shown in Figure 7.35.

Figure 7.35. You can mark a component's interface to support queuing.

Creating a Client for a Queued Component

One of the ways you create an object in the client program is by using the new operator. However, you don't want to use that when working with queue because with a queued component, your objective is not to create an instance of an object. Instead, you need a way in which you can record a message for the client and store it in a queue so that the server object can read that message when possible.

Recording a message for a queued component is generally a three-step process:

  1. Call the Marshal.BindToMoniker() method and pass it a moniker string that corresponds to the interface of the queued component. A moniker string is formed by preceding the full type name (qualified with namespace) with the string "queue:/new:" . For example,

     Marshal.BindToMoniker("queue:/new:StepByStep7_19. Ordering") 

    The Marshal.BindToMoniker() method returns a reference to the interface identified by the given moniker string.

  2. Use the interface reference obtained in step 1 to execute methods on the queued component. These methods are not executed immediately; instead, they will be recorded and placed in the message queue.

  3. When done calling methods, call the Marshal.ReleaseComObject() method to release the reference of the interface reference obtained in step 1.

Step By Step 7.20 shows how to create a client program that uses the Marshal.BindToMoniker() method to record messages for a queued component.

STEP BY STEP

7.20 Using Queued Components: Creating a Client Order Form

  1. Add a new Visual Basic .NET Windows application named StepByStep7-20 to the solution.

  2. In the Solution Explorer, right-click project StepByStep7-20 and select Add Reference from the context menu to add references to the System.EnterpriseServices and StepByStep7-19 components.

  3. In the Solution Explorer, rename the default Form1.vb to OrderForm.vb . Open the form in code view and change all occurrences of Form1 to refer to OrderForm instead.

  4. Add the following Imports directives:

     Imports System.Data.SqlClient Imports System.Runtime.InteropServices Imports StepByStep7_19 
  5. Place two Label controls, two ComboBox controls ( cboCustomers and cboProducts ), and one Button control ( btnPlaceOrder ) on the form. You can use the design of the form in Figure 7.32.

  6. Open Server Explorer and drag the Customers and Products table from the Northwind data connection node to the form. A SqlConnection object and two SqlDataAdapter objects are created on the form.

  7. Right-click the first SqlDataAdapter object and select Generate DataSet from the context menu. In the Generate DataSet dialog box, choose the New radio button and name the new DataSet dsOrders . Select the Customers table and click OK.

  8. Right-click the second SqlDataAdapter object and select Generate DataSet from the context menu. In the Generate DataSet dialog box, choose the existing DataSet dsOrders . Select the Products table and click OK.

  9. Set the DataSource property to dsOrders1.Customers , DisplayMember to ContactName , ValueMember to CustomerID , and DropDownStyle to DropDownList for the cboCustomers combo box. Set the DataSource property to dsOrders1.Products , DisplayMember to ProductName , ValueMember to ProductID , and DropDownStyle to DropDownList for the cboProducts combo box.

  10. Double-click the form and add the following code in the Load event handler:

     Private Sub OrderForm_Load(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles MyBase.Load     SqlDataAdapter1.Fill(DsOrders1, "Customers")     SqlDataAdapter2.Fill(DsOrders1, "Products") End Sub 
  11. Double-click the Button control and add the following code to the Click event handler:

     Private Sub btnPlaceOrder_Click(_  ByVal sender As System.Object, _  ByVal e As System.EventArgs) _  Handles btnPlaceOrder.Click     Dim ord As IOrdering = Nothing     Try         ord = CType(Marshal.BindToMoniker(_          "queue:/new:StepByStep7_19.Ordering"), _          IOrdering)         ord.PlaceOrder(_          cboCustomers.SelectedValue.ToString(), _          CInt(cboProducts.SelectedValue))         MessageBox.Show("Order placed in the queue")     Catch ex As Exception         MessageBox.Show(ex.Message)     Finally         Marshal.ReleaseComObject(ord)     End Try End Sub 
  12. Set the project StepByStep7-20 as the startup project. Set OrderForm as the startup object for the project. Build the project.

  13. Run the solution. Select a Customer and Product from the customers and products combo box and click the Place Order button. You see that because both billing and shipping components are available, the message is immediately processed.

  14. Try disabling or uninstalling either the billing or shipping components or the client application again. Note that the records for a sale are not immediately created in the database tables. Instead, this information is now part of the message queue, waiting for the serviced component to be started. You can view the message queue for a computer via the Computer Management tool available in the Administrative Tools section of the Windows Control Panel.

In Step By Step 7.20, you learned how to record messages in a queue for a serviced component. You also experimented with making an application available irrespective of failure of one or more components.

REVIEW BREAK

  • A transaction is a series of operations performed as a single unit. A transaction is successful only when all of the operations in that transaction succeed.

  • The outcome of a transaction depends on the status of the abort bit that is maintained in the context of a transaction and the consistent and done bits, which are maintained in each context.

  • You can use the methods of the ContextUtil class to change the status of the consistent and done bits programmatically.

  • The AutoComplete attribute automatically calls the ContextUtil.SetComplete() method if a method completes successfully; otherwise, ContextUtil.SetAbort() is called to abort the transaction.

  • A queued component is a serviced component that can be invoked and executed asynchronously. With the help of a queued component, you can make your applications available even when some of their components are not.


   
Top


MCAD. MCSD Training Guide (Exam 70-310. Developing XML Web Services and Server Components with Visual Basic. NET and the. NET Framework)
MCAD/MCSD Training Guide (70-310): Developing XML Web Services and Server Components with Visual Basic(R) .NET and the .NET Framework
ISBN: 0789728206
EAN: 2147483647
Year: 2002
Pages: 166

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