Object Relational Mapping (ORM) Using NHibernate - Part 6 of 8

Before reading this article, please go through the following articles:

  1. Object Relational Mapping (ORM) Using NHibernate - Part 1 of 8

  2. Object Relational Mapping (ORM) Using NHibernate - Part 2 of 8

  3. Object Relational Mapping (ORM) Using NHibernate - Part 3 of 8

  4. Object Relational Mapping (ORM) using NHibernate - Part 4 of 8

  5. Object Relational Mapping (ORM) Using NHibernate - Part 5 - A of 8

  6. Object Relational Mapping (ORM) Using NHibernate: Part 5 - B of 8

Coding Inheritance Of Entity Classes

Parts 1-5 of the article series detailed various types of associations representing the "has a" relationship between entity classes. Inheritance represents the "is a" relationship among entity classes. We are all familiar with this and also know if used correctly inheritance can yield the benefit of polymorphic behavior and that an interface based inheritance, if used correctly in clients, can increase flexibility, but if used incorrectly, inheritance reduces flexibility. There are four ways of mapping inheritance hierarchies in NHibernate. We will discuss the one way which gives maximum support for inheritance and polymorphism (the main benefit of capturing an inheritance heirarchy). This is called Table Per Subclass. Also note that there may be a few places where we would have used NHibernate queries in the samples for this article. It is self explanatory. We will be writing a 3-part article series on NHibernate queries later and perhaps subsequent to that, another article series that does a comparative study between NHibernate and Language Integrated Query (most popular and extensively used library) on using them, if its known that there might be sufficient interests in such a comparison study.

BACKGROUND

In Table Per Subclass inheritance mapping, each and every entity class of the hierarchy (including an abstract class) is mapped to a table. Every property of an entity class is mapped to its own table only. The "is a" relationship between the entity classes is mapped to their respective tables using the foreignkey constraint i.e. the pimarykey of the superclass is posted as a foreignkey to all the respective child classes. Objects of a particular class in the hierarchy is fetched using the "JOIN" statments (NHibernate generates SQL case statements for the hierarchy). The advantages of this scheme are many: Normalized schema (unlike the other simpler schemes like Table Per Class hierarchy which uses one table for the entire hierarchy resulting in denormalization), allows polymorphic calls using base classes (the foreignkey posted from a base class table to a child class table is used), etc. The main disadvantage is performance loss by joins for very large hierarchies. But the full hierarchy (with polymorphic support) can be captured in database tables using this technique in NHibernate. Hence I prefer this over other techniques used to capture inheritance in NHibernate.

E-COMMERCE SAMPLE SCENARIO

Before looking at the sample for inheritance, the OrderProcessing of the ecommerce example needs to be completed so that an order is made available to the ecommerce site for Payment and then Shipping (our sample class for inheritance). So first we will work on order processing and do the Shipping inheritance sample here in the next section of this same article.

ORDER PROCESSING

The product description matching customer's buying needs are selected by the customer for buying and are added to the shoppingcart of the customer. After the selections are over, the customer makes an Order out of the selections in the shopping cart and submits it to the ecommerce site along with details like fast shipping or slow parcel service and whether gift packing is required. The ecommerce site will in turn process the Order and provide the total amount to be paid, including the amount for shipping the items in an order. The customer will make a payment and buy. The ECommece Site will link the items to the paid order matching the product descriptions in the shopping cart of the customer and all these items will be shipped afterwards.

In the last part of the article series (Part 5 B) we completed the scenario of a customer selecting items for a shopping cart. Items are now in the ShoppingCart as a collection of ShoppingCartSelection instances. Each instance of ShoppingCartSelection contains a ProductDescription which is of interest to the customer and a reference to the parent ShoppingCart that this ShoppingCartSelection belongs to. Now in this section we will see how the selections in a shoppingcart is submitted as order to the ecommerce site and how it is processed further.

The constructior code of PaymentApprovedOrder in Article Part 4 - Figure 5 and the client code so far, shows how order processing is done currently for testing purposes. Now here we will refine this order processing. As a first step towards it, we will remove the order processing code from the PaymentApprovedOrder constructor and move it to the ECommerceSellerSystem class. The constructor for PaymentApprovedOrder will now contain only code for initializing associations and properties.

The ECommerceSellerSystem class currently has a method for adding ShopSelections to the Shopping Cart. Now we are adding OrderProcessing methods to it. A look at the methods in it with the client code should give the idea of what we are doing and the control flow. Please refer to the ECommerceSellerSystem class code given below. The main idea is that the ShoppingCart instance has a collection of ShoppingCartSelections each of which contains a particular ProductDescription instance that the Customer wants to buy. Note that the cart selection instance has a product description and not inventory item. This is done as explained before to avoid the problem of an item being in a cart until it's flushed or expired and not being available to customers ready to purchase it. Also this captures the e-commerce domain correctly. But other departments like the shipping department needs concrete Item instances of inventory and not ProductDescriptions.

