Extending Commerce Server Order System

Introduction

Order system represents the actual orders placed by the customers. Order system stores the order information in collection of OrderForm objects. Commerce Server runtime persists all the information represented by the order system objects in database to facilitate the post purchase operations. Order system classes contains information like products purchased, discount applied, payment details etc. Commerce server stores selected information in separate columns along with the serialized marshal data in database. Basket object represents all the order related data in name value collection pattern (IDictionary). Basket object holds many other objects like Payments, Shipping etc which has their own hierarchy of persistence in the database.

Order System Extension in previous versions of Commerce

Since Order system classes represent data through IDictionary, the classes can be extended by storing the custom information in new key value pair. Since the entire object is stored in database, extended information will also get persisted. The main disadvantage in this approach is that, the extended information cannot be searched using SQL queries and this information cannot leverage the advantages of SQL indexes. Only approach in working on the extended data is by loading back the serialized object through commerce API. Usual work around is creating extended tables to store the custom information by having link to the Order Form tables. But this extension will not be sensed by the commerce server runtime.

To overcome these issues, Latest version of Commerce (Commerce 2007) allows the implementers to extend the order object to cater their needs in an object oriented fashion.

Core Order System classes

Order system contains a chain of classes which represents the entire order system. Basket class inherits from OrderGroup class. The core classes in the order system are OrderForms, and LineItems. By default commerce server has corresponding tables for persisting the objects information. Following are the details about some of the core classes and its corresponding table mapping information.

Class Name                                     Table Name
Basket                                            OrderTemplatesAndBaskets
OrderForms                                     OrderForms
Payments (Credit card payments)      CreditCardPayments
Payments (Cash card payments)       CashCardPayments
LineItems                                        LineItems

Extending OrderForm Object in Commerce Server 2007

Scenario

Popular Books Mall hosts an E-Com site for selling books online. Popular books sell different categories of books like Computer books, Novels, Books relating to research etc. The user can add any types of books to the cart. To provide quick delivery, the site administrators have decided to appoint various delivery managers for different book categories to speed up the post delivery process. So when ever the order is getting placed, e-com site will create OrderForm objects based on the books category and will include the email address of the delivery administrators in the order form object. Once the order is placed, the delivery manager will get intimated through email to take necessary actions.

Extending the OrderForms table

OrderForms table needs to be modified by adding EmailAddress column of type nvarchar (50)

Extending the OrderForm Class

1. Create a new class library project named CommerceObjectExtensions and add reference to Microsoft.CommerceServer.Runtime.dll.

2. Create a new class named SiteOrderForm object by inheriting the class from Microsoft.Commerce.Runtime.Orders.OrderForm class.

 
 

using System;
using System.Text;
using Microsoft.CommerceServer.Runtime.Orders;
using System.Reflection;
using System.Runtime.Serialization;

class SiteOrderForm :OrderForm
{

// Default Constructor

public SiteOrderForm(){}
private string _EmailAddress;

/// <summary>
/// Property to set the email address. Property also calls
/// SetDirty method to indicate the base class about this change.
/// </summary>
public string EmailAddress
{
    get
     {
          return _EmailAddress;
      }
    set
     {
         SetDirty(value);
         _EmailAddress = value;
      }
}

/// <summary>
/// Constructore called by Commerce server runime to create
/// back the OrderForm object from the
/// binary / marshalled data from database.
///
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
public SiteOrderForm(SerializationInfo info, StreamingContext context): base(info, context)
{
     try
     {
           this.EmailAddress = (string)info.GetValue("EmailAddress", typeof(string));
      }
     catch (Exception)
     {
           throw;
      }
}

/// <summary>
/// Get object method will be called by commmerce runtime when /// the object is getting serialized to store in database.
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
     try
      {
          base.GetObjectData(info, context);
          info.AddValue("EmailAddress", this.EmailAddress);
       }
     catch (Exception)
     {
         throw;
      }
}

}

When the object is serialized, Commerce Runtime calls the GetObjectData method to add extended information to the Serialization info. This method acts as a hook for the implementers to serialize the extended information. When the object is getting de-serialized, commerce server uses a special constructor to re-initialize the object. This constructor acts as a hook for the implementers to de-serialize their extended data.

Updating OrderObjectMapping.xml file

OrderObjectMapping.xml stores the various commerce related table information and their corresponding class information. Commerce Server runtime uses this file to do data persistence in the Database. OrderObjectMapping.xml contains following information.

1. Table and its column information

