Sample Application


This sample application simulates a simplified scenario that writes new orders to the Northwind sample database. As shown in Figure 30-7, multiple components are used with the COM+ application. The class OderControl 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 OrderLineData must participate in the same transaction.

image from book
Figure 30-7

Start by creating a C# Component library with the name NorthwindComponent. Sign the assembly with a keyfile, and define the Enterprise Services application attributes as shown in the following code:

 [assembly: ApplicationName("Wrox.NorthwindDemo")] [assembly: ApplicationActivation(ActivationOption.Library)] [assembly: ApplicationAccessControl(false)] 

Entity Classes

Next add some entity classes that represent the columns in the Northwind database tables Order and Order Details. The class Order has a static method Create() that creates and returns a new instance of the class Order, and initializes this instance with the arguments passed to this method. Also, the class Order has some read-only properties to access the fields oderId, customerId, orderData, shipAddress, shipCity, and shipCountry. OrderId is not known at creation time of the class Order, but because the Order table in the Northwind database has an auto-increment attribute, OrderId is just known after the order is written to the database. The method SetOrderId() 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:

 using System; using System.Collections.Generic; namespace Wrox.ProCSharp.EnterpriseServices { [Serializable] public class Order { public static Order Create(string customerId, DateTime orderDate,  string shipAddress, string shipCity, string shipCountry) { Order o = new Order(); o.customerId = customerId; o.orderDate = orderDate; o.shipAddress = shipAddress; o.shipCity = shipCity; o.shipCountry = shipCountry; return o; } public Order() { } internal void SetOrderId(int orderId) { this.orderId = orderId; } public void AddOrderLine(OrderLine orderLine) { orderLines.Add(orderLine); } private int orderId; private string customerId; private DateTime orderDate; private string shipAddress; private string shipCity; private string shipCountry; private List<OrderLine> orderLines = new List<OrderLine>(); public int OrderId { get { return orderId; } } public string CustomerId { get { return customerId; } } public DateTime OrderDate { get { return orderDate; } } public string ShipAddress { get { return shipAddress; } } public string ShipCity { get { return shipCity; } } public string ShipCountry { get { return shipCountry; } } public OrderLine[] OrderLines { get { OrderLine[] ol = new OrderLine[orderLines.Count]; orderLines.CopyTo(ol); return ol; } } } } 

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 productId, unitPrice, and quantity:

 using System; namespace Wrox.ProCSharp.EnterpriseServices { [Serializable] public class OrderLine { public static OrderLine Create(int productId, float unitPrice, int quantity) { OrderLine detail = new OrderLine(); detail.productId = productId; detail.unitPrice = unitPrice; detail.quantity = quantity; return detail; } public OrderLine() { } private int productId; private float unitPrice; private int quantity; public int ProductId { get { return productId; } set { productId = value; } } public float UnitPrice { get { return unitPrice; } set { unitPrice = value; } } public int Quantity { get { return quantity; } set { quantity = value; } } } } 

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:

 using System; using System.EnterpriseServices; using System.Data; using System.Data.SqlClient; using System.Collections.Generic; namespace Wrox.ProCSharp.EnterpriseServices { public interface IOrderControl { void NewOrder(Order order); } [Transaction(TransactionOption.Supported)] [EventTrackingEnabled(true)] public class OrderControl : ServicedComponent, IOrderControl { [AutoComplete()] public void NewOrder(Order order) { OrderData data = new OrderData(); data.Insert(order); } } } 

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 gets updated:

 using System; using System.EnterpriseServices; using System.Data; using System.Data.SqlClient; namespace Wrox.ProCSharp.EnterpriseServices { public interface IOrderUpdate { void Insert(Order order); } 

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 only use default constructors. However, you can use the Component Services explorer to configure a construction string that is sent to a component (see Figure 30-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 connectionString that is used later to connect to the database:

image from book
Figure 30-8

  [Transaction(TransactionOption.Required)] [EventTrackingEnabled(true)] [ConstructionEnabled(true, Default="server=localhost; database=northwind;trusted_connection=true")] public class OrderData : ServicedComponent, IOrderUpdate { private string connectionString = null; protected override void Construct(string s) { connectionString = s; } 

The method Insert() is at the heart of the component. Here you use ADO.NET to write the Order object to the database. (ADO.NET is discussed in more detail in Chapter 19, "Data Access with .NET.") For this example, you create a SqlConnection object where the connection string that was set with the Construct() method is used to initialize the object.

The attribute [AutoComplete()] is applied to the method to get automatic transaction handling as discussed earlier:

 [AutoComplete()] public void Insert(Order order) { SqlConnection connection = new SqlConnection(connectionString); 

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:

 try { SqlCommand command = connection.CreateCommand(); command.CommandText = "INSERT INTO Orders (CustomerId, OrderDate, " + "ShipAddress, ShipCity, ShipCountry)" + "VALUES(@CustomerId, @OrderDate, @ShipAddress, @ShipCity, " + "@ShipCountry)"; command.Parameters.AddWithValue("@CustomerId", order.CustomerId); command.Parameters.AddWithValue("@OrderDate", order.OrderDate); command.Parameters.AddWithValue("@ShipAddress", order.ShipAddress); command.Parameters.AddWithValue("@ShipCity", order.ShipCity); command.Parameters.AddWithValue("@ShipCountry", order.ShipCountry); connection.Open(); command.ExecuteNonQuery(); 

Because OrderId is defined 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 is set to the Order object by calling the method SetOrderId():

 command.CommandText = "SELECT @@IDENTITY AS 'Identity'";  object identity = command.ExecuteScalar(); order.SetOrderId(Convert.ToInt32(identity)); 

After the order is written to the database, all order lines of the order are written by using the OrderLineData component:

 OrderLineData updateOrderLine = new OrderLineData(); foreach (OrderLine orderLine in order.OrderLines) { updateOrderLine.Insert(order.OrderId, orderLine); } } 

Finally, regardless of whether the code in the try block was successful or an exception occurred, the connection is closed:

 finally { connection.Close(); } } } } 

The OrderLineData Component

The OrderLineData component is implemented similar to the OrderData component. You use the attribute [ConstructionEnabled] to define the database connection string:

 using System; using System.EnterpriseServices; using System.Data; using System.Data.SqlClient; namespace Wrox.ProCSharp.EnterpriseServices { public interface IOrderLineUpdate { void Insert(int orderId, OrderLine orderDetail); } [Transaction(TransactionOption.Required)] [EventTrackingEnabled(true)] [ConstructionEnabled(true, Default="server=localhost;database=northwind;" + "trusted_connection=true")] public class OrderLineData : ServicedComponent, IOrderLineUpdate { private string connectionString = null; protected override void Construct(string s) { connectionString = s; } 

With the Insert() method of the OrderLineData class in this example, the [AutoComplete] attributeisn't used to demonstrate a different way to define the transaction outcome. It shows how to set the consistent and done bit with the ContextUtil class instead. The method SetComplete() is called at the end of the method, depending on whether inserting the data in the database was successful. In case of an error where an exception is thrown, the method SetAbort() sets the consistent bit to false instead, so that the transaction is undone with all components participating in the transaction:

 public void Insert(int orderId, OrderLine orderDetail) { SqlConnection connection = new SqlConnection(connectionString); try { SqlCommand command = connection.CreateCommand(); command.CommandText = "INSERT INTO [Order Details] (OrderId, " + "ProductId, UnitPrice, Quantity)" + "VALUES(@OrderId, @ProductId, @UnitPrice, @Quantity)"; command.Parameters.AddWithValue("@OrderId", orderId); command.Parameters.AddWithValue("@ProductId", orderDetail.ProductId); command.Parameters.AddWithValue("@UnitPrice", orderDetail.UnitPrice); command.Parameters.AddWithValue("@Quantity", orderDetail.Quantity); connection.Open(); command.ExecuteNonQuery(); } catch (Exception) { ContextUtil.SetAbort(); throw; } finally { connection.Close(); } ContextUtil.SetComplete(); } } } 

Client Application

Having built the component, you can create a client application. For testing purposes, a console application serves the purpose. After referencing the assembly NorthwindComponent and the assembly System.EnterpriseServices, 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. With 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():

 Order order = Order.Create("PICCO", DateTime.Today, "Georg Pipps",  "Salzburg", "Austria"); order.AddOrderLine(OrderLine.Create(16, 17.45F, 2)); order.AddOrderLine(OrderLine.Create(67, 14, 1)); using (OrderControl orderControl = new OrderControl()) { orderControl.NewOrder(order); } 

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 30-9).

image from book
Figure 30-9



Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

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