An Order in the ECommerce system contains an OrderItems collection for items in the inventory that is ordered and the NotAvailableItemDescriptions property in it is a collection of ProductDescriptions to represent the product descriptions in the Order for which an item is not presently available in the Inventory. The ShoppingCart instance with a collection of selections has to be converted to an Order instance with the collection of items first. So when the customer wants to make an order for his shoppingcart, we process the ShoppingCart from the customer containing ShoppingCartSelections (each selection has a product description that the cutomer wants to buy) and query the ITEM table and obtain an Item in inventory matching that selection's ProductDescription and add it to the OrderItems collection, if the match is found for a particular ProductDescription. If the match is not found in Inventory to get an Item instance for a particular ProductDescription, we add the ProductDescription itself to the NotAvailableProductDescription collection of Orders. The Total Value of all items including not available product descriptions are calculated along with shipping charges and provided to the customer. Once the Customer makes a Payment, a PaymentApprovedOrder is created by the ECommerceSellerSystem class along with items for a particular PaymentApprovedOrder marked so in the inventory ITEM table. Items in an Order which is not found in the inventory may still exist as a NotAvailableProuctDescription collection and are added to a separate table (called NOTAVAILABLEPAIDORDER_PRODUCTDESCRIPTIONS) with the corresponding PaymentApprovedOrderId. They will be later processed by the e-commerce system like purchasing the required item and adding it to the inventory and then adding it to the corresponding PaymentApprovedOrder. Thus a PaymentApprovedOrder with all items for the order is created only from ProductDescription instances stored in a cart. Refer to the following code.

The code for the ECommerceSellerSystem class is:

public class ECommerceSellerSystem
{
  public ECommerceSellerSystem()
  {
  }
  
public ShoppingCartSelection AddSelectionToCustomerCart(ProductDescription product_description,ShoppingCart cart,int quantity)
   {
    
return new ShoppingCartSelection(product_description, cart,quantity);             
   }

