This sample application simulates a simplified scenario that writes new orders to the Northwind sample database. As shown in Figure 44-7, multiple components are used with the COM+ application. The class OrderControl is called from the client application to create new orders, OrderControl uses the OrderData component OrderData has the responsibility of creating a new entry in the Order table of the Northwind database. The OrderData component uses the OrderLineData component to write Order Detail entries to the database. Both OrderData and OrderLineDa ta must participate in the same transaction.
Start by creating a C# Component library with the name Northwind Component. Sign the assembly with a key file, and define the Enterprise Services application attributes as shown in the following code:
Next add the entity classes Order and OrderLine that represent the columns in the Northwind database tables Order and Order Details. Entity classes are just data holders representing the data that is important for the application domain – in that case for doing orders. The class Order has a static method Create() that creates and returns a new instance of the class Order, and initializes this instance with th~arguments passed to this method. Also, the class Order has some read-only properties Orderld, Customerld, OrderData, ShipAddress, ShipCi ty, and ShipCountry. The value of the Orderld property is not known at creation time of-the class Order, but because the Order table in the Northwind database has an auto-increment attribute, the value is just known after the order is written to the database. The method SetOrderld() is used to set the corresponding ID after the order has been written to the database. Because this method is called by a class inside the same assembly, the access level of this method is set to internal. The method AddOrderLine() adds order details to the order:
The second entity class is OrderLine. OrderLine has a static Create() method similar to the one of the Order class. Other than that, the class only has some properties for the fields productld, unit Price and quantity:
The OrderControl Component
The class OrderControl represents a simple business services component. In this example, just one method, NewOrder(),is defined in the interface IOrderControl. The implementation of NewOrder() does nothing more than instantiate a new instance of the data services component OrderData and call the method Insert () to write an Order object to the database. In a more complex scenario, this method could be extended to write a log entry to a database or to invoke a queued component to send the Order object to a message queue:
The OrderData Component
The OrderData class is responsible for writing the values of Order objects to the database. The interface IOrderUpdate defines the Insert() method, You can extend this interface to also support an Update() method where an existing entry in the database is updated:
The class OrderData has the attribute [Transaction] with the value TransactionOption. Required applied. This means that the component will run in a transaction in any case. Either a transaction is created by the caller and OrderData uses the same transaction, or a new transaction is created. Here a new transaction will be created because the calling component OrderControl doesn’t have a transaction.
With serviced components, you can use only default constructors. However, you can use the Component Services Explorer to configure a construction string that is sent to a component (see Figure 44-8). Selecting the Activation tab of the component configuration enables you to change the construction string. The option “Enable object construction” is turned on when the attribute [ConstructionEnabled) is set, as it is with the class OrderData. The Default property of the [ConstructionEnabled) attribute defines the default connection string shown in the Activation settings after registration of the assembly. Setting this attribute also requires you to overload the method Construct () from the base class ServicedComponent. This method is called by the COM+ runtime at object instantiation, and the construction string is passed as an argument. The construction string is set to the variable connection String, which is used later to connect to the database:
The method. Insert () is at the heart of the component. Here, you use AOO.NET to write the Order
object to the database. (AOO.NET is discussed in more detail in Chapter 26, “Data Access.”) In this
example, you create a SqlConnection object where the connection string that was set with the
Construct () m…ethod is used to initialize the object.
The attribute [AutoComplete ( ) ] is applied to the method to use automatic transaction handling as
The method connection CreateCommand() creates a SqlCommand object where the CommandText property is set to a SQL INSERT statement to add a new record to the Orders table. The method ExecuteNonQuery() executes the SQL statement:
Because OrderId isdefined as an auto-increment value in the database, and this ID is needed for writing the Order Details to the database, OrderId is read by using @@IDENTITY. Then it isset to the Order object by calling the method Set OrderId():
After the order is written to the database, all order lines of the order are written using the OrderLineData component:
Finally, regardless of whether the code in the try block was successful or an exception occurred, the connection is closed:
The OrderLineData Component
The OrderLineData component is implemented similarly to the OrderData component. You use the attribute (ConstructionEnabled) to define the database connection string:
With the Insert () method of the OrderLineData class in this example, the [AutoComplete] attribute isn’t used to demonstrate a different way to define the transaction outcome. It shows how to set the consistent and done bits with the Context Util class instead. The method SetComplete() is called at the end of the method, depending on whether inserting the data in the database was successful. If there is an error where an exception is thrown, the method SetAbort() sets the consistent bit to false instead, so that the transaction is undone along with all components participating in the transaction:
Having built the component, you can create a client application. For testing purposes, a console . application serves the purpose. After referencing the assembly Northwind Component and the assembly System. Enterprise Services, you can create a new order with the static method Order .Create(). order .AddOrderLine() adds an order line to the order. OrderLine . Create() accepts product IDs, the price, and quantity to create an order line. In a real application, it would be useful to add a Product class instead of using product IDs, but the purpose of this example is to demonstrate transactions in general.
Finally, the serviced component class OrderControl is created to invoke the method NewOrder():
You can try to write products that don’t exist to the OrderLine (using a product ID that is not listed in the table Products). In this case, the transaction will be aborted, and no data will be written to the database.
While a transaction is active, you can see the transaction in the Component Services Explorer by selecting Distributed Transaction Coordinator in the tree view (see Figure 44-9).
You might have to add a sleep time to the Insert() method of the OrderData class to see the live transaction otherwise, the transaction might be completed too fast to display .
If you are debugging the serviced component while it is running inside a transaction. be aware that the default transaction timeout is 60 seconds for serviced components. You can change the default for the complete system in the Component Services Explorer by clicking My Computer. selecting Action I¢ Properties, and opening the Options tab. Instead of changing the value for the complete system. the transaction timeout can also be configured on a component-by-component level with the Transaction options of the component.