Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / NHibernate

Object Relational Mapping (ORM) using NHibernate - Part 7 of 8 Completing the Ecommerce Example

5.00/5 (7 votes)
15 May 2013CPOL7 min read 28.5K   2.6K  
A full series of 8 part articles to show One-To-One, Many-To-One, Many-To-Many associations mapping using NHibernate, Using Collections With NHibernate, Inheritance Relationships Using NHibernate, Lazy Initializations/Fetches Using NHibernate.

The article series

Introduction

In articles 1-6, we finished the various types of associations between entity classes and inheritance between entity classes. The example to illustrate these ideas had been the sample ECommerce scenarios. We have reached a point where shipping has to be done for the product. In this seventh article and its client code, we will demonstrate that the classes we have put forth to incorporate the shipping functionality will allow shipment items of one order to be shipped in parts by multiple shipping services. This is a flexibility that provides a business advantage. Also possible is the case that multiple orders belonging to the same customer can be shipped in one shipping. All this is possible because we have modeled the relationship between entities Shipping and PaymentApprovedOrder as a Many-To-Many association with OrderShipment as an association class. This article and the client code will also demonstrate that the way in which we add ShipmentItem i.e., the item to be shipped for a paid order will capture the material movement in the ecommerce domain.

Background

All required background covered in Articles 1 - 6.

Note: The two projects included are ECommerce1 and ECommerce 2. ECommerce1 is for samples in articles 1 - 3 and Ecommerce 2 is for the rest of the samples till this article 7. The client project to test the classes and their associations for each article is also provided and each article's test code is clearly named like "Test Code for Article 1" etc. While starting a subsequent article, the previous article's test code is commented out in the test client project and the classes in the main project would have evolved and changed to fit the subsequent scenarios of the next article. The commented out previous article's test code has not been updated to reflect changes to the class. So the test code will compile and work correctly for the most recent article for each project, i.e., article 3 for ECommerce1 and articles 7 and 6 for Ecommerce 2 which completes our ECommerce samples. Follow the instructions given earlier for using the projects with Microsoft Visual Studio 2012, though we all know how to get it done without any help.

Using the code

Code Sample for Ecommerce Scenario

Here we will briefly explain and complete the ShipmentItem scenario and demonstrate the points articulated in Introduction. The scenario is, Once a customer gives the paid order to the ecommerce site, selecting his preferred method of Shipping, the Ecommerce Site Shipping Department, will process the order and fulfill the shipping of the every item in the paid order to the customer.

So, when a paid order is created by the customer, the customer selects fast shipping or parcel service according to which an instance of FastShipping or ParcelService is created and stored in the base class reference Shipping. This Shipping reference stores the paid order ID to which it is attached in the PrimaryPaidOrderId property and is persisted to the database. Not a foreign key but just a property (because Shipping and PaymentApprovedOrder are bound by the association class OrderShipment). When Shipping information has to be input by a personnel of the Shipping department, they input this primary paid order ID (PRIMARYPAIDORDERID) to which shipping has to be done to get a particular shipping instance. How do they know this PRIMARYPAIDORDERID?

This is the the paid order ID (i.e., the ID of a new order - PAYMENTAPPROVEDORDERID) for which a shipping has not been done which is available for the application to retrieve and show in a listbox from which the shipping department can select one. So using this, the particular shipping instance for that primary paid order ID, stored in the repository while an order is created by a customer, is retrieved from the repository and shipping information is added to it and persisted again into the repository. If the shipping department decides to do the Shipping in parts, i.e., more than one shipping for a particular order, then for subsequent parts they do not need to add the primary paid order ID. They can just create new instances of Shipping giving all the shipping information. The OrderShipment instance will associate Shipping and PaymentApprovedOrder anyway. This design will provide maximum flexibility to the business scenario.

The code in ECommerceSellerSystem for AddShippingInformation is:

C#
public Shipping AddShippingInformation(bool is_primary, bool is_fast, 
  long prime_id, string shipping_name, DateTime shipping_start_date, 
  string tracking_number,bool is_gift_wrapped)
{
    Shipping shipping = null;
    IRepository<Shipping> shipping_repo = new DBRepository<Shipping>();
    NHibernate.ISession session = null;
    NHibernateHelper nh = NHibernateHelper.Create();
    session = nh.Session;    
    if (is_primary)
    {
        string query_string = 
          "from Shipping shipping1 where shipping1.PrimaryPaidOrderId = :prime_id";
        shipping = session.CreateQuery(query_string).SetParameter<long>(
                     "prime_id",prime_id).UniqueResult<Shipping>();
        if (shipping != null)
        {
            shipping.ShippingName = shipping_name;
            shipping.ShippingStartDate = DateTime.Today;
            if (shipping is FastShipping)
                ((FastShipping)shipping).TrackingNumber = tracking_number;
        }
    }
    else
    {
        if (is_fast)
        {
            shipping = new FastShipping { ShippingName=shipping_name, 
              ShippingStartDate=DateTime.Today, PrimaryPaidOrderId=null, 
              TrackingNumber=tracking_number };
        }
        else
        {
            shipping = new ParcelService { ShippingName = shipping_name, 
              ShippingStartDate = DateTime.Today, 
              PrimaryPaidOrderId = null, IsGiftWrapped = is_gift_wrapped };
        }
    }
    if (shipping != null)
    {
        using (NHibernate.ITransaction trans = session.BeginTransaction())
        {
            session.SaveOrUpdate(shipping);
            trans.Commit();
        }
    }
    return shipping;
}

As mentioned before, the association class for the Many-To-Many association between Shipping and PaymentApprovedOrder is OrderShipment. Each instance of OrderShipment will also contain the list of items that was ordered by a customer and shipped in that shipment i.e., collection of ShipmentItems. But if the shipping department of the ECommerce site decides to send a particular order's items in parts, i.e., first few items in one shipping instance and remaining in other shipping instances, then accordingly multiple instances of Shipping and OrderShipment will be created and each instance of OrderShipment will contain the collection of shipment items shipped in it. The code in ECommerceSellerSystem for AddingShipmentItem is:

C#
public void AddShipmentItem(OrderShipment order_shipment, string inventory_serial_code)
{
    //The barcode scanner returns a string of inventory serial code.
    //Item instance will have to be retrieved using serial code.
    //Item in Inventory will have to be deleted inside Transaction.
    //In the same transaction, the item has to be added to inventory.
    NHibernateHelper nh = NHibernateHelper.Create();
    using (NHibernate.ISession session = nh.Session)
    {
        string query_string = "from Item item1 where item1.InventorySerialCode = :code";
        Item inventory_item = session.CreateQuery(query_string).SetParameter<string>(
             "code", inventory_serial_code).UniqueResult<Item>();
        if (inventory_item != null)
        {
            IRepository<Item> item_repository = new DBRepository<Item>();
            IRepository<OrderShipment> order_shipment_repository = new DBRepository<OrderShipment>();
            using (NHibernate.ITransaction trans = session.BeginTransaction())
            {
                inventory_item.PaidOrder.PaidOrderItems.Remove(inventory_item);
                session.Delete(inventory_item);
                order_shipment.ShipmentItems.Add(
                  new ShipmentItem { InventorySerialCode = inventory_item.InventorySerialCode });
                session.SaveOrUpdate(order_shipment);
                trans.Commit();
            }
        }
    }
}

The important thing to note about the above piece of code is how it captures the domain's material movement. When an item is ordered and shipped, it no longer exists in the inventory of items. The code written above captures the domain exactly. The item moves from the inventory of the item represented by the ITEM table to the SHIPMENTITEMS table. A flag in the ITEM table would have been enough but removing the sold and shipped ones from the ITEM table and moving them to the SHIPMENTITEMS table makes it easier for any scrutiny and adds clarity. The client code to test is given by:

C#
//Add Shipping Information - NOTE THAT THESE METHODS ARE CALLED WITHIN DEPARTMENTS OF ECOMMERCE SITE
//Shipping information for PAYMENTAPPROVEDORDER
ECommerceSellerSystem seller_system3 = new ECommerceSellerSystem();
//SHIPPING DEPARTMENT SELCTS A APPROPRIATE SHIPPING AGENCY AND  CREATES A SHIPPING FOR THE PRODUCT
Shipping shipping1 = seller_system3.AddShippingInformation(true, true, 
  pay_order1.PaymentApprovedOrderId, "Noble House Ships", 
  DateTime.Today, "A101A101", false);