   public Order MakeOrder(ShoppingCart cart,bool is_fast,bool is_gift_wrap)
   {
           
//***************************************
            //ISOLATION & SYNCHRO REQUIRED HERE LATER
            //***************************************
            //ProductDescriptions in Cart must be
            //Changed to Items in Inventory here
            //and a order must be created
            //Item availability in inventory must
            //be handled
            //Use PRODUCTDESCRIPTIONID which is a
            //FOREIGN KEY IN INVENTORY ITEM to pull
            //items
    Order order = new Order();
    order.OrderedByCustomer = cart.CartOfCustomer;
    order.IsGiftWrapped = is_gift_wrap;
    order.IsFastShipping = is_fast;
   
IRepository<Item> item_repo = new DBRepository<Item>();
   
try
    {
     
foreach (ShoppingCartSelection selection in cart.CartSelections)
    {
     
for (int count = 0; count < selection.Quantity; count++)
    {
   
  Item item = NewItemMatchingProductDescription(selection.CurrentProduct);
      
if (item != null)
      {
    
   item.IsShopCarted = true;
     
  item_repo.updateItem(item);
     
  order.OrderItems.Add(item);
       }
      
//else if it is null item is unavailable
       //add selection to not available list in orders collection
       else
                                   order.NotAvailableItemDescriptions.Add(selection.CurrentProduct);
      }
     }
    }
   
catch (Exception ex)
    {

     }
    
return order;
   }
  
public void DestructShoppingCartBindingForItemsInOrder(Order order)
   {
           
//************************************************************************
            //The IsCartedState of Item exists only when a order is made. At the exit
            //of making an order, the IsCarted state should be destroyed immaterial
            //of whether order was made or not. A item will never be allowed to be carted
            //permanently. A item can exist in inventory or in order but not in cart.SO,
            // Whenever a Order object is destroyed
            // call this method for that Order.It destroys the item bindings to cart.
            //If a Order goes out of scope, before it goes out
            // of scope, this method is called (example by using unload event handler etc).
            //So a item will be paid or in inventory only. When a Order is made a item can be carted, but
            //by the end of order lifetime item will cease to
            // exist in cart because we set IsShopCarted to false.
            //***************************************************************************
   try
   {
  
  IRepository<Item> item_repo = new DBRepository<Item>();
     
foreach (Item item in order.OrderItems)
     {
      item.IsShopCarted =
false;
      item_repo.updateItem(item);
      }
     }
    
catch (Exception ex)
     {
      }
    }
  
public Order ProcessAmountForOrder(Order order)
   {
    
double total_order_amount = 0;
           
//Calculate Total Payment
    foreach (Item item in order.OrderItems)
    {
     total_order_amount += item.ItemDescription.Price;
     }
    
foreach (ProductDescription desc in order.NotAvailableItemDescriptions)
     {
      total_order_amount += desc.Price;
     }
     order.OrderCost = total_order_amount;
           
//Calculate Shipping cost
     Shipping shipping = ProvideShippingInformation(total_order_amount, order.IsFastShipping, order.IsGiftWrapped);
    
if (shipping != null)
     {
    
  order.ShippingCharges = shipping.ShippingCharges;
     }
     
return order;
    }
   
public void MakeOrderPayment(Order order, Payment pay)
    {
   
  NHibernateHelper nh = NHibernateHelper.Create();
     
using (NHibernate.ITransaction transaction = nh.Session.BeginTransaction())
      {
       
IRepository<PaymentApprovedOrder> pay_order_repo = new DBRepository<PaymentApprovedOrder>();
      
IRepository<Shipping> shipping_repo = new DBRepository<Shipping>();

       MarkItemAsOrdered(order);
      
PaymentApprovedOrder paid_order = new PaymentApprovedOrder(order, pay);
       pay_order_repo.addItem(paid_order);
               
//Item will have only two states - Is Ordered or Inventory item.
                //The IsCartedState of Item exists only when a order is made. At the exit
                //of making a order, the IsCarted state should be destroyed,
       DestructShoppingCartBindingForItemsInOrder(order);
      
Shipping shipping = ProvideShippingInformation(order.OrderCost, order.IsFastShipping, order.IsGiftWrapped);
      
shipping.ShippingCharges = order.ShippingCharges;
       shipping_repo.addItem(shipping);
       transaction.Commit();
       }

     }
    
public Shipping ProvideShippingInformation(double order_amount,bool is_fast_shipping,bool is_gift_wrapped)
     {
           
//Important thing to note here is PaymentApprovedOrder is already saved in db
            //no need to process a transaction to envelope both

      Shipping shipping = null;
     
if (!is_fast_shipping)
      {
       shipping =
new ParcelService(is_gift_wrapped);
      }
     
else
      {
               
//Fast Shipping - same day delivery - no time for gift wraps
                //So only the latest date by which item should be sent - i.e today
                //which is same day as order day
       shipping = new FastShipping(DateTime.Today);
      }           

      shipping.CalculateShippingCharges(order_amount);
           
//MostImportant thing is Shipping is abstract super class -
            //having two concrete child classes
            //We did not tell NHibernate which child class we are dealing with

      return shipping;

    }
   
public Item NewItemMatchingProductDescription(ProductDescription product_description)
    {
    
Item item = null;
    
try
     {
    
//create nhibernatehelper for query
     NHibernateHelper nh = NHibernateHelper.Create();
    
string query_string = "from Item item1 where item1.ItemDescription = :description" +
                                                 
" and item1.IsOrdered = :ordered_truth_value and item1.IsShopCarted = :karted_truth_value";
    
IList<Item> items_list = nh.Session.CreateQuery(query_string).
    
  SetParameter<ProductDescription>("description", product_description).
    
  SetParameter<bool>("ordered_truth_value", false).
       SetParameter<
bool>("karted_truth_value",false).List<Item>();
    
if (items_list != null && items_list.Count > 0)
     {
      
item = items_list[0];
      }
     
catch (Exception ex)
      {

      }
     
return item;
    }
   
public void MarkItemAsOrdered(Order order)
    {
   
  //Isolation Control Required
      try
      {         
        
foreach (Item item in order.OrderItems)
        {
      
      item.IsOrdered = true;
        }        
       }
      
catch (Exception ex)
       {
      
}
     }
   } 

The code for PaymentApprocedOrder is:

public class PaymentApprovedOrder
{
       
public PaymentApprovedOrder()
        {
           
//NOTE: THIS LIST IS MAPPED TO IDBAG. SO NO ORDERING.
            PaidOrderItems = new List<Item>();
        }
       
public PaymentApprovedOrder(Order order,Payment pay)
        {
           
//NOTE: THIS LIST IS MAPPED TO IDBAG. SO NO ORDERING.
            PaidOrderItems = new List<Item>();
            CurrentOrder = order;

            // SET ONE END OF ASSOCIATION

            OrderPayment = pay;

            // SET OTHER END OF ASSOCIATION

            pay.PaidOrder = this;
           
// Set the Customer BIDEIRECTIONAL ASSOCIATION
            order.OrderedByCustomer.AddPaidOrder(this);

            foreach (Item item in order.OrderItems)
            {
               
//SET THE ORDER ITEMS
                // THE ORDER USED IN PAYMENTAPPROVEDORDER HERE
                //CONTAINS ORDERITEMS
                AddPaidItem(item);
            }

            this.NotAvailableItemDescriptions = order.NotAvailableItemDescriptions;
        }

        public virtual long PaymentApprovedOrderId { get; set; }

        public virtual Order CurrentOrder { get; set; }

        // NOTE: BIDIRECTIONAL ASSOCIATION WITH "PAYMENT"

        public virtual Payment OrderPayment { get; set; }

        //NOTE: BIDIRECTIONAL ONE-TO-MANY ASSOCIATION WITH ITEM
        //NOTE: THIS LIST IS MAPPED TO IDBAG. SO NO ORDERING.
 
       
public virtual IList<Item> PaidOrderItems { get; set; }
 
       
//NOTE: BIDIRECTIONAL MANY-TO-ONE ASSOCIATION WITH CUSTOMER
        public virtual Customer PaidByCustomer { get; set; }
       
public virtual Iesi.Collections.Generic.ISet<OrderShipment> ShipmentsOfThisPaidOrder { get; set; }
       
public virtual System.Collections.Generic.IList<ProductDescription> NotAvailableItemDescriptions { get; set; }
       
public virtual void AddPaidItem(Item item)
        {
           
//SET THE REFERENCE FOR PAYMENTAPPROVEDORDER
            //IN ITEM OBJECT TO THIS (THE PAYMENT APPROVED
            //ORDER INSTANCE TO WHICH THE item IS ADDED")
            // THIS IS FIRST END OF THE
            // ONE-TO-MANY ASSOCIATION - THE "ONE" END
            item.PaidOrder = this;
           
//ADD "item" TO THE SET PaidOrderItems
            //OTHER END OF ASSOCIATION - THE "MANY" END
            PaidOrderItems.Add(item);
        }
}


The mapping code for PaymentApprovedOrder is:

<class name="PaymentApprovedOrder" table="PAYMENTAPPROVEDORDER">
    <
id name ="PaymentApprovedOrderId" type ="long" column ="PAYMENTAPPROVEDORDERID" generator="native"/>