2. Relationship between tables

3. Class information of various commerce server objects

4. Mapping information between the class and table.

By default commerce server OrderForm object will be configured and the same will be mapped to OrderForms tables. The above mapping should be changed to point the extended SiteOrderForm class.

Updating the new column information in OrderForms table info

<Table Name="OrderForms">
    <Columns>
      <Column Name="OrderFormId" DataType="uniqueidentifier" GUID="true" />
      <Column Name="OrderGroupId" DataType="uniqueidentifier" />
      <Column Name="Name" DataType="nvarchar" Precision="64" IsNullable="true" />
      <Column Name="BillingAddressId" DataType="nvarchar" Precision="50" IsNullable="true" />
      <Column Name="PromoUserIdentity" DataType="nvarchar" Precision="64" IsNullable="true" />
      <Column Name="SubTotal" DataType="money" />
      <Column Name="ShippingTotal" DataType="money" />
      <Column Name="HandlingTotal" DataType="money" />
      <Column Name="TaxTotal" DataType="money" />
      <Column Name="Total" DataType="money" />
      <Column Name="Created" DataType="datetime" />
      <Column Name="LastModified" DataType="datetime" />
      <Column Name="ModifiedBy" DataType="nvarchar" Precision="64" />
      <Column Name="Status" DataType="nvarchar" Precision="64" IsNullable="true" />
      <Column Name="MarshalledData" DataType="image" IsNullable="true" />
      <Column Name="EmailAddress" DataType="nvarchar" Precision="50" IsNullable="true" />
    </Columns>
  <Constraints>
      <PrimaryKey Name="PK_OrderForms">
           <ColumnRef Name="OrderFormId" />
      </PrimaryKey>
     <ForeignKey Name="FK_OrderForms_PurchaseOrders" ForeignTable="PurchaseOrders" CascadeDelete="false">
         <ColumnMatch Name="OrderGroupId" ForeignName="OrderGroupId" />
     </ForeignKey>
</Constraints>
</Table>

Updating OrderForm class

<Class Name="SiteOrderForm">
    <Property Name="OrderFormId"/>
    <Property Name="OrderGroupId"/>
    <Property Name="Name"/>
    <Property Name="BillingAddressId"/>
    <Property Name="PromoUserIdentity"/>
    <Property Name="SubTotal"/>
    <Property Name="ShippingTotal"/>
    <Property Name="HandlingTotal"/>
    <Property Name="TaxTotal"/>
    <Property Name="Total"/>
    <Property Name="Created"/>
    <Property Name="LastModified"/>
    <Property Name="ModifiedBy"/>
    <Property Name="Payments"/>
    <Property Name="LineItems"/>
    <Property Name="Shipments"/>
    <Property Name="PromoCodeRecords"/>
    <Property Name="Status"/>
    <Property Name="EmailAddress"/>

</Class>

Updating the Class – Table Mapping Information

<ClassTableMap Class="SiteOrderForm" Table="OrderForms">
    <PropertyMap Property="OrderFormId" Column="OrderFormId" />
    <PropertyMap Property="OrderGroupId" Column="OrderGroupId" />
    <PropertyMap Property="Name" Column="Name" />
    <PropertyMap Property="BillingAddressId" Column="BillingAddressId" />
    <PropertyMap Property="PromoUserIdentity" Column="PromoUserIdentity" />
    <PropertyMap Property="SubTotal" Column="SubTotal" /> 
    <PropertyMap Property="ShippingTotal" Column="ShippingTotal" />
    <PropertyMap Property="HandlingTotal" Column="HandlingTotal" />
    <PropertyMap Property="TaxTotal" Column="TaxTotal" />
    <PropertyMap Property="Total" Column="Total" />
    <PropertyMap Property="Created" Column="Created" />
    <PropertyMap Property="LastModified" Column="LastModified" />
    <PropertyMap Property="Status" Column="Status" />
    <PropertyMap Property="ModifiedBy" Column="ModifiedBy" />
    <PropertyMap Property="EmailAddress" Column="EmailAddress" />

</ClassTableMap>

Updating class dependency

OrderForm object has relationship with many other classes like Payments, LineItems etc. The related class information needs to be updated to reflect the change in the OrderForm Object.

<CollectionRelationships> 
 <CollectionRelationship Name="PurchaseOrderOrderForms" ParentClass="PurchaseOrder" ParentProperty="OrderForms"     ChildClass="SiteOrderForm" />
