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.
Comments
Anonymous
November 27, 2006
Following your recommendations, I've extended OrderForm (as "ExtendedOrderForm") to contain a two extra properties ("PurchaseOrderId" and "Remarks", both strings). I've created the extended object in a separate assembly, updated both the object and pipeline mappings to reference the new assembly/object and modified the database to match. When I use your test method, the order saves properly. However, when I'm using the production starter site and when I attempt to submit an order, I get a exception somewhere in the basket pipeline "Object does not match target type." Is extending the order system incompatible with the starter site? Can I transparently make the changes and then just use my ExtendedOrderForm object as needed? If I extend the object model in the way you've described, does this involve making extensive changes to the starter site?Anonymous
December 26, 2006
Have you extended mapping files in the starter site. This should work fine with the starter site as well. Pls send me the detailed exception.Anonymous
January 31, 2007
Hi Sathish, nice post. I used it in an attempt to extend CreditCardPayment. But I am having a problem with OrderMapping.exe. So I followed your instructions and created SiteOrderForm. But I get the same problem. When I use OrderMapping.exe to create the SQL or the XSD, I get the following error: One or more errors found during validation: Warning O0019 - Class SiteOrderForm is present in Object Mapping file but is not present in web.config as a UserTypeName. Is this desired? Error G0010 - Mapping defined for class SiteOrderForm which does not exist in any provided assembly But the site works and does not complain as OrderMapping.exe is. I understand that the validation is the same. But it appears to be different. For OrderMapping.exe, I am copying the mapping, pipeline, web.config, CommerceObjectExtensions.dll, and OrderMapping.exe into the same folder. Then executing as you described. Have you see or heard of this problem? Thanks, BilliamAnonymous
March 08, 2007
hi, will microsoft still provide support should we go into sql tables to extend the fields? the approach provided is interesting, and we're going to test this out thanks mingAnonymous
March 08, 2007
hi, will microsoft still provide support should we go into sql tables to extend the fields? the approach provided is interesting, and we're going to test this out thanks mingAnonymous
April 08, 2007
The approach used in the extension is through OOPS. Commerce Server team has provided this framework.In future versions they will support the same.Till the framework is supported, the implementations should get backward compatablity.Anonymous
April 08, 2007
The comment has been removedAnonymous
November 05, 2007
Hi Sathish, Great post!! However, I've implemented a single field extension to the OrderForm class, so very similar to your example, only difference being that the new field is DataType int. I can create the instance of the SiteOrderForm, and add it to my basket object. However, when I call Basket.Save(); i get the following error: Type 'CommerceObjectExtensions.SiteOrderForm' in Assembly 'CommerceObjectExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ef6a1cd6d2063a98' is not marked as serializable. One other that I've done is to strongly sign the assembly. Could that have caused some issues? Thanks!! NatAnonymous
November 13, 2007
Hi Nat, Place [Serializable] attribute in the extended type. This should solve the issue.Anonymous
March 26, 2009
Hi Satish, Nice article. I'm running into one issue. I had extended LineItem class in my application and the site was in production for some time. NOw I want to add 2 new columns to the LineItem. I checked with the article "How to Create Multiple Versions of an Extended Orders Class" on MSDN. I followed the following steps:
- Created new class and inherited from the first class which was extended.
- Changed the OrdersObjectMapping.xml and OrderPipelineMappings.xml file.
- Changed the web.config file.
- Created storage file OrdersStorage.sql file.
- Added columns to the LineItem table.
- Tried to run the application but it is givine error : Failed to marshall data into the commerce dictionary. Any help will be great! Thank, Manju
Anonymous
July 10, 2009
The comment has been removedAnonymous
July 13, 2009
Hi Sathishc, great post...i do that, and the persistent processes work fine...but when i filter the baskets using the newest properties, the commerce throw a exception (The '[property_name]' property is not a searchable property of entity 'Basket'... How can i mark this new properties searchables in commerce server? Any ideia? Best regards. Rogerio.Anonymous
July 13, 2009
to complete the information, i had extended the OrderTemplatesAndBaskets table, and Basket class. Rogerio.Anonymous
November 23, 2009
Hi, I have extende my OrderForm by update a new Parameters which will be returned by verisign after Creditcard Transaction I have following the steps mentioned as in http://blogs.msdn.com/sathishcg/archive/2006/11/08/extending-commerce-server-order-system.aspx after this i also have updated new column in the en_OrdersPresentationInfo.xml for the change in orderform as below <Class Name="OrderForm" DisplayName="Order form"> <Property Name="cc_auth_number" DisplayName="cc_auth_number" />/my new field/ now i am added my products in one page... as below Microsoft.CommerceServer.Runtime.Orders. Basket bas = CommerceContext.Current.OrderSystem.GetBasket(UserID, "default"); SiteOrderForm frm = new SiteOrderForm (); frm.cc_auth_number= "65";/my new field just for verification i am updating here/ LineItem item = new LineItem(CatalogName, ProductID, "", Quantity); frm.LineItems.Add(item); bas.OrderForms.Add(frm); bas.Save(); PipelineInfo pipeline = new PipelineInfo("basket", OrderPipelineType.Basket); Profile profile = CommerceContext.Current.ProfileSystem.GetProfile(Session["AuthUserId"].ToString(), "UserObject"); if (profile != null) if(profile != null) { pipeline.Profiles.Add("UserObject", profile); bas.RunPipeline(pipeline); } In my checkout page after verisignApproval Basket bas = CommerceContext.Current.OrderSystem.GetBasket(UserID, "default"); SiteOrderForm orderform = GetSiteOrderForm(); orderform.cc_auth_number = "232"; bas.OrderForms.Add(orderform);/throws error over here/ //Error thrown is "An order form by the same name or ID already exists in the order forms collection."/ //If i comment the section bas.OrderForms.Add(orderform); no error is thrown but i am not getting the updated value in the cc_auth_number.Please let mw know where i am wrong bas.Save(); bas.SaveAsOrder(); public SiteOrderForm GetSiteOrderForm(string orderFormName) { if (String.IsNullOrEmpty(orderFormName)) { orderFormName = SiteOrderForm.DefaultOrderFormName; } Guid registeredUserID = new Guid(Session["AuthUserId"].ToString()); Basket registeredBasket = OrderContext.Current.GetBasket(registeredUserID, "default"); SiteOrderForm orderForm = registeredBasket.OrderForms[orderFormName] as SiteOrderForm; if (orderForm == null) { orderForm = new SiteOrderForm(orderFormName); registeredBasket.OrderForms.Add(orderForm); } return orderForm; } public SiteOrderForm GetSiteOrderForm() { return this.GetSiteOrderForm(null); }Anonymous
July 29, 2015
The comment has been removed