    <many-to-one name ="OrderPayment" lazy="false"  class ="Payment" column="PAYMENTID" unique="true" not-null="true" cascade="save-update"/>
    <
many-to-one name ="PaidByCustomer"  class ="Customer" column ="CUSTOMERID" not-null="true" insert="false" update="false" cascade ="save-update"></many-to-one>

    <idbag name="PaidOrderItems" table="PAYMENTAPPROVEDORDER_ITEMS" cascade="save-update">
      <
collection-id column="JoinTableRowId" type="long">
        <
generator class="identity" />
      </
collection-id>
      <
key column="PAYMENTAPPROVEDORDERID"></key>
      <
many-to-many class="Item" column="ITEMID" unique="true"/>
    </
idbag>

    <idbag name="NotAvailableItemDescriptions" table="NOTAVAILABLEPAIDORDER_PRODUCTDESCRIPTIONS" cascade="save-update">
      <
collection-id column="NOTAVAILABLEPRODUCTSDESCRIPTIONID" type="long">
        <
generator class="identity" />
      </
collection-id>
      <
key column ="PAYMENTAPPROVEDORDERID"></key>
      <
many-to-many class ="ProductDescription" column="PRODUCTDESCRIPTIONID"/>
    </
idbag>

  </class>

The code for the Item class is:

public class Item
{
       
public Item()
        {
            IsOrdered =
false;
            IsShopCarted =
false;
            PaidOrder =
null;
        }
       
public virtual long ItemId { get; set; }
       
public virtual bool IsOrdered { get; set; }
       
public virtual string InventorySerialCode { get; set; }
       
public virtual PaymentApprovedOrder PaidOrder { get; set; }
       
public virtual ProductDescription ItemDescription { get; set; }
       
public virtual bool IsShopCarted { get; set; }
}

The mapping code for the Item class is:

<class name ="Item" table ="ITEM">

    <id name ="ItemId" column ="ITEMID" type ="long" generator="native" />

    <property name="IsOrdered" type="bool" column="ISORDERED"/>

    <property name ="IsShopCarted" type ="bool" column ="ISSHOPCARTED" />

    <property name ="InventorySerialCode" type="string" column ="INVENTORYSERIALCODE"></property>

    <many-to-one name="ItemDescription" class="ProductDescription" column="PRODUCTDESCRIPTIONID" not-null="true" lazy="false"></many-to-one>

    <join optional="true" inverse="true" table ="PAYMENTAPPROVEDORDER_ITEMS">
      <
key column ="ITEMID" unique="true" not-null="true"/>
      <
many-to-one  class ="PaymentApprovedOrder" name ="PaidOrder" column ="PAYMENTAPPROVEDORDERID"/>  
    </
join>

  </class>

The code for the ProductDescription class is:

public class ProductDescription
{
       
public ProductDescription()
        {
            ProductUserReviews =
new List<ProductReview>();

            CartSelectionsWithThisProduct = new HashedSet<ShoppingCartSelection>();       
        }
       
public virtual long ProductDescriptionId { get; set; }
       
public virtual string ProductName { get; set; }
       
public virtual string ManufacturerName { get; set; }
       
public virtual double Price { get; set; }
       
public virtual IList<ProductReview> ProductUserReviews{ get; set; }
       
public virtual Iesi.Collections.Generic.ISet<ShoppingCartSelection> CartSelectionsWithThisProduct { get; set; }     
}


The mapping code for ProductDescription is:

<class name="ProductDescription" table ="PRODUCTDESCRIPTION" >

    <id name ="ProductDescriptionId" type ="long" column ="PRODUCTDESCRIPTIONID" generator="native"></id>

    <property name ="ProductName" type ="string" column ="PRODUCTNAME" />
    <
property name ="ManufacturerName" type ="string" column ="MANUFACTURERNAME" />
    <
property name ="Price" type ="double" column ="PRICE" />

       <list table="PRODUCTREVIEWS" name="ProductUserReviews">

      <key column="PRODUCTDESCRIPTIONID" not-null="true"></key>

      <list-index column="REVIEWLIST_POSITON"></list-index>

      <composite-element class="ProductReview">
        <
property name="UserComment" type="string" column="USERCOMMENT"/>
        <
property name="UserEmailId" type="string" column="USEREMAILID"/>
        <
property name="ProductRating" type="double" column="USERRATING"/>
      </
composite-element>     
    </
list>

    <
set name="CartSelectionsWithThisProduct" inverse="true">
      <
key column ="PRODUCTDESCRIPTIONID"></key>
      <
one-to-many class ="ShoppingCartSelection"/>     
    </
set>

