Welcome to MSDN Blogs Sign in | Join | Help

Syndication

Tags

    No tags have been created or used yet.
How to Enumerate Global Resource Properties.

I got few questions around how to enumerate the properties for a global resource. So here is how you do it. Following sample spits the properties for all the global resources in the console window. This sample uses the manager version of the globalconfig object. The APIs in this object return the  ADO RecordSets. SO we'll need to enumerate through the Fields collecitons.

GlobalConfig2FreeThreaded config = new GlobalConfig2FreeThreaded();

config.Initialize("");

Fields globalResources = config.Fields;

foreach (Field globalResource in globalResources)

{

_Recordset globalResourceProperties = globalResource.Value as _Recordset;

      if (globalResourceProperties != null)

      {

            foreach (Field property in globalResourceProperties.Fields)

            {

                  Console.WriteLine("{0} = {1}", property.Name, property.Value);

            }

      }

}

 

 

 

Posted Thursday, August 17, 2006 11:13 AM by kumar vinod | 1 Comments

Orders DataMigration From CS2002/2000 to CS2007

OrderSystem in CS2007 comprises of 2 site resources as it did in CS2002/2000. Orders migration is not an in place migration. Migration tool loads data from CS2002 tables, transforms it to CS2007 format and inserts it into CS2007 tables. Tool creates the CS2007 orders system schema in the same database as CS2002. Since all table names are unique it does not over write any out of the box CS2002/2000 tables (However if you’ve extended your CS2002/2000 database schema then it’s recommended to verify that there are no conflicting table names before running the migration tool, otherwise data from the tables with conflicting names will get deleted. For this you can open the Requisition.sql and TaxAndShipping.sql files installed with CS2007 installation under SDK/SiteCreate folder.). Following tables participate in migration process. Since OrderSystem migration is CPU intensive, it’s recommended to run the tool from a different machine than the Sql box so that SqlServer and the migration tool are not competing for the resources.

 

OrderSystem Site Resources

CS2002/CS2000

CS2007

Transactions

OrderGroups

OrderGroupAddresses

OrderFormHeader

LineItems

PurchaseOrders

OrderAddresses

OrderForms

LineItems

 

TransactionConfig

ShippingConfig

tableShippingRates

Region

ShippingMethod

ShippingRates

RegionCodes

 

Here I'm going to illustrate the data migration for the Runtime data present in the transaction database (i.e. Purchase Orders\Baskets\ Order Templates). Migration tool loads the CS2002 data in dictionary format using dbstorage. Data from the dictionary is converted to strongly typed objects using the Pipeline adapter. Once the objects are created they are persisted using the Save methods implemented by these objects. Pipeline adapter makes use of the mapping file specially customized for the migration purposes only (OrdersMigrationMappings.xml). Mapped storage system uses the default OrderObjectMappings file. Before the migration the tool verifies the data in CS2002 database and reports if data from a column will land in the blob column after the migration. You should carefully analyze these messages and if you don’t want data from a column to move into the blob column you’ll need to perform extensions to the order system. OrderMigrationMappings.xml file contains the mapping between the dictionary key and the properties in CS2007 object model and the orderobjectmappings.xml files contains the mapping for properties with the Sql table columns. These mapping are used to determine whether data from a strongly typed column in CS2002 database table will land in blob column in CS2007 table.

 

Since there can be large set of orders and baskets in your database, migration tool implements fault tolerant logic so that you’ll not need to restart the migration process if there are bad records in the database. If there are certain records which cannot be migrated successfully it keeps logging the OrderGroupId for such records in an ExceptionLog this log is different from the detailed error log used by the migration tool. After migration tool completes the migration you can go and fix such records and re-run the migration tool in the exception mode. In this case it will only migrate the records which are logged in the exception log. There is a threashold (10000 records) after which migration tool will error out and you’ll need to re-run it in the normal mode. 

 

Migration for TransactionConfig data is straight forward. Data is loaded from the CS2002 tables and is inserted into CS2007 data tables. ShippingConfig table in CS2000 database did not contain the language column so the migration tool populates ‘Unknown’ value for this column in ShippingMethod table in CS2007 database. In CS2002 same shipping method in different languages was considered as different shipping method. This problem has been fixed in CS2007. But the migration tool cannot group the shipping methods from different languages so you’ll need to make use of the Customer and Order manager UI to group the same shipping methods in different language. CS2007 database schema has enforced few unique constraints in ShippingMethod and RegionCodes tables. These constraints were not present in the corresponding tables in CS2002/2000 database. Migration tool will report about the data which violates these constraints before migrating the data. If you see any such errors you’ll need to fix such rows manually and then run the migration tool.

 

 

 

Posted Sunday, July 16, 2006 10:44 PM by kumar vinod | 5 Comments

Extensibility Notes

Restrictions on Collection Semantics

The mapped storage, Pipeline Adapter, and XML serialization support in the Orders system will not work with custom collections that implement dictionary collection semantics. Any collection’s GetEnumerator method must return a list-type enumerator instead of an IDictionaryEnumerator.

PurchaseOrder and Deserialization Constructors

The only extendable Orders class that does not require a deserialization constructor is PurchaseOrder—it is the root class for storage through the mapped storage system and is not saved through serialization to the same table to which the Basket and OrderTemplate classes are saved. The different storage mechanisms serve different purposes. Baskets and OrderTemplates are saved and loaded often as a customer updates an order at a site; PurchaseOrders are created less often, when a user completes an order. Therefore Basket and OrderTemplate operations need higher performance, and give up some flexibility (for example, saving to multiple tables) that is present in the slower but full-featured storage mapping system used with PurchaseOrder.

Mapping Complex Container Relationships

The samples provided with the CS2007 do not cover mapping more complex container relationships among classes. The following are several rules that you must follow in order for relationships to work with the Orders storage mapping system:

The mapping system allows two classes (for example, A and B) to participate in either of the following relationships, but not both; attempting to create a mapping file that describes both relationships will result in unknown behavior.

A is contained by B through a collection class.

An inheritance model where A is the base class and B is the inherited class and one or both classes are mapped.

Multiple-parent container relationships are not allowed, creating such a relationship results in unpredictable behavior. For example, consider a case where there are classes A, B, and C. An invalid scenario results if B contains a collection of C, and A contains both a collection of B and a collection of C, such that C has two parent containing classes.

A collection relationship cannot be created mapping collections of the same type to different tables from the same class. An example of this is the base Orders class LineItem and its two collection members LineItem.OrderLevelDiscountsApplied and LineItem.ItemLevelDiscountsApplied, each of which contain a set of DiscountApplicationRecord instances. There is no way to map members of each of these collections to different tables; there is only one mapping allowed from the child class DiscountApplicationRecord to a set of tables, independent of parent collection instances to which they might belong.

Requiring Setters on Mapped Properties

In the CS2007, it is a requirement that any properties in extended Orders classes that are to be mapped to storage must have both a getter and a setter. The setter is required to restore data from the mapped storage system into a class instance.

Posted Thursday, June 29, 2006 11:46 AM by kumar vinod | 3 Comments

Changing Column Matching for OrderTemplate and Basket Persistence

During Basket and OrderTemplate persistence, the Orders system performs column matching against column names in the OrderTemplatesAndBaskets table. When a column of the table matches in a case-insensitive fashion the name of a strongly-typed or weakly-typed property of the Basket or OrderTemplate instance, the value of the property is placed in the column during save and the column value placed in the property during load. This allows you to perform queries against the column values; the binary data in OrderTemplatesAndBaskets.marshaled_data, which is the full binary serialization of the Basket or OrderTemplate instance, is not understandable by SQL Server.

When you wish to modify the OrderTemplatesAndBaskets table to add a column to participate in this matching, you must also modify following stored procedures that interact with this table:

SPI_OrderTemplatesAndBaskets SPS_Search_OrderTemplatesAndBasketsBySoldToIdOrderGroupId, SPS_Search_OrderTemplatesAndBasketsByOrderGroupId, SPS_Search_OrderTemplatesAndBasketsByStatusCodeSoldToId and SPS_Search_OrderTemplatesAndBasketsByStatusCodeSoldToIdName

These stored procedures do not need to be modified:

·         SPD_OrderTemplatesAndBaskets

·         SPD_BulkDeleteBaskets