//THE ORDER SHIPMENT BEGINS
OrderShipment order_shipment1 = seller_system3.CreateOrderShipment(pay_order1, shipping1);
//THE SHIPPING MANAGER, USES A BARCODE SCANNER TO SCAN A ITEM TO BE SHIPPED
//BARCODE SCANNER JUST GIVES A TEXT IDENTIFYING THE PRODUCT - HERE 00A0110
seller_system3.AddShipmentItem(order_shipment1, "00A0110");
//THE SHIPPING MANAGER, USES A BARCODE SCANNER TO SCAN A ITEM TO BE SHIPPED
//BARCODE SCANNER JUST GIVES A TEXT IDENTIFYING THE PRODUCT - HERE "01A0101"
seller_system3.AddShipmentItem(order_shipment1, "01A0101");
//FOR THE SAME ORDER A DIFFERENT SHIPPING IS SELECTED - THIS FLEXIBILITY IS GOOD BECAUSE IT ALLOWS
//THE SHIPPING DEPARTMENT TO GIVE ITEMS OF SAME ORDER
// TO DIFFERENT SHIPPERS EACH SPECIALISING IN THAT ITEM TYPE
Shipping shipping2 = seller_system3.AddShippingInformation(false, true, 
  pay_order1.PaymentApprovedOrderId, "Fast Service", DateTime.Today, "A10101A", false);   
//AS BEFORE THE SHIPPINGMANAGER ADDS ITEMS TO SHIP
OrderShipment order_shipment2 = seller_system3.CreateOrderShipment(pay_order1, shipping2);
seller_system3.AddShipmentItem(order_shipment2, "02A10101");
seller_system3.AddShipmentItem(order_shipment2, "03A01010");
seller_system3.AddShipmentItem(order_shipment2, "04A101010");
//WHEN A ITEM IS PICKED FROM INVENTORY TO BE SHIPPED BY SHIPPING MANAGER USING THE ABOVE METHOD,
//IT CEASES TO LIVE IN INVENTORY's ITEM TABLE AND IS MOVED TO SHIPMENTITEMS TABLE TO CAPTURE
//MATERIAL MOVEMENT IN DOMAIN MAKING IT EASIER FOR SCRUTINY LATER.

Figure 1 below shows the tables created by the client code. The top one is the SHIPMENTITEMS table and the centre one is the SHIPPING Table and bottom one is the ITEM table. Note that the second row of the SHIPPING table (center table) has nulls. Nulls are there because we have not given the shipping information for it in the client code and the row was created when the customer created the order as shown in Article 6. Compare it with Row 1 for which we gave all the shipping information in the client code above. Also note that in the SHIPPING table (center), for the third row with SHIPPINGID = 3, PRIMARYPAIDORDERID is null because this row is not created when the customer creates the paid order. This row gets created by the shipping department of the Ecommerce site which decides to send PAYMENTAPPROVEDORDER=1 in two parts and hence adds this shipping instance by providing shipping info. As mentioned before, PRIMARYPAIDODERID is filled up by the application only when the customer gives shipping preferences while creating a paid order. Also note in the top SHIPMENTITEMS table, that all items are of the same paid order but belong to two different shipping instances each representing a different shipping firm (see the ShippingID column and PAYMENTAPPROVEDORDERID columns in the topmost SHIPMENTITEM table). This is the flexibility that we mentioned before. Also note that when these shipment items are moved to the SHIPMENTITEMS table, they cease to exist in the inventory i.e., ITEM table (bottom table in figure 1).

Image 1

Figure 1 - Top - SHIPMENTITEMS table, Center - SHIPPING Table, Bottom - ITEM Table

Points of Interest

Conclusion

The samples are created keeping two core principles in mind: simplistic (KISS) and minimalistic (YAGNI). The entity classes have been factored keeping high cohesion in mind but obviously they cannot have low coupling because the entire article series is for explaining various entity associations and there have been bidirectional associations used for most part to illustrate the ideas. This article completes the various scenarios of ECommerce samples. The next article of this article series will discuss the lazy loading feature in NHibernate. Enjoy NHiberante.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)