  </class>

There is an important point to note here in the ECommerceSellerSystem class and the Item class. The ITEM table and Item class has a flag called IsShopCarted. This flag is set briefly when an order is processed and an Item instance is queried and obtained for a ProductDescription to show that the particular item is now attached to a ShoppingCart. The moment the Order is paid or Order Instance is going to be destroyed or Order is going to go out of scope etc. this flag must be reset to false (usually in an event handler that is triggered by the cause for the destruction of the Order instance) wherever such a thing might happen. So the lifetime of this flag to be in true is restricted to the time an Order is processed. This flag can be reset for an Order by using the method "DestructShoppingCartBindingsForItemInOrder" (even a similar method can be done for each Item in the Order later). When both the IsShopCarted flag and IsOrdered flag is set to False then an Item is available in the inventory for the order. If the IsShopCarted flag is set, an item is not available and is in a ShoppingCart which is currently undergoing Order processing but the flag will be reset to true or false immaterial of whetehr OrderProcessing is success or failure. The IsOrdered flag is set to True when an item is purchased.

The Item object is simply in various states: an Item is in Inventory, the Item is in a Cart, the Item is a PaidOrder or the Item is in Shipping. Just the flags IsShopCarted and IsOrdered will do for this sample. We will handle the state of Item IsInShipping a different way in the next article without any flag setting. But before that we need to look at how shipping is handled by the system.

The client test code is shown below:

IRepository<Item> items_repo = new DBRepository<Item>();
           
IRepository<ProductDescription> product_repo = new DBRepository<ProductDescription>();
           
IRepository<Customer> customer_repo = new DBRepository<Customer>();
           
IRepository<ShoppingCartSelection> cart_selection_repo = new DBRepository<ShoppingCartSelection>();
           
IRepository<ShoppingCart> cart_repo = new DBRepository<ShoppingCart>();

            //Create 2 product descriptions

            ProductDescription description1 = new ProductDescription { ManufacturerName = "samsung", Price = 60000, ProductName = "mobile" };

            description1.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "GOOD PRODUCT.MUST BUY", ProductRating = 5.0 });

            description1.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "Dull PRODUCT.Dont BUY", ProductRating = 0 });

            description1.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "OK PRODUCT.Can Buy", ProductRating = 3.0 });

            ProductDescription description2 = new ProductDescription { ManufacturerName = "nokia", Price = 70000, ProductName = "mobile" };

            description1.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "GOOD PRODUCT.MUST BUY", ProductRating = 5.0 });

            description1.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "Dull PRODUCT.Dont BUY", ProductRating = 0 });

            description1.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "OK PRODUCT.Can Buy", ProductRating = 3.0 });

            product_repo.addItem(description1);

            product_repo.addItem(description2); 

            //CREATE 7 NEW ITEMS

            Item[] items = new Item[7];
            items[0] =
new Item { InventorySerialCode = "00A0110",ItemDescription = description1 };
            items[1] =
new Item { InventorySerialCode = "01A0101", ItemDescription = description1 };
            items[2] =
new Item { InventorySerialCode = "02A10101", ItemDescription = description1 };
            items[3] =
new Item { InventorySerialCode = "03A01010", ItemDescription = description1 };
            items[4] =
new Item { InventorySerialCode = "04A101010", ItemDescription = description1 };
            items[5] =
new Item { InventorySerialCode = "05A010101", ItemDescription = description1 };
            items[6] =
new Item { InventorySerialCode = "06A0100100", ItemDescription = description1 };

            //ADD ALL ITEMS TO REPSITORY
            //ITEMS ADDED HAVE SERIAL CODE 00--- to 06
            //ALL THESE ITEMS WILL HAVE NULL FOR PAYMENTAPPROVEDORDER REFERENCE
            //BECAUSE THEY EXIST BEFORE IN INVENTORY AND NOT BOUGHT
            for (int counter = 0; counter < items.Length; counter++)
            {
                items_repo.addItem(items[counter]);
            }
           
Customer customer1 = new Customer { CustomerName = "AliceWonder" };

            Customer customer2 = new Customer { CustomerName = "JimTreasure" };

           
Customer customer3 = new Customer { CustomerName = "OliverTwist" };

            customer1.EmailIdentity = new Email { EmailAddress = "[email protected]" };

            customer2.EmailIdentity = new Email { EmailAddress = "[email protected]" };

            customer3.EmailIdentity = new Email { EmailAddress = "[email protected]" };

            //customer 1 added to repository
            customer_repo.addItem(customer1);

            //Extra customers added to repository - these have no carts
            customer_repo.addItem(customer2);

            customer_repo.addItem(customer3);

            //Customer is buying. So fist step is shopping cart created
            //if none exists before.
            ShoppingCart cart1 = new ShoppingCart(customer1);
            cart_repo.addItem(cart1);
           
ShoppingCart cart2 = new ShoppingCart(customer2);
            cart_repo.addItem(cart2);
           
ECommerceSellerSystem system = new ECommerceSellerSystem();
           
// customer selects a product description to buy
            ShoppingCartSelection selection1 = system.AddSelectionToCustomerCart(description1, cart1, 23);
           
//system adds the selection to repository (adds to selection - viewable from cart&descriptions)
            cart_selection_repo.addItem(selection1);
           