·         SPM_Ord_MetaData_OrderTemplatesAndBaskets

The following sections detail how to make changes to each of these stored procedures.

Modifying SPI_OrderTemplatesAndBaskets                                                                             This stored procedure will either update (if the Basket or OrderTemplate row is pre-exisitng) or insert a row into OrderTemplatesAndBaskets. If you add a column to the OrderTemplatesAndBaskets table, perform the following steps to update SPI_OrderTemplatesAndBaskets:

·         Add a parameter to SPI_OrderTemplatesAndBaskets that follows the pattern, “@@param_<ColumName>” in the same order that the new column is specified in the OrderTemplatesAndBaskets declaration. Note that the parameter @@param_marshaled_data must appear last in the parameter list.

·         Modify the two UPDATE and one INSERT statements in the stored procedure to set the column value to the value of the parameter.

Modifying SPS_OrderTemplatesAndBasketsByStatusCodeSoldToIdName           This stored procedure searches for a specific Basket or OrderTemplate based on the SoldToID and order name (name can be null). Update this stored procedure by adding the new column name to the two SELECT statements that perform the search query. Add the new column in the same order as its declaration in the OrderTemplatesAndBaskets table definition. Note that the marshaled_data column must be the first column in each of these SELECT statements.

 

Modifying SPS_OrderTemplatesAndBasketsByStatusCodeSoldToId

This stored procedure searches for a specific Basket or OrderTemplate based on the SoldToID. Update this stored procedure by adding the new column name to the SELECT statement that performs the search query. Add the new column in the same order as its declaration in the OrderTemplatesAndBaskets table definition. Note that the marshaled_data column must be the first column in each of these SELECT statements.

 

Modifying SPS_OrderTemplatesAndBasketsByOrderGroupId

This stored procedure searches for a specific Basket or OrderTemplate based on the OrderGroupId. Update this stored procedure by adding the new column name to the SELECT statement that performs the search query. Add the new column in the same order as its declaration in the OrderTemplatesAndBaskets table definition. Note that the marshaled_data column must be the first column in each of these SELECT statements.

 

Modifying SPS_OrderTemplatesAndBasketsBySoldToIdOrderGroupId

This stored procedure searches for a specific Basket or OrderTemplate based on the SoldToId and OrderGroupId. Update this stored procedure by adding the new column name to the SELECT statement that performs the search query. Add the new column in the same order as its declaration in the OrderTemplatesAndBaskets table definition. Note that the marshaled_data column must be the first column in each of these SELECT statements.

Posted Thursday, June 29, 2006 11:26 AM by kumar vinod | 2 Comments

Mapping Weakly Typed Properties to Storage