<CollectionRelationship Name="OrderFormPayments" ParentClass="SiteOrderForm" ParentProperty="Payments" ChildClass="Payment" />
<CollectionRelationship Name="PurchaseOrderAddresses" ParentClass="PurchaseOrder" ParentProperty="Addresses" ChildClass="OrderAddress" />
<CollectionRelationship Name="OrderFormLineItems" ParentClass=" SiteOrderForm" ParentProperty="LineItems" ChildClass="LineItem" />
<CollectionRelationship Name="LineItemDiscountsApplied" ParentClass="LineItem" ParentProperty="OrderLevelDiscountsApplied" ChildClass="DiscountApplicationRecord" />
<CollectionRelationship Name="ItemLevelDiscountsApplied" ParentClass="LineItem" ParentProperty="ItemLevelDiscountsApplied" ChildClass="DiscountApplicationRecord" />
<CollectionRelationship Name="OrderFormShipments" ParentClass="SiteOrderForm" ParentProperty="Shipments" ChildClass="Shipment" />
<CollectionRelationship Name="OrderFormPromoCodeRecords"
ParentClass="SiteOrderForm" ParentProperty="PromoCodeRecords" ChildClass="PromoCodeRecord" />
<CollectionRelationship Name="ShipmentDiscountsShipping" ParentClass="Shipment" ParentProperty="ShippingDiscounts" ChildClass="ShippingDiscountRecord" />
</CollectionRelationships>

Updating OrderPipeLineMapping.xml file

OrderPipeLineMapping.xml stores the mapping information between the strongly typed properties of various commerce objects to its corresponding Dictionary key values. This information will be used by Commerce Server runtime to convert the Order object to IDictionary object for running the order against the pipelines. Dictionary keys which start with underscores will not get persisted in the database as part of the serialized binary data.

<Class Name="SiteOrderForm">
    <Property Name="EmailAddress" DictionaryKey="Email_Address" />

</Class>

Updating assembly information in web.config

Commerce server related assemblies' information is stored in web.config file of the site and same will be used by the commerce runtime to load it in runtime. By default all the Order system assemblies are configured to load from GAC. AssemblyType=Local indicates commerce runtime to load the assembly from the local bin path.

<orders honorStatus="true" newOrderStatus="NewOrder" sqlCommandTimeoutSeconds="60" sqlLongRunningCommandTimeoutSeconds="6000">
<Types>

<Type Key="OrderForm" UserTypeName="SiteOrderForm" AssemblyType="Local" NameSpace="CommerceObjectExtension" Assembly="CommerceObjectExtension"/>
</Types>
</orders>

Generating Modified Stored Procedures

Commerce Server provides OrderMapping.exe tool to generate the stored procedures for the object extensions done by the implementers. This tool runs against the web.config and order mapping xml files. It also expect the new order form assembly in GAC or in the Commerce server tools folder
(C:\Program Files\Microsoft Commerce Server 2007 \Tools) for generated the OrderStorage.sql file. This behaviour is documented in Commerce Server Documentation. Following are the steps to generate the OrderStorage.sql.

1. Copy the new CommerceObjectExtension.dll to the Commerce Server tools folder.

2. In Command Prompt Go to the site folder where the config file exists (c:\inetpub\wwroot\PopularBooks).

3. Run %Commerce_Server_root%"\Tools\OrderMapping.exe /w web.config /i to generate OrderStorage.sql

4. Run the generated SQL file in the corresponding transaction database of the site.

Sample Code to Test the OrderForm extension

private void SaveOrder(Guid UserID, string CatalogName, int ProductID, int Quantity,string EmailAddress)
{
    try 

    {
       Microsoft.CommerceServer.Runtime.Orders.Basket bas = CommerceContext.Current.OrderSystem.GetBasket  (UserID), @"Basket" + Guid.NewGuid().ToString());
      CommerceObjectExtension.SiteOrderForm frm = new CommerceObjectExtension.SiteOrderForm ();
      frm.EmailAddress= EmailAddress;
      LineItem item = new LineItem(Catalog, productID, "", Quantity);
      frm.LineItems.Add(item);
      bas.OrderForms.Add(frm);
      bas.Save();
      bas.SaveAsOrder();
    } 
  catch (Exception)
   {
      // Manage Exception
     throw ex;
     }
}

Conclusion

Commerce Server 2007 allows the implementers to extend the order system through inheritance and by updating the Meta data xml file. The same approach can be applied to extend all the classes of the Order System like Payments, Shipping etc.