//customer selects to buy
            ShoppingCartSelection selection2 = system.AddSelectionToCustomerCart(description2, cart2, 2);
           
//system adds selection to repository (adds to selection - viewable from cart&descriptions)
            cart_selection_repo.addItem(selection2);

            ECommerceSellerSystem seller_system1 = new ECommerceSellerSystem();
           
// Customer makes a order out of his cart - Selects items he needs to buy
            //Fast Shipping cannot be gift wrapped is a constraint to be handled in Presentationlayer
            Order order1 = seller_system1.MakeOrder(cart1,true,false);
           
//Customer wants to process the order to know the payment
            order1 = seller_system1.ProcessAmountForOrder(order1);
            
//Customer Makes a payment and ORDERS the items
            Payment payment1 = new Payment { PaymentAmount=(order1.OrderCost+order1.ShippingCharges) };
            seller_system1.MakeOrderPayment(order1, payment1);
           
//The previous order is added to customer list of paid orders

            ECommerceSellerSystem seller_system2 = new ECommerceSellerSystem();
           
// Customer makes a order out of his cart - Selects items he needs to buy
            //Fast Shipping cannot be gift wrapped is a constraint to be handled in Presentationlayer
            Order order2 = seller_system2.MakeOrder(cart2,false,true);
           
//Customer wants to process the order to know the payment
            order2 = seller_system2.ProcessAmountForOrder(order2);
           
//Customer Makes a payment and ORDERS the items
            Payment payment2 = new Payment { PaymentAmount = (order2.OrderCost+order2.ShippingCharges) };
            seller_system2.MakeOrderPayment(order2, payment2);

The customer's shoping cart selection, manages shopping cart, does order processing, and creates a payment approved order and next will be doing shipping processing also. To accomplish this it uses a set of domain classes which has no static variable except for one or two which can be removed. The preceding lines would get the wheels turning for astute WCF developers for the possibility of opening this class has a percall instanced service. What would be even better would be proper interface factoring so that each of the functions of order processing, shipping and shop selection handling are factored into various interfaces and made available with a service orientation. That was the essence of the ideas right from the start and could be accomplished with effort and improvements. Next we will see the inheritance example with Shipping having created an Order. The MakeOrderPayment method in the class.

MAPPING INHERITANCE EXAMPLE

The entire Shipping scenario in the ECommerce system primarily consists of a Shipping class (abstract class) that has all the information for shipping the product (shipping firm name etc). It has two concrete child classes which we discuss in the next paragraph. We provide flexibility that a PaidOrder (instance of PaymentApprovedOrder) can be shipped in many parts, in other words in many shippings. Also a Shipping instance can have many PaymentApprovedOrders in it if it is made by the same customer. So a many-to-many bidirectional association exists between Shipping and PaymentApprovedOrder and the association class is OrderShipment. Each OrderShipment will have a collection of Items. This collection is part of the items ordered by the customer, which should be shipped in that particular instance of OrderShipment. Remember the association class Ticket with its passengers collection in article 5A; the same method is used here but Items to be shipped are populated differently which is to be shown in the next article and also an ordershipment may have only part of the ordered items (the remaining items may be sent in a different shipping and hence put under a different order shipment). So for now we capture Shipping and the association class OrderShipment between Shipping and PaymentApprovedOrder.

The Shipping class code is given by:
 

public abstract class Shipping
{
       
public virtual long ShippingId { get; set; }
       
public virtual string ShippingName { get; set; }
       
public virtual DateTime? ShippingStartDate { get; set; }
       
public virtual ISet<OrderShipment> ShipmentsInThisShipping { get; set; }
       
public virtual double ShippingCharges { get; set; }
       
public abstract void CalculateShippingCharges(double total_amount);
}

The OrderShipment class code is given by

 public class OrderShipment
{
       
public OrderShipment()
        {
           
//SHIPMENT ITEMS - DONT SET HERE - Just allocate memory only
            ShipmentItems = new List<ShipmentItem>();
        }
       
public OrderShipment(PaymentApprovedOrder paid_order, Shipping shipping)
        {
            OrderShipmentId = new CompositeKey();
            //Set the Composite Key
            OrderShipmentId.class1Key = paid_order.PaymentApprovedOrderId;
            OrderShipmentId.class2Key = shipping.ShippingId;
            //set shipments
            ShipmentItems = new List<ShipmentItem>();
            //Set Both sides of the bidirectional association
            paid_order.ShipmentsOfThisPaidOrder.Add(this);
            shipping.ShipmentsInThisShipping.Add(this);
            //other ends
            CurrentPaidOrder = paid_order;
            CurrentShipping = shipping;

        }
       
public virtual CompositeKey OrderShipmentId { get; set; }
       
public virtual PaymentApprovedOrder CurrentPaidOrder { get; set; }
       
public virtual Shipping CurrentShipping { get; set; }
       
public virtual IList<ShipmentItem> ShipmentItems { get; set; }
}

The mapping for Order Shipment is:

<class name="OrderShipment" table="ORDERSHIPMENT" >

    <composite-id name ="OrderShipmentId" class ="CompositeKey">
      <
key-property name ="class1Key" column ="PAYMENTAPPROVEDORDERID" type ="long" access="field"></key-property>
      <