You can think of any class property as a name-value pair. Strongly typed properties are properties defined as part of a class definition. Weakly typed properties are properties that have not been added as class properties to a class, but instead are placed in a string indexer (this[string] in C#) as a name-value pair. This functionality is provided in all of the base Orders classes for backward compatibility with legacy COM-based pipeline components. Normally, weakly typed properties are serialized into a single binary large object (BLOB) as part of saving to storage. Weakly typed properties whose names start with an underscore, and those that are not serializable, are not serialized.

The Orders storage mapping system provides the ability to map individual weakly typed properties to their own columns, similar to the way in which you map strongly typed properties. The default Orders storage mapping file contains no weakly typed property mappings.

When saving a weakly typed property to storage, an exception is thrown by the storage code Microsoft.CommerceServer.Runtime.Orders.Basket.SaveAsOrder and Microsoft.CommerceServer.Runtime.Orders.PurchaseOrder.Save if the type of the weakly typed property is not compatible with the column's type.

The following example shows how to perform such a mapping from a weakly typed property UserNickname in the OrderAddress class to its own column added to the OrderAddresses table.

 

Step 1: Modify the Storage Mapping File

Only excerpts from the mapping file are shown in this step.

 

Adding the UserNickname Column

You must add a new column definition to the OrderAddresses table in which to store values from OrderAddress class instances.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Table Name="OrderAddresses">

2  <Columns>

3   <Column Name="OrderGroupId" DataType="uniqueidentifier" />

  ...

4   <Column Name="UserNickname" DataType="nvarchar" Precision="50" IsNullable="true" />

5   <Column Name="MarshalledData" DataType="image" IsNullable="true" />

6  </Columns>

7 </Table>

 

At line 4, add the new column definition for UserNickname as type nvarchar(50) and null-able. It is required that any column corresponding to a weakly typed property have a null-able column constraint. When the weakly typed property is not present in the name-value pair set, null is applied to the column value.

 

Adding a Weakly Typed Property to a Class Definition

 

Next, update the class definition for OrderAddress to add a definition for your new weakly typed property that you want to map. Only weakly typed properties mapped to their own columns must be added to the class definition; unmapped properties can simply be added to the name-value pairs on an ad hoc basis.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Class Name="OrderAddress">

2  <Property Name="OrderAddressId" />

...

3  <WeaklyTypedProperty Name="UserNickname" />

4 </Class>

 

At line 3, add a special type of XML node to the class, WeaklyTypedMember. The only attribute for this node is the Name attribute, which defines the name of the weakly typed property.

 

Adding the Mapping for the Weakly Typed Property

 

Finally, change the OrderAddress mapping to add a mapping between the weakly typed property and the column.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <ClassTableMap Class="OrderAddress" Table="OrderAddresses">

2  <PropertyMap Property="OrderAddressId" Column="OrderAddressId" />

...

3  <PropertyMap Property="UserNickname" Column="UserNickname" />

4 </ClassTableMap>

 

At line 3, add a PropertyMap XML node containing the source object property and the target database column names.

 

Step 2: Use OrderMapping.exe on the Mapping File

See the notes from the MyLineItem sample on how to use OrderMapping.exe with a Web.config file. You use the generated OrdersStorage.sql file to update the site database. This new file contains changes to the default Orders stored procedures to support saving to and loading from the new table.

 

Step 3: Modify the Site Database

 

In the Commerce Server 2007, you perform modifications to existing tables. Assuming this sample is applied to the CSharpSite sample provided with the CS2007, this means adding a column named UserNickname of type nvarchar(50) to the OrderGroupAddresses table. After adding the column, you update the stored procedures in the database. Load the OrdersStorage.sql file into the SQL Server Query Analyzer and run it against the site database (CSharpSite_transactions in the CSharpSite demonstration site provided with the CS2007).

 

Step 4: Deploy the Mapping File

The MyLineItem sample describes the steps to deploy the new version of the mapping file.

Posted Wednesday, June 28, 2006 11:48 AM by kumar vinod | 4 Comments

Extending the Orders System

The Orders system Commerce Server 2007, enables you to extend the default objects with custom strongly typed properties and custom methods. The Orders system is accessible through the Microsoft.CommerceServer.Runtime.Orders namespace.

Using the Orders system, you can add custom properties to existing classes and collections of objects to model your specific business needs, which you can then map to your database and, through the Order System Pipeline Adapter, to Dictionary keys for use with COM-based pipeline components.

Order capture system classes are persisted to SQL storage in two ways:

·         Serialization with column matching—used when high performance is a requirement, because OrderTemplate and Basket are loaded and saved often and serialization is faster. This persistence mechanism is different from the mapped storage system used by purchase orders. The order is serialized using binary serialization and placed in the OrderTemplatesAndBaskets.marshaled_data column. Additionally, during load and save, when a column of the OrderTemplatesAndBaskets table matches (in a case-sensitive fashion) the name of a strongly-typed or weakly-typed property of the Basket or OrderTemplate instance, the value of the property will be copied to the column on save and copied into the new instance from the column during load. This allows you to perform queries against the column values, since the binary data in the marshaled_data column is not readable or queryable by SQL Server. See the

·         Mapped Storage System—purchase orders use the mapped storage subsystem, which provides more flexibility than serialization, but at the cost of some performance. It provides the ability to map individual class properties to one or more SQL table columns. This enables the user to perform data mining on placed orders.

The Pipeline Adapter translates an OrderForm class graph into one or more legacy OrderForm-style COM IDictionary instances, which are then passed as the “order” IDictionary to COM-based pipeline components during execution of a pipeline. A Pipeline Adapter mapping XML file provides the ability to specify how existing and new class members you may add will be mapped to IDictionary keys during Pipeline Adapter translation.

The following sections describe extending the Orders system using Microsoft® Visual Studio® .NET and the C# managed-code language. The Orders system supports any Common Language Runtime (CLR) based language. The following table lists the base classes for the Orders system and indicates whether each class is extensible and notes derived classes.

 

Class Name

Extensibility

OrderContext

Non-extensible and sealed.

OrderGroup

Non-extensible.

Basket

Extensible and derived from OrderGroup.

PurchaseOrder

Extensible and derived from OrderGroup.

OrderTemplate

Extensible and derived from OrderGroup.

OrderGroupCollection

Non-extensible and sealed.

OrderForm

Extensible.

PromoCodeStringCollection

Non-extensible and sealed.

OrderFormCollection

Non-extensible and sealed.

LineItem

Extensible.

LineItemCollection

Non-extensible and sealed.

DiscountApplicationRecordBase

Non-extensible

DiscountApplicationRecord

Extensible and derived from DiscountApplicationRecordbase

DiscountApplicationRecordCollection

Non-extensible and sealed.

ShippingDiscountCollection

Non-extensible and sealed.

ShippingDiscountRecord

Extensible and derived from DiscountApplicationRecordbase

OrderAddress

Extensible.

OrderAddressCollection

Non-extensible and sealed.

Shipment

Extensible.

ShipmentCollection

Non-extensible and sealed.

PromoCodeRecord

Extensible.

PromoCodeRecordCollection

Non-extensible and sealed.

Payment

Extensible.

CreditCardPayment

Extensible and derived from Payment.

GiftCertificatePayment

Extensible and derived from Payment.

PurchaseOrderPayment

Extensible and derived from Payment.

CashCardPayment

Extensible and derived from Payment.

PaymentCollection

Non-extensible and sealed.

 

Virtual Methods

The following are the virtual methods of the Orders base classes that you can override in your derived classes to provide custom behavior. The methods are grouped by class.

OrderGroup, Basket, OrderTemplate, PurchaseOrder

public virtual void GetObjectData(SerializationInfo, StreamingContext)

public virtual void Save()

public virtual void Clear()

public virtual IEnumerator GetEnumerator()

public virtual void RunPipeline(PipelineInfo)

OrderForm

public virtual void GetObjectData(SerializationInfo, StreamingContext)

OrderAddress

public virtual void GetObjectData(SerializationInfo, StreamingContext)

LineItem

public virtual void GetObjectData(SerializationInfo, StreamingContext)

DiscountApplicationRecord, ShippingDiscountRecord

public virtual void GetObjectData(SerializationInfo, StreamingContext)

Payment, CreditCardPayment, GiftCertficatePayment, PurchaseOrderPayment, CashCardPayment

Public virtual void GetObjectData(SerializationInfo, StreamingContext)

PromoCodeRecord

Public virtual void GetObjectData(SerializationInfo, StreamingContext)

Shipment

Public virtual void GetObjectData(SerializationInfo, StreamingContext)

 

Type Conversions in the Mapped Storage System

The mapped storage system used in PurchaseOrder storage only allows certain property types to be mapped. Mapping complex types (e.g. properties that return references to classes) is not allowed.

The following table illustrates the allowed type conversions between .NET-based data types and SQL data types. The far left column lists the SQL data types and the top row lists the .NET-based data types. Allowed conversions are marked with an X.

 

 

Int64

Boolean

DateTime

Decimal

Double

Int32

String

Single

GUID

Bigint

X

 

 

 

 

 

 

 

 

Bit

 

X

 

 

 

 

 

 

 

Datetime

 

 

X

 

 

 

 

 

 

Float

 

 

 

 

X

 

 

 

 

Int

 

 

 

 

 

X

 

 

 

Money

 

 

 

X

 

 

 

 

 

NText

 

 

 

 

 

 

X

 

 

Numeric

 

 

 

X

 

 

 

 

 

NVarchar

 

 

 

 

 

 

X

 

 

NChar

 

 

 

 

 

 

X

 

 

Real

 

 

 

 

 

 

 

X

 

Uniqueidentifier

 

 

 

 

 

 

 

 

X

 

 Note

·         The following SQL data types are not allowed for mapped storage:

·         Binary

·         Char

·         Decimal

·         Image

·         Smalldatetime

·         Smallint

·         Smallmoney

·         SQLVariant

·         Sysname

·         Text

·         Tinyint

·         Varbinary

·         Varchar

Strongly-typed and weakly-typed properties

You can think of any class property as a name-value pair. Strongly typed properties are properties defined as part of a class definition. Weakly typed properties are properties that have not been added as class properties to a class, but instead are placed in a string-keyed indexer (this[string] in C#) as a name-value pair. This functionality is provided in many (but not all) of the base Orders classes for backward compatibility with legacy COM-based pipeline components. Normally, weakly typed properties are serialized into a single binary large object (BLOB) as part of saving to storage. Weakly typed properties whose names start with an underscore and those that are not serializable, are not serialized.

For instance, setting a weakly typed property _currency in an instance of OrderForm would look like the code below. The “_currency” weakly-typed property, because it starts with an underscore, would not be serialized as part of saving the weakly-typed properties for storage to a SQL table.

OrderForm orderForm = new OrderForm();

orderForm["_currency"] = "USD";

 

Weakly typed properties are mapped directly to Microsoft.CommerceServer.Runtime.Dictionary entries during Pipeline Adapter marshaling to and from the Commerce Server 2002 legacy COM-based orders pipeline.

Serialization code will throw UnSupportedTypeException if a non-serializable property is found in the idexer.

 

  Note

·         You cannot use weakly typed properties with the same name as strongly typed properties or having the same name as a key in the Pipeline Adapter mapping file that maps to a strongly-typed property.

All Orders system classes support weakly typed property indexers.

 

Orders Extensibility Checklist

The following provides an overview of the steps necessary to extend the Orders system:

1.      Derive new classes when extending base orders classes, or create new classes and build an assembly containing the finished code. Take into account versioning issues and guidelines when creating extended classes.

2.      Update the Orders storage mapping file with the changes. By default, the name of this mapping file is OrderObjectMappings.xml.

3.      Update the Orders pipeline mapping file with the changes. By default, the name of this mapping file is OrderPipelineMappings.xml.

4.      Update the Web.config file with the changes.

5.      Use the OrderMapping.exe tool with the Orders storage-mapping file to generate Orders stored procedure code.

6.      Update the site database with table changes needed to support new classes and apply the new stored procedures.

7.      If you are modifying the OrderTemplatesAndBaskets table to add columns for property matching, you must update the SPI_OrderTemplatesAndBaskets, SPS_Search_OrderTemplatesAndBasketsBySoldToIdOrderGroupId, SPS_Search_OrderTemplatesAndBasketsByOrderGroupId, SPS_Search_OrderTemplatesAndBasketsByStatusCodeSoldToId and SPS_Search_OrderTemplatesAndBasketsByStatusCodeSoldToIdName stored procedures to add the new column names.

8.      Deploy the new assembly and mapping files.

9.      Don’t forget to upgrade the OrdersWebService. If you’re using the Customer and order manager UI.

Getting Started

In this section we’re going to cover extending Commerce Server Order System class and also how to add completely new class. For the first one I’m going to demonstrate how to extend LineItem class with a new property called WidgetDescriptionProperty. And for the second one I’m going to add a new entity called VirtualGiftCertificate which has a 1:m relationship with OrderForm class. For this we’re going to implement VirtualGiftCertificateCollection class. A new property of VirtualGiftCertificateCollection type will be added to the OrderForm class which requires extending the OrderForm class also. This collection can contain one or more instances of VirtualGiftCertificate. Code for these samples can be found in Commerce Server 2007 Install folder under Sdk\Samples\OrdersExtensibility.

 

Before you begin extending the Orders system, you must first create a new solution.

To create a new solution

1.      In the Visual Studio.NET window, click File, click New, and then click Project.

In the New Project window, choose the Visual C# Class Library Project. This creates a new managed code assembly as shown in the following figure.

2.      In the New Project dialog box, in the Name field, type the name of the project, for example: MyOrders and click OK.

After you create the project, you must add a reference to the Commerce Server Base Class Library (BCL).

3.      In the Microsoft Visual Studio .NET window, click Project, and then click Add Reference.

In the Select Component window, click the Microsoft.CommerceServer.Runtime.dll assembly.

4.      You can browse for the Microsoft.CommerceServer.Runtime.dll assembly under the “Microsoft Commerce Server 2007\Assemblies” folder on the installation drive.

5.      In the Add New Item – MyOrders window, right-click the MyOrders project, and then click Add New Item.

In the Add New Item – MyOrders dialog box, click Class to add a new C # class, and name it MyLineItem using the Name field as shown in the following figure. This creates a new file in the project called MyLineItem.cs.

 

Extending the LineItem Base Class

The following information and source code demonstrates how to extend the LineItem object by a single property and store it in the same table as the other properties. By default, the LineItem object is stored in the LineItems table.

Step 1: Derive a New LineItem Class

Derive a new LineItem class and add a new strongly typed property as shown in the following code.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1  namespace MyOrdersClasses

2  {

3  [Serializable]

4  public class MyLineItem : LineItem

5  {

6    private string widgetDescriptionProperty;

7    public MyLineItem() : base()

8    {

9      this.widgetDescriptionProperty = "No Description";

10   }

11   public string WidgetDescriptionProperty

12   {

13     set

14     {

15       if (value != null)

16         value = value.Trim();

17       SetDirty(value);

18       this.widgetDescriptionProperty = value;

19     }

20     get

21     {

22       return this.widgetDescriptionProperty;

23     }

24   }

25   protected MyLineItem(SerializationInfo info, StreamingContext context) : base(info, context)

26   {

27     try

28     {

29       widgetDescriptionProperty = info.GetString("widgetDescriptionProperty");

30     }

31     catch(SerializationException se)

32     {

33       // Handle different versions here

34     }

35   }

36   [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]

37   public override void GetObjectData(Serialization info, StreamingContext      context)

38   {

39     base.GetObjectData(info, context);

40     info.AddValue("widgetDescriptionProperty", widgetDescriptionProperty);

41   }

42 }

43

44 }  // End namespace MyOrdersClasses

 

Line 1: Define your own namespace for derived classes. Configuration information you change will include this namespace name.

Line 3: Your extended class must be marked with the Serializable attribute to support serialization.

Line 5: LineItem is a public, extensible class. You create a custom implementation of this class called MyLineItem.

Line 6: You back up the public property WidgetDescriptionProperty with a private string field named widgetDescriptionProperty.

Lines 7-10: Each class must implement a default constructor. This must in turn call the constructor for the base object.

Line 9: Each custom property exposed must have an appropriate default value set during construction of the object.

Line 11: You create a strongly typed, public property called WidgetDescriptionProperty, which is a read/write property.

Lines 13-19: The set method for this strongly typed property can perform any custom validation on the value for the property. It trims any spaces from the beginning and end of the value, and calls the public method of SetDirty with the value of what is being set. The SetDirty method performs automatic maximum length validation of string values against their respective column widths in underlying storage, and allows for optimizations within the Orders system and updates the appropriate DateTime stamps.

Lines 20-23: The get method for this strongly typed property can perform any custom actions necessary.

Line 25: Each class must implement ISerializable. As a part of the ISerializable interface, you must implement a constructor that takes streaming information. The constructor must call the base() implementation. The base implementation is responsible for setting the values of all the base class properties.

Lines 25-35: The deserialization constructor allows for setting the values of the object. The derived constructor for the class is responsible for setting the values of its own properties—in this case, the field that backs the WidgetDescriptionProperty.

Lines 31-34: In case the code is deserializing a previous version of the object, when setting the values, it should handle the case when the property does not exist in the serialized stream.

Lines 36-41: When serializing the object, call the ISerializable method ISerializable.GetObjectData. This method calls the base implementation first. The implementation of ISerializable.GetObjectData is responsible for serializing all of the properties that should be stored from an object. The security demand attribute on GetObjectData is the minimum recommended security demand.

This example contains several important points of which you should be aware. They are as follows:

·         An explicit field named widgetDescriptionProperty backs the strongly typed property, as opposed to the value coming from a method or another object. You can calculate the value of the field at the time you access the get method.

·         There are no Indexer properties defined.

All strongly typed properties are writeable (for example, implemented as get and set methods).

Step 2: Modify the Orders Storage Mapping File

You have created a new assembly containing a class derived from LineItem. Next, you must revise the Orders system storage-mapping file to account for the changes you made. The default version of the mapping file is stored in the virtual root of the CSharpSite sample site included with the Commerce Server 2007, in the OrdersObjectMappings.xml file. Rename this file to indicate that it is a custom version. Additionally, update the linkage in the Web.config file (the <MappingFiles> XML tag StorageMappingFilename attribute) so the site points to the new file name. For the purposes of this sample, modify the existing file. The following descriptions show excerpts from the file and indicate recommended changes

 

Modifying the SQL Table Definition

The following is a portion of the table definition for the LineItems table.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Table Name="LineItems">

2  <Columns>

...

3   <Column Name="WidgetDescriptionProperty" DataType="nvarchar" Precision="64" IsNullable="true" />

4   <Column Name="MarshalledData" DataType="image" IsNullable="true" />

5  </Columns>

 

Add a new column (line 3) providing storage for your new property, which requires use of the nvarchar SQL data type (as opposed to varchar).Choose a maximum length in the Precision attribute that is appropriate for the maximum string length you expect for this property. The IsNullable attribute corresponds to the null column constraint in the SQL language. Since you allow null values for the property in the class code, you must make this column null-able.

Note that in this sample you do not rename the table; however, you do change class information to point to the new class name. When you regenerate the Orders system stored procedure code later, you will be responsible for generating your own SQL script (or using the SQL Server user interface) to physically add the new column to the existing LineItems table. You must add the column with the same attributes as above; in other words, it must be named the same, be of type nvarchar(64), and have a null column constraint.

 

Modifying the Class Definition

Next, you make changes to the class name and members in the mapping file to point to MyLineItem instead of LineItem. The following is a modified section of the file containing the class definition for LineItem.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Class Name="MyLineItem">

2  <Property Name="LineItemID" />

...

3  <Property Name="OrderLevelDiscountsApplied" />

4  <Property Name="ItemLevelDiscountsApplied" />

5  <Property Name="WidgetDescriptionProperty" />

6 </Class>

 

On line 1, change the class name from LineItem to MyLineItem. On line 5, add a member definition for the new property WidgetDescriptionProperty.

 

Modifying Collection Relationships

Next, you must modify the parent-child collection relationships for the classes.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1  <CollectionRelationships>

...

2   <CollectionRelationship Name="LineItems" ParentClass="OrderForm" ParentProperty="LineItems" ChildClass="MyLineItem" />

3   <CollectionRelationship Name="LineItemDiscountsApplied" ParentClass="MyLineItem" ParentProperty="OrderLevelDiscountsApplied" ChildClass="DiscountApplicationRecord" />

4   <CollectionRelationship Name="LineItemDiscountsApplied" ParentClass="MyLineItem" ParentProperty="ItemLevelDiscountsApplied" ChildClass="DiscountApplicationRecord" />

...

5  </CollectionRelationships>

 

Above is a modified version of the CollectionRelationships section of the mapping file. Change the child relationship from LineItem to OrderForm.LineItems to point instead to MyLineItem (line 2), and the parent relationships from LineItem.OrderLevelDiscountsApplied and LineItem.ItemLevelDiscountsApplied to the DiscountApplicationRecord class (lines 3, 4) to point to MyLineItem.

 

Modifying Class-Table Mappings

Next, you modify the mapping from the LineItem class to the LineItems table. The following is a modified excerpt from the <Mappings> section of the file:

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <ClassTableMap Class="MyLineItem" Table="LineItems">

2  <PropertyMap Property="OrderGroupID" Column="OrderGroupID" />

...

3  <PropertyMap Property="ModifiedBy" Column="ModifiedBy" />

4  <PropertyMap Property="WidgetDescriptionProperty" Column="WidgetDescriptionProperty" />

5 </ClassTableMap>

 

Change the class name in the mapping to MyLineItem, and add a new field mapping between MyLineItem.WidgetDescriptionProperty and LineItems.WidgetDescriptionProperty. This new copy of the file should replace the old version in the virtual root of the site.

Step 3: Modify the Orders Pipeline Mapping File

Changes to the Orders classes also require changing pipeline mapping information defined in the OrderPipelineMappings.xml file. You use this file to describe how instances of classes used with the Orders system are marshaled to and from legacy Commerce Server IDictionary instances for use with the legacy order pipeline. The changes required are similar in spirit though not in format, to the changes made to the object-mapping file. First, you change the parent-child collection relationship from OrderForm.LineItems to the LineItem class. The following excerpt from the OrderPipelineMappings.xml file was modified to point to the MyLineItem class:

<Class Name="OrderForm">

 <Property Name="OrderFormID" DictionaryKey="orderform_id"/>

...

 <Collection Name="LineItems" DictionaryKey="items" KeyType="SimpleList" referTo="MyLineItem"/>

...

</Class>

 

The Collection tag describes a parent-child collection relationship. The name attribute describes the property name of the collection within the parent. The dictionaryKey attribute indicates the name of the key within the IDictionary to which the collection should map. The keyType attribute indicates the type of legacy Commerce Server collection that you should used to hold the contents of the collection; in this case, you are mapping to an ISimpleList. The referTo attribute indicates the class type contained within the collection. You change the referTo value to MyLineItem since you are switching to MyLineItem instances. The pipeline-mapping file also contains class definitions used for mapping information. The following is a relevant portion of the LineItem class definition, modified to show your new class name and to add your new property. The name to which the property is mapped can be anything that does not conflict with another property's name. The convention is to use the older, non-Pascal naming convention when translating into a pipeline context, so you use the name widget_description_property:

<Class Name="MyLineItem">

...

<Property Name="WidgetDescriptionProperty" DictionaryKey="widget_description_property"/>

 <Collection Name="ItemLevelDiscountsApplied" DictionaryKey="_itemlevel_discounts_applied" KeyType="SimpleList" ReferTo="DiscountApplicationRecord"/>

 <Collection Name="OrderLevelDiscountsApplied" DictionaryKey="_orderlevel_discounts_applied" KeyType="SimpleList" ReferTo="DiscountApplicationRecord"/>

</Class>

Step 4: Modify the Web.config File

You must modify the Web.config file Orders information to change the definition of a LineItem to MyLineItem. The Web.config file contains an <Orders> section that contains a <Types> subsection. The <Types> subsection contains individual type definitions that relate an Orders class to its current derived class name and provides information about the assembly to use to find the class. The following is the relevant <Types> subsection with the modifications you will need for this sample:

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Types>

2  <Type Key="Basket" UserTypeName="Basket" AssemblyType="GAC" NameSpace="Microsoft.CommerceServer.Runtime.Orders" Assembly="Microsoft.CommerceServer.Runtime, Version=5.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

...

3  <Type Key="LineItem" UserTypeName="MyLineItem" AssemblyType="Local" NameSpace="MyOrdersClasses" Assembly="MyOrders" />

...

4 </Types>

 

Line 2 shows a typical type definition for an original Orders type. 

The changes you made on line 3 include the following:

·         UserTypeName: You change this to MyLineItem to match your new class. The Key attribute remains unchanged since its value indicates the original Orders class from which you are deriving.

·         AssemblyType: You are using the Local value, which implies that you are going to deploy the assembly locally in the virtual root of the Web application.

·         NameSpace: Change to the MyOrdersClasses namespace.

Assembly: Change to a local assembly deployment.

 

Step 5: Use OrderMapping.exe on the Storage File

OrderMapping.exe is a tool provided with Commerce Server 2007. It provides pre-deployment validation of a storage-mapping file against a Web.config file and the assemblies specified in it. It also uses the information gathered to generate Orders stored procedures used for loading and saving mapped classes.

Because this sample uses local deployment of the assembly, you must run OrderMapping.exe from a directory containing the assembly. The command line syntax to use is as follows:

OrderMapping.exe /w <web.config path>

 

For instance, if you copy the MyOrders.dll assembly into the virtual root directory of the Web application, along with your new mapping files and the modified Web.config file, you could execute this command from that directory as follows:

OrderMapping.exe /w web.config

 

OrderMapping.exe opens the Web.config file, reads the type definitions in the Orders section, loads the assemblies, and performs a large number of validations between the contents of the mapping file and the assemblies. The OrderMapping.exe prints warnings, errors, and informational messages to the console. These same checks are performed at load time of a Web application that uses Orders, but in this case, if an error is found in the mapping an exception is thrown, aborting load of the application. The output of this command is a new SQL data definition language file named OrdersStorage.sql.

 

Step 6: Modify the Existing Database

In the Commerce Server 2007, you must perform modifications to existing tables. Assuming you apply this sample to the CSharpSite sample provided with the CS2007, this means you must modify the CSharpSite_transactions database, and add a new column named WidgetDescriptionProperty of type nvarchar(64) to the LineItems table.

After updating the table definitions, you must update the stored procedures in the database. Load the OrdersStorage.sql file into the SQL Server Query Analyzer and run it against the site database (CSharpSite_transactions in the CSharpSite demonstration site provided with the CS2007).

 

Step 7: Deploy the New Assembly and Mapping Files

There are several more steps to completing the changes needed to use MyLineItem from ASP.NET-based code. Note that these steps provide a simple deployment scheme using one computer. For a more advanced deployment, you can use the Commerce Server Site packager (Pup).

As with any .NET-based assembly code, there are two ways to deploy—locally and through the Global Assembly Cache (GAC). Local deployment implies that you copy the assembly DLL to the virtual root of the Web server. GAC deployment implies that the assembly is strongly named (signed with a public-private key pair) and that you use the gacutil.exe to provide a link to the assembly through the global assembly cache. Either method is usable as long as the web.config type definition contains a valid assembly locator string to find the assembly.

  Note

·         If you did not copy the changes you made to the XML files to the virtual root, then you should copy them there now.

Changing the Web.config file causes a reload of settings (including re-reading of the OrderObjectMappings.xml file). Reset Microsoft Internet Information Services (IIS) to ensure that the settings and XML files are re-read.

 

Implementing Virtual Gift Certificates

The following topics explain how to add a new collection to the existing OrderForm class.

Step 1: Create the New Class

In Visual Studio .NET you create a new class to hold information about the Virtual Gift Certificates. Then, you create a collection that models a one-to-many relationship between an OrderForm and a VirtualGiftCertificate as shown in the following code. Note that this code is abbreviated.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1   namespace MyOrdersClasses

2   {

3   [Serializable]

4   public class VirtualGiftCertificate : MappedStorageBase, ISerializable

5   {

6     private const int currentClassRevision = 1;

7     private int virtualGiftCertificateID;

8     private int giftCertificateNumber;

9     private int giftCertificatePin;

10    private Decimal price;

11    private Decimal balanceAmount;

12    private Guid orderGroupID;

13    private Guid orderFormID;

14    private OrderForm parentOrderForm;

15    public VirtualGiftCertificate() : base()

16    {

17      virtualGiftCertificateID = -1;

18      orderGroupID = Guid.Empty;

19      orderFormID = Guid.Empty;

20    }

21    protected VirtualGiftCertificate(SerializationInfo info, StreamingContext context)

22    {

23      this.orderFormID = (Guid)info.GetValue("OrderFormID", typeof(Guid));

24      this.orderGroupID = (Guid)info.GetValue("OrderGroupID", typeof(Guid));

25      this.virtualGiftCertificateID = (int)info.GetValue("VirtualGiftCertificateID", typeof(int));

26      this.giftCertificateNumber = (int)info.GetValue("GiftCertificateNumber", typeof(int));

27      this.giftCertificatePin = (int)info.GetValue("GiftCertificatePin", typeof(int));

28      this.price = (Decimal)info.GetValue("Price", typeof(Decimal));

29      this.balanceAmount = (Decimal)info.GetValue("BalanceAmount", typeof(Decimal));

30      this.parentOrderForm = (OrderForm)info.GetValue(“ParentOrderForm”, typeof(OrderForm));

31    }

32    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]

33    public void GetObjectData(SerializationInfo info, StreamingContext context)

34    {

35      info.AddValue(“Ver”, currentClassRevision);

36      info.AddValue("OrderFormID", this.orderFormID);

37      info.AddValue(“ParentOrderForm”, this.parentOrderForm);

38      ...

39    }

40    public int VirtualGiftCertificateID

41    {

42      set

43      {

44        SetDirty(value);

45        virtualGiftCertificateID = value;

46      }

47      get

48      {

49        return virtualGiftCertificateID;

50      }

51    }

52    ...

53    public Guid OrderGroupID

54    {

55      set

56      {

57        SetDirty(value);

58        orderGroupID = value;

59      }

60      get

61      {

62        return orderGroupID;

63      }

64    }

65    public Guid OrderFormID

66    {

67      set

68      {

69        SetDirty(value);

70        orderFormID = value;

71      }

72      get

73      {

74        return orderFormID;

75      }

76    }

77    public void override SetDirty(object propertyValue)

78    {

79      // Check to see if StorageLoadInProgress is set - if so, skip

80      // processing of this call.

81      if (!StorageLoadInProgress)

82      {

83        // Call the base method first to allow automated value checking

84        // to throw an exception if applicable.

85        base.SetDirty(propertyValue);

86

87        // No local last modified time to update, so simply call

88        // the parent.

89        if (this.parentOrderForm != null)

90          this.parentOrderForm.SetChildDirty();

91      }

92    }

93    public void override SetChildDirty()

94    {

95      // Pass the call on to the parent if not loading.

96      if (!StorageLoadInProgress && this.parentOrderForm != null)

97        this.parentOrderForm.SetChildDirty();

98    }

99    public void override SetChildDirty(DateTime modificationTime)

100   {

101     // Pass the call on to the parent if not loading.

102     if (!StorageLoadInProgress && this.parentOrderForm != null)

103       this.parentOrderForm.SetChildDirty(modificationTime);

104   }

105   public override void SetParent(Object parentObject)

106   {

107     if (parentObject == null)

108     {

109       this.parentOrderForm = null;

110       this.orderGroupID = Guid.Empty;

111       this.orderFormID = Guid.Empty;

112     }

113     else

114     {

115       this.parentOrderForm = (OrderForm)parentObject;

116       this.orderFormID = this.parentOrderForm.OrderFormID;

117       if (this.ParentOrderForm.ParentOrderGroup != null)

118         this.orderGroupID = this.parentOrderForm.ParentOrderGroup.OrderGroupID;

119     }

120   }

121 }

122 }  // End namespace MyOrdersClasses

 

Line 1: Define your own namespace for derived classes and configuration. Information you change in this sample will include this namespace name.

Line 3: The class must support the Serializable attribute.

Line 4: The name of the class is VirtualGiftCertificate. This is a non-collection class and therefore must derive from and implement the abstract portions of the Microsoft.CommerceServer.Runtime.MappedStorageBase abstract base class. It may also implement ISerializable if you wish to use the default serializer; the sample implements the interface in order to support serialization versioning.

Line 6: This private integer indicates the current revision level of this class, and is used for versioning support.

Lines 7-11: These are custom fields specific to this object. If a field should not be stored during Basket and OrderTemplate serialization, it should be marked with the NonSerialized attribute (this is not demonstrated in this sample).

Lines 12-13: You must have a field backing the OrderGroupID property, which is a copy of the grandparent OrderGroup.OrderGroupID property. You also must have a backing field for the key of the direct parent (OrderFormID).

Line 14: In addition, this object holds a reference to the parent object instance that contains this object, in this case an OrderForm. You will need this later so you can notify the parent of changes (for time stamp updates and query optimization).

Lines 15-20: The storage code requires a default constructor. As long as a default constructor exists, you can use other constructors. It should call the base constructor (the default constructor for MappedStorageBase). The constructor should initialize all of the custom fields.

Lines 21-31: A deserialization constructor is required for ISerializable support. See the full implementation in the sample code file for a complete example of how to implement this. The only extendable Orders class that does not need to implement a deserialization constructor is PurchaseOrder, which is the root class for storage through the mapped storage system and is not saved through serialization to the same table the Basket and OrderTemplate classes are saved to.

Lines 32-39: GetObjectData is required for ISerializable support. The security demand attribute shown is the minimum recommended security demand for GetObjectData implementations. Note the addition of the currentClassRevision name-value pair to the serialization data; this revision may be used in a later class revision to handle backward compatibility for persisted, serialized data. The full implementation of this method is available in the sample code.

Lines 40-51: This is an example of a strongly typed property. It must implement both a getter and setter in order to be compatible with the Orders storage system in Commerce Server 2007. SetDirty is called to cause change notifications to propagate to the parent.

Lines 53-76: Implement strongly typed properties for the parent order form ID and grandparent order group ID like any other property, including calls to SetDirty.

Lines 77-92: MappedStorageBase.SetDirty implementation. This method is called by class property set accessor and internal class logic when class instance contents change, to update local and parent change information. The propertyValue parameter is the value provided to the property set accessor. This method serves several purposes, as listed:

·         It allows the local class to update its own "last modified" DateTime property (if present) in a common fashion.

·         When implemented according to guidelines in the documentation for MappedStorageBase.SetDirty, it ensures that modification date updates propagate to parent classes by calling SetChildDirty on the parent.

·         It allows the Orders system to support optimistic concurrency in the site database.

·         It allows the storage implementation to optimize SQL update queries to minimize thrashing and the amount of data marshaled and sent across the network.

·         When used with the StorageLoadInProgress Boolean value in the MappedStorageBase base class, it ensures that LastModified times are not updated while loading from storage.

·         When base.SetDirty(object) is called, automatic string property length validation is performed against column widths specified in the storage mapping file. See the documentation on MappedStorageBase.SetDirty for more information.

Lines 93-104: MappedStorageBase.SetChildDirty method implementations. You implement these despite that this class is not currently a parent in a collection relationship (for example, it does not contain any collections that are mapped). These methods are called by child instances from their SetDirty implementations, just as you call these methods on OrderForm from your SetDirty implementation. These calls would update a local last modified timestamp if you had one, and then pass the call upward to the parent.

Lines 105-120: MappedStorageBase.SetParent method implementation. This is called during OrderGroup conversion (for example, from a Basket to a PurchaseOrder), when adding the object or a parent to a collection, or when removing the object from a collection, to propagate the parent change throughout the class hierarchy. The object reference parameter is a reference to the direct parent (OrderForm in this case). You cast it to the correct type, and then use the reference to fill in your order form ID copy, the order group ID copy, and the parent order form reference.

 

Step 2: Create a New Collection Class

 

Next, you create a new collection class to hold a set of virtual gift certificates. The following code is an excerpt from the full sample.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1  namespace MyOrdersClasses

2  {

3  [Serializable]

4  public class VirtualGiftCertificateCollection : ICollection, ISerializable

5  {

6    private ArrayList virtualGiftCertificates;

7    internal OrderForm parentOrderForm;

8    public VirtualGiftCertificateCollection(OrderForm parentOrderForm)

9    {

10     this.virtualGiftCertificates = new ArrayList();

11     this.parentOrderForm = parentOrderForm;

12   }

13   public VirtualGiftCertificateCollection(SerializationInfo info, StreamingContext context)

14   {

15     this.virtualGiftCertificates = (ArrayList)info.GetValue("VirtualGiftCertificates", typeof(ArrayList));

16     this.parentOrderForm = (OrderForm)info.GetValue(“ParentOrderForm”, typeof(OrderForm));

17   }

18   [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]

19   public void GetObjectData(SerializationInfo info, StreamingContext context)

20   {

21     info.AddValue("VirtualGiftCertificates", this.virtualGiftCertificates);

22     info.AddValue(“ParentOrderForm”, this.parentOrderForm);

23   }

24   public int Count

25   {

26     get

27     {

28       return this.virtualGiftCertificates.Count;

29     }

30   }

31   ...

32   public IEnumerator GetEnumerator()

33   {

34     return virtualGiftCertificates.GetEnumerator();

35   }

36   public VirtualGiftCertificate this[int index]

37   {

38     get

39     {

40       return (VirtualGiftCertificate)virtualGiftCertificates[index];

41     }

42   }

43   public void Add(VirtualGiftCertificate vgc)

44   {

45     // Check to see if the VGC is already a member of a collection.

46     if (vgc.ParentOrderForm != null)

47       throw new ArgumentException("VGC already member of collection");

48     vgc.SetParent(this.parentOrderForm);

49     this.virtualGiftCertificates.Add(vgc);

50     parentOrderForm.SetChildDirty();

51   }

52   public void Remove(VirtualGiftCertificate vgc)

53   {

54     ...

55     virtualGiftCertificates.Remove(vgc);

56     vgc.SetParent(null);

57     parentOrderForm.SetChildDirty();

58   }

59 }

60 }  // End namespace MyOrdersClasses

 

Line 3: The class must be marked with the Serializable attribute.

Line 4: You must derive the collection from ICollection. Collections used in mapped storage and the Pipeline Adapter must implement list-style collection semantics instead of dictionary-style semantics. Implement ISerializable if you do not wish to use the default serializer. You do not need to derive the collection from MappedStorageBase since collection classes are not directly mapped for Orders storage.

Lines 6-7: The collection holds some internal data structure to hold the actual VirtualGiftCertificate instances, which uses an ArrayList. You also store a reference to the parent object.

Lines 8-12: You implement a custom constructor that you call from your new version of OrderForm.

Lines 13-17: A deserialization constructor is required for ISerializable support. This implementation is similar to the deserialization constructor you created for the VirtualGiftCertificate class.

Lines 18-23: GetObjectData is required for ISerializable support. See the full implementation in the sample code and the description for VirtualGiftCertificate.GetObjectData for more information. The security demand attribute shown is the minimum recommended for GetObjectData implementations.

Lines 24-35: You must implement the ICollection interface, including Count, SyncRoot, IsSynchronized, and GetEnumerator. This section shows a Count and GetEnumerator implementation. The GetEnumerator implementation must return a list-based IEnumerator (as opposed to an IDictionaryEnumerator).

Lines 36-42: If the collection internally is holding the items with an IList derived data structure, such as ArrayList, an indexer by integer index is mandatory.

Lines 43-51: This public Add method takes a single parameter of the object type that the collection holds. It calls VirtualGiftCertificate.SetParent with the collection's parent reference. This forces propagation of parent OrderForm ID and reference, and grandparent OrderGroupID, through the VirtualGiftCertificate (and any children, if VirtualGiftCertificate itself had collections of other classes). To prevent errors where a VirtualGiftCertificate is added to more than one collection, check for the parent reference to be null before adding the instance to the collection.

Lines 52-58: A public Remove method must exist that accepts the object to be removed. Call the parent SetChildDirty method when the removal succeeds. Set the parent on the removed object to null to be compatible with the Add logic and allow this instance to be added to a collection later.

 

Step 3: Derive a New OrderForm Class

 

In Visual Studio .NET, you derive a new OrderForm object and add the new virtual gift certificates collection as shown in the following code.

  Note

·         The line numbers appear for clarity; remove them before using this code.

1  namespace MyOrdersClasses

2  {

3  [Serializable]

4  public class MyOrderForm : OrderForm

5  {

6    private GiftCertificateCollection giftCertificates;

7    public MyOrderForm() : base()

8    {

9      giftCertificates = new GiftCertificateCollection(this);

10   }

11   public MyOrderForm(SerializationInfo info, StreamingContext context) : base(info, context)

12   {

13     this.giftCertificates = (GiftCertificateCollection)info.GetValue("GiftCertificates");

14   }

15   [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]

16   public override void GetObjectData(SerializationInfo info, StreamingContext context)

17   {

18     base.GetObjectData(info, context);

19     info.AddValue("GiftCertificates", this.giftCertificates);

20   }

21   public GiftCertificateCollection GiftCertificates

22   {

23     get

24     {

25       return giftCertificates;

26     }

27   }

28   public override void SetParent(object parentObject)

29   {

30     // We must call the base version to ensure propagation to base

31     // class members and collections.

32     base.SetParent(parentObject);

33

34     // Propagate the SetParent to the custom collection.

35     foreach (VirtualGiftCertificate vgc in this.giftCertificates)

36       vgc.SetParent(this);

37   }

38 }

39 }  // End namespace MyOrdersClasses

 

Line 3: The class must be marked with the Serializable attribute.

Line 4: New class MyOrderForm derives from the main Orders system OrderForm class.

Line 6: Add a private field to hold the new collection.

Lines 7-10: Override the default constructor and call the base constructor, adding a call to create your new collection.

Lines 11-20: Override the ISerializable interface members to handle the collection field. The GetObjectData security demand attribute is the minimum recommended for a GetObjectData implementation.

Lines 21-27: Expose a strongly typed property for the new collection.

Lines 28-37: You must override base SetParent implementation to ensure parent propagation occurs for all members of the added collection. You must invoke the base version of the function to ensure the propagation is performed for members of the base Orders class.

 

Step 4: Update the Storage Mapping File

You have created a new assembly containing your three new classes. Next, you must revise the Orders system storage-mapping file to account for the changes you made. The default version of the mapping file is stored in the virtual root of the CSharpSite sample site included with the CS2007, in the OrdersObjectMappings.xml file. When modifying this file you can choose to rename it to indicate that it is a custom version, and update the linkage in the Web.config file (the <MappingFiles> XML tag StorageMappingFilename attribute) for the site to point to the new file name. For this sample, you simply modify the existing file. The sample code contains a full, modified version of the file. The following excerpts from the file indicate the changes to be made.

Updates to Add the VirtualGiftCertificate Class

The following are changes that you must make to add the VirtualGiftCertificate class mapping information to the mapping file. Additionally, you update OrderForm to change its name and add its new collection member. Adding a class requires the addition of a new table, a new class definition, and mappings between the two. The following is the new table definition, which you place in the <Tables> section of the file:

  Note

·         The line numbers appear for clarity; remove them before using this code.

1  <Table Name="VirtualGiftCertificates">

2   <Columns>

3    <Column Name="VirtualGiftCertificateID" DataType="int" />

4    <Column Name="OrderFormID" DataType="uniqueidentifier" IsNullable="false" />

5    <Column Name="GiftCertificateNumber" DataType="int" />

6    <Column Name="GiftCertificatePin" DataType="int" />

9    <Column Name="Price" DataType="money" />

10   <Column Name="BalanceAmount" DataType="money" />

11   <Column Name="OrderGroupID" DataType="uniqueidentifier" />

12  </Columns>

13  <Constraints>

14   <PrimaryKey Name="PK_VirtualGiftCertificates">

15    <ColumnRef Name="VirtualGiftCertificateID" />

16   </PrimaryKey>

17   <ForeignKey Name="FK_VirtualGiftCertificates_OrderForm" ForeignTable="OrderForms" CascadeDelete="true">

18    <ColumnMatch Name="OrderFormID" ForeignName="OrderFormID" />

19   </ForeignKey>

20  </Constraints>

21 </Table>

 

The table definition describes the columns and constraints on the table. The column definitions are straightforward, a translation from the class members to columns and their SQL data types. It is worth noting that not all class properties must map to a column if, for instance, a property is used during site code execution for temporary values.

The foreign key constraint listed in the table definition is required when you have a collection relationship between a parent and child class, and to enable use of relational integrity among the Orders classes. You store the parent OrderForm.OrderFormID in a column and create a foreign key relationship to the order form's table entry. Generated Orders storage stored procedure code takes into account any foreign key relationships when generating code to table rows from a hierarchy of objects.

Next, create the class definition. Add a new <Class> tag within the <Classes> section of the mapping file, as the following code shows:

<Class Name="VirtualGiftCertificate">

  <Property Name="VirtualGiftCertificateID" />

  <Property Name="GiftCertificateNumber" />

  <Property Name="GiftCertificatePin" />

  <Property Name="Price" />

  <Property Name="BalanceAmount" />

  <Property Name="OrderGroupID" />

  <Property Name="OrderFormID" />

</Class>

 

This class definition is straightforward. Include only the class members that you are mapping to storage; this definition is not intended to be a full duplication of the entire set of reflection information available about the class.

Then, add the mappings from class members to columns. You add a new <ClassTableMap> node to the <Mappings> section in the mapping file, as the following code shows:

<ClassTableMap Class="VirtualGiftCertificate" Table="VirtualGiftCertificates">

  <PropertyMap Property="OrderGroupID" Column="OrderGroupID" />

  <PropertyMap Property="OrderFormID" Column="OrderFormID" />

  <PropertyMap Property="VirtualGiftCertificateID" Column="VirtualGiftCertificateID" />

  <PropertyMap Property="GiftCertificateNumber" Column="GiftCertificateNumber" />

  <PropertyMap Property="GiftCertificatePin" Column="GiftCertificatePin" />

  <PropertyMap Property="BalanceAmount" Column="BalanceAmount" />

</ClassTableMap>

 

Updates for MyOrderForm

Start by modifying the information for OrderForm to point to your new class and include your new collection member. First, you modify the class definition for OrderForm:

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Class Name="MyOrderForm">

2  <Property Name="OrderFormID" />

...

3  <Property Name="PromoCodeRecords" />

4  <Property Name="GiftCertificates" />

5 </Class>

 

On line 1, change the name OrderForm to MyOrderForm to change to the new class implementation. On line 4, add a new member entry for the new collection member GiftCertificates.

Next, modify the parent-child collection relationships for the classes. Rename instances of OrderForm to MyOrderForm, and add a new relationship (at line 6) to describe the one-to-many relationship between MyOrderForm.GiftCertificates and VirtualGiftCertificate, as shown in the following code:

1  <CollectionRelationships>

2   <CollectionRelationship Name="PurchaseOrderOrderForms"  ParentClass="PurchaseOrder" ParentProperty="OrderForms" ChildClass="MyOrderForm" />

...

3   <CollectionRelationship Name="LineItems" ParentClass="MyOrderForm" ParentProperty="LineItems" ChildClass="MyLineItem" />

...

4   <CollectionRelationship Name="OrderFormShipments" ParentClass="MyOrderForm" ParentProperty="Shipments" ChildClass="Shipment" />

5   <CollectionRelationship Name="OrderFormPromoCodeRecords" ParentClass="MyOrderForm" ParentProperty="PromoCodeRecords" ChildClass="PromoCodeRecord" />

6   <CollectionRelationship Name="OrderFormGiftCertificates" ParentClass="MyOrderForm" ParentProperty="GiftCertificates" ChildClass="VirtualGiftCertificate" />

7  </CollectionRelationships>

 

The Orders storage mapping system validates that, when such a relationship exists, there is also a foreign key relationship between the VirtualGiftCertificates and OrderForms tables. You added such a relationship in the table definition above.

Replace the old version of the mapping file in the virtual root of the site with this new copy.

Step 5: Modify the Orders Pipeline Mapping File

Changes to the Orders classes also require changing mapping information defined in OrderPipelineMappings.xml file. This file describes how instances of classes used with the Orders system are marshaled to and from legacy Commerce Server IDictionary instances for use with the legacy order pipeline. The changes required are similar in spirit, though not in format, to the changes made to the object-mapping file. The pipeline adapter does not translate PurchaseOrder, Basket, or OrderTemplate instances into IDictionary format, so you do not need to make any changes related to having one of those classes' OrderForms member point to your new MyOrderForm class.

First, you change the class name used for mapping OrderForm class instances to your new class, and then you add the new GiftCertificates collection. The following code shows the changes needed to the OrderForm class definition:

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Class name="MyOrderForm">

2  <property name="OrderFormID" dictionaryKey="orderform_id"/>

...

3  <collection name="GiftCertificates" dictionaryKey="GiftCertificates" keyType="SimpleList" referTo="VirtualGiftCertificate" />

4 </Class>

 

Line 1 changes the definition of an OrderForm to a MyOrderForm. Line 3 adds the collection, with a reference to the VirtualGiftCertificate class.

Next, you add a definition for your new VirtualGiftCertificate class, as shown in the following code:

<Class name="VirtualGiftCertificate">

 <property name="VirtualGiftCertificateID" dictionaryKey="VirtualGiftCertificateID" classKey="true" />

 <property name="ShippingMethodID" dictionaryKey="shipping_method_id" />

 <property name="GiftCertificateNumber" dictionaryKey="GiftCertificateNumber" />

 <property name="GiftCertificatePin" dictionaryKey="GiftCertificatePin" />

 <property name="Price" dictionaryKey="Price" />

 <property name="BalanceAmount" dictionaryKey="BalanceAmount" />

</Class>

 

You use the classKey attribute on the VirtualGiftCertificateID property to find and update the class instance if it was created prior to being marshaled into an IDictionary to provide to the Orders pipeline. You should set the class key properties to the same as the set of primary keys used in the table definition in the class storage mapping—in this case, the ID.

 

Step 6: Modify the web.config File

You must modify the Web.config file Orders information to change the definition of LineItem to MyLineItem. The Web.config file contains an <Orders> section that contains a <Types> section. The Types section contains individual Type definitions that relate an Orders class to its current derived class name and provides information about the assembly to use to find the class. The following is the relevant Types section with the modifications you will need for this sample:

  Note

·         The line numbers appear for clarity; remove them before using this code.

1 <Types>

2  <Type Key="Basket" UserTypeName="Basket" AssemblyType="GAC" NameSpace="Microsoft.CommerceServer.Runtime.Orders" Assembly="Microsoft.CommerceServer.Runtime, Version=5.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

...

3  <Type Key="OrderForm" UserTypeName="MyOrderForm" AssemblyType="Local" NameSpace="MyOrdersClasses" Assembly="MyOrders"/>

4  <Type Key"VirtualGiftCertificate" UserTypeName="VirtualGiftCertificate" AssemblyType="Local" NameSpace="MyOrdersClasses" Assembly="MyOrders" />

...

5 </Types>

 

Line 2 shows a typical type definition for an original Orders type. A more complete description can be found in the Web.config file description.

At line 3, you change the OrderForm definition. The changes are similar to those made for the MyLineItem sample.

On line 4, you add a new definition for your new class. The Key attribute in this case is simply the name of the class—you have not derived VirtualGiftCertificate from a base Orders class. UserTypeName is also the same value. AssemblyType is Local since you have not added strong naming and GAC deployment of the assembly. NameSpace is the MyOrdersClasses namespace. Finally, the Assembly is the local-deployment assembly string for your assembly.

 

Step 7: Use OrderMapping.exe on the Storage File

See the notes from the MyLineItem sample on how to use OrderMapping.exe with the Web.config file and the assembly. You use the generated OrdersStorage.sql file to update the site database.

 

Step 8: Modify the Existing Database

In the CS2007, you perform modifications to existing tables. Assuming you apply this sample to the CSharpSite sample provided with the CS2007, this means adding the VirtualGiftCertificates table definition to the database.

CREATE TABLE [dbo].[VirtualGiftCertificates] (

  [VirtualGiftCertificateID] [INT] NOT NULL,

  [OrderFormID] [UNIQUEIDENTIFIER] NOT NULL,

  [GiftCertificateNumber] [INT] NULL,

  [GiftCertificatePin] [INT] NULL,

  [Price] [MONEY] NULL,

  [BalanceAmount] [MONEY] NULL,

  [OrderGroupID] [UNIQUEIDENTIFIER] NOT NULL

) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[VirtualGiftCertificates] WITH NOCHECK ADD

    CONSTRAINT [PK_VirtualGiftCertificates] PRIMARY KEY CLUSTERED (

        [VirtualGiftCertificateID]

    ) ON [PRIMARY],

 

    CONSTRAINT [FK_VirtualGiftCertificates_OrderForm] FOREIGN KEY (

        [OrderFormID]

    ) REFERENCES [OrderForms] (

        [OrderFormID]

    ) ON DELETE CASCADE

GO

CREATE INDEX [IX_Ord_VirtualGiftCertificates_OrderGroupID] ON [dbo].[VirtualGiftCertificates] (

    [OrderGroupID]

) ON [PRIMARY]

GO

 

The table definition is straightforward. One noteworthy item is the addition of the index on OrderGroupID. All Orders classes must define a mapping to OrderGroupID even if it is not the direct parent. You use this value as a global key during storage loading and re-population of a PurchaseOrder and all of its contained children to look up all instances in all tables that correspond to a single, root PurchaseOrder instance. Therefore, it is advisable to add an index on that column for each table corresponding to an Orders class.

After adding the table definition, you update the stored procedures in the database. Load the OrdersStorage.sql file into the SQL Server Query Analyzer and run it against the site database (CSharpSite_transactions in the CSharpSite demonstration site provided with the CS2007).

 

Step 9: Deploy the Assembly and Mapping Files

The MyLineItem sample describes the steps to deploy the assembly and the new versions of the mapping files.

 

 

Posted Wednesday, June 28, 2006 10:04 AM by kumar vinod | 13 Comments

Page view tracker