key-property name ="class2Key" column ="SHIPPINGID" type ="long" access="field"></key-property>
    </
composite-id>

    <many-to-one class="PaymentApprovedOrder" name="CurrentPaidOrder" column="PAYMENTAPPROVEDORDERID" insert="false" update="false"></many-to-one>

    <many-to-one class="Shipping" name="CurrentShipping" column="SHIPPINGID" insert="false" update="false"></many-to-one>

     <list name ="ShipmentItems" table ="SHIPMENTITEMS" cascade="save-update">
      <
key not-null="true">
        <
column name ="PAYMENTAPPROVEDORDERID" />
        <
column name ="SHIPPINGID" />
      </
key>
      <
list-index column="POSITION" />
      <
one-to-many class ="ShipmentItem"/>
    </
list>

  </class>

The "Shipping" abstract class has two concrete child classes: a. FastShipping and b. ParcelService. FastShipping is a specialized form of Shipping that offers same-day shipping and faster delivery with extra charges. ParcelService just transfers an item with gift wrapping if required. FastShipping is not offered with gift wrap because our efficient e-commerce site jumps into action when an order is to be fast-shipped and does not have time for gift wraps.

Have a look at Figure 1 below. It shows the shipping's abstract super class and 2 child classes and their mapping code. Read the note in the figure that explains it.

Figure 1.jpg

Figure 1

To know the power of inheritance mapping using NHibernate we will have a look at the methods "MakeOrderPayment" & "ProvideShippingInformation" in the ECommerceSellerSystem class. What is of interest here is the way the Shipping instance is created and persisted to the database. The Shipping instance is created in the ProvideShippingInformation method and consumed in the MakeOrderPayment method and persisted to the database. Have a look at the code below.

public void MakeOrderPayment(Order order, Payment pay)
        {
           
NHibernateHelper nh = NHibernateHelper.Create();
           
using (NHibernate.ITransaction transaction = nh.Session.BeginTransaction())
            {
               
IRepository<PaymentApprovedOrder> pay_order_repo = new DBRepository<PaymentApprovedOrder>();
               
IRepository<Shipping> shipping_repo = new DBRepository<Shipping>();

                MarkItemAsOrdered(order);
               
PaymentApprovedOrder paid_order = new PaymentApprovedOrder(order, pay);
                pay_order_repo.addItem(paid_order);
               
//Item will have only two states - Is Ordered or Inventory item.
                //The IsCartedState of Item exists only when a order is made. At the exit
                //of making a order, the IsCarted state should be destroyed,
                DestructShoppingCartBindingForItemsInOrder(order);
               
Shipping shipping = ProvideShippingInformation(order.OrderCost, order.IsFastShipping, order.IsGiftWrapped);
                shipping.ShippingCharges = order.ShippingCharges;
                shipping_repo.addItem(shipping);
                transaction.Commit();
            }

        }
public Shipping ProvideShippingInformation(double order_amount,bool is_fast_shipping,bool is_gift_wrapped)
        {
           
//Important thing to note here is PaymentApprovedOrder is already saved in db
            //no need to process a transaction to envelope both
            Shipping shipping = null;
           
if (!is_fast_shipping)
            {
                shipping =
new ParcelService(is_gift_wrapped);
            }
           
else
            {
                
//Fast Shipping - same day delivery - no time for gift wraps
                //So only the latest date by which item should be sent - i.e today
                //which is same day as order day
                shipping = new FastShipping(DateTime.Today);
            }
            shipping.CalculateShippingCharges(order_amount);
           
//MostImportant thing is Shipping is abstract super class -
            //having two concrete child classes
            //We did not tell NHibernate which child class we are dealing with
            return shipping;
        }


Have a look at the method "ProvideShippingnformation". It creates a concrete instance of FastShipping or ParcelService according to the info provided in the Order passed as parameters and stores it to a base class reference of Shipping. This same baseclass reference is then used to calculate shipping charges of an Order by using the polymorphic call to the method CalculateShippingCharges which is overridden in both concrete classes of Shipping i.e FastShipping and ParcelService (each has a different way of calculating shipping charges; fast shipping is higher). It is then returned to the caller with "MakeOrderPayment" as the base class reference containing concrete child instance itself. What is absolutely of interest here is that in the caller, this abstract base class reference i.e Shipping and storing the concrete child class instance (FastShipping or ParcelService) is used with NHibernate for persistence. You do not tell NHibernate what concrete instance is to be stored etc. It is all handled by NHibernate (though the internals of how NHibernate perststs are clearly understandable if you see the structure of tables in Figure 2 later).

The concrete child classes of the abstract Shipping class i.e FastShipping and ParcelService is as shown below (note that they do not have a property for the identity value; more on this later):

public class FastShipping:Shipping
{
       
public FastShipping()
        {
            ShipmentsInThisShipping = new Iesi.Collections.Generic.HashedSet<OrderShipment>();          
        }
       
public FastShipping(string docket, DateTime delivety_date)
        {
            TrackingNumber = docket;
            ShipmentsInThisShipping = new Iesi.Collections.Generic.HashedSet<OrderShipment>();     
            DeliveryDate = DeliveryDate;
        }
       
public FastShipping(DateTime start_latest_by)
        {
             ShipmentsInThisShipping = new Iesi.Collections.Generic.HashedSet<OrderShipment>();  
             StartDeliveryLatestBy = start_latest_by;
        }
       
public virtual int STANDARDRATE { get; set; }
       
public virtual string TrackingNumber { get; set; }
       
public virtual DateTime? DeliveryDate { get; set; }
       
public virtual DateTime? StartDeliveryLatestBy { get; set; }
       
public override void CalculateShippingCharges(double total_amount)
        {
            ShippingCharges =((.1) * total_amount) + STANDARDRATE;   
        }
}
public class ParcelService:Shipping
    {
       
public ParcelService()
        {
            ShipmentsInThisShipping = new Iesi.Collections.Generic.HashedSet<OrderShipment>();
        }
       
public ParcelService(bool is_gift_wrapped)
        {
            ShipmentsInThisShipping = new Iesi.Collections.Generic.HashedSet<OrderShipment>();

            IsGiftWrapped = is_gift_wrapped;
        }
       
public virtual int STANDARDRATE { get; set; }
       
public virtual string ParcelRegistrationNumber { get; set; }
       
public virtual bool IsGiftWrapped { get; set; }
       
public override void CalculateShippingCharges(double total_amount)
        {
            ShippingCharges = ((.02) * total_amount) + STANDARDRATE;           
        }
    }


So what are the rows formed by NHibernate in the shipping tables while an order is processed for the testcode given earlier? Refer to Figure 2. Please note that this is the snapshot of the shipping tables in the database when customer leaves an order to the site. A customer can only give a preference for fast shipping or parcel service with gift wrapping. Hence a row is created in the superclass table SHIPPING and its primarykey is posted as a foreignkey in a row of the FASTSHIPPING table if the customer wants the FastShipping Service or it is posted in a row of PARCELSERVICE table if he wants ParcelService. The charges is also filled in along with information for the subclass like the giftwrap for the parcelservice subclass and startdeliveryby date for fast shipping (useful to send notifications to the shipping department). A customer cannot name the courier by which shipping will be made (column SHIPPINGNAME in the SHIPPING TABLE) or other Shipping details. These are enterd by the shipping department of the site. Hence unless they start shipping and enter information for shipping, all these values will be null as shown in figure 2. To avoid nulls, you can use default values like "To Be Filled". Refer to Figure 2.

Figure 2.jpg

Figure 2

The mapping file for Shipping and its subclasses is shown below. The most important point to note in this mapping file is that for the subclasses, i.e FastShipping and ParcelService, the primarykey value is not generated (i.e FASTSHIPPINGID and PARCELSERVICEID). Instead it is a foreign key value posted from the SHIPPING TABLE (base class) and declared in mapping file using <key column=".."> tag. See the Figure 2 arrows and mapping codes to understand. Also look at the C# code for the FastShipping subclass and ParcelService subclass. They do not have a property for FastShippingId and ParcelServiceId as we do for all entity classes. This is because both columns FASTSHIPPINGID & PARCELSERVICEID are foreign keys posted automatically by NHibernate from the baseclass using the information (<key column="...">) given in the mapping file while declaring the subclasses using the <joined-subclass> tag. NHibernate uses the foreignkey from the base class to the child class to capture the "is a" inheritance relationship in this Table Per Subclass method.

<class name="Shipping" table="SHIPPING">
    <
id name ="ShippingId" column ="SHIPPINGID" generator ="native" />

    <property name="ShippingName" column="SHIPPINGNAME" type="string" />

    <property name="ShippingStartDate" column="SHIPPINGSTARTDATE" type="DateTime" />

    <property name="ShippingCharges" column="SHIPPINGCHARGES" type="double" />

    <set name="ShipmentsInThisShipping" table="ORDERSHIPMENT">
      <
key column ="SHIPPINGID" />
      <
one-to-many class ="OrderShipment"/>
    </
set>

    <
joined-subclass name="FastShipping" table="FASTSHIPPING">
      <
key column="FASTSHIPPINGID"/>
      <
property name ="TrackingNumber" type ="string" column ="TRACKINGNUMBER" />
      <
property name ="StartDeliveryLatestBy" type ="DateTime" column ="STARTDELIVERYBY" />
      <
property name ="DeliveryDate" type ="DateTime" column ="DELIVERYDATE" />
    </
joined-subclass>

    <joined-subclass name="ParcelService" table="PARCELSERVICE">
      <
key column="PARCELSERVICEID"/>
      <
property name ="ParcelRegistrationNumber" type ="string" column ="PARCELREGISTRATIONNUMBER" />
      <
property name ="IsGiftWrapped" type ="bool" column ="ISGIFTWRAPPED" />
    </
joined-subclass>
  </
class>

CONCLUSION

Full Inheritance hierarchy mapping and polymorphic support are the strengths of NHibernate's Table Per Subclass method. For very large hierarchies, the joins in SQL statements may result in a performance loss. In the next article (Part 7) we will finish the shipping processing by allowing the shipping information to be input by the site and also shipping of the items by adding the item collection to OrderShipment in a different and useful way that captures the movement of the Item in the organization (i.e. from inventory to shipments). Enjoy NHibernate.
 

Up Next
    Ebook Download
    View all
    Learn
    View all