Object Relational Mapping (ORM) Using NHibernate: Part 5 - B 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

Coding Many-to-Many Entity Associations

This is the second part of Many-To-Many entity associations. The idea here is to apply the Many-To-Many entity association in the E-Commerce scenario used, for example, throughout this article series.

Background

The background mentioned in Article PART 5 A holds good here. Taking clues from it, what's worth remembering is: a Many-To-Many entity association in NHibernate can be represented exactly the same as a Many-To-Many association is split in OOAD (Use 1 Association class and split it into two One-to-Many associations.).

E-Commerce Example Scenario

The scenario that gives the example for a Many-To-Many entity association is as in the following.

When a customer visits an e-commerce site, he opens various product descriptions on the site. If a product description matches his buying interests, he may select to add the item to his shoppingcart for ordering subsequently.

Looking at this sample scenario, the obvious entity classes are ProductDescription, ShoppingCart, Customer. One may conclude using OOAD that the abovementioned scenario has the Many-To-Many association between ProductDescription and Customer and use the ShoppingCart class as an Association class with a collection of shoppingcart items in it (exactly like the Ticket Association class with collections of passengers in Ticket in the previous Article 5 A). This is the usual practice but there is a better way to model this.

Looking back at the domain, the shopping cart and the product description is associated by the link ShoppingCartSelection which represents a particular item selected by the customer and dropped into a particular ShoppingCart. So, a ShoppingCart may have many ProductDescriptions selected in it and a ProductDescription may be selected in many ShoppingCarts (each cart maybe belonging to a different customer). Thus the association between ProductDescription and ShoppingCart can be modeled as a Many-To-Many association and the association class is ShoppingCartSelection. This association class ShoppingCartSelection associates one particular ShoppingCart to a particular ProductDescription i.e it splits the Many-To-Many association between ShoppingCart and ProductDescription to two One-to-Many associations with itself. The primary concern before finalizing a ShoppingCartSelection as an association class should be, is it an Entity class or a Value type based on ShoppingCart. The order will be based on ShoppingCartSelection and when an order is made a shopping cart will cease to exist but its ShoppingCartSelection must live beyond it inside an order for further processing like Payment and Shipping. Hence ShoppingCartSelection is an Entity class only and can be finalized as an association class for Many-To-Many associations between ShoppingCart and ProductDescription.

The next thing to be captured to complete this scenario is that a shopping cart is always associated with the customer with a One-to-One or Many-to-One association as deemed fit by us. If we allow a customer to have many shopping carts then it is Many-to-One between shoppingcart and customer or else it is a One-to-One association. We use a One-to-One assoiation between Customer and ShoppingCart. We can use an optional One-to-One association to correctly capture the scenario that a customer may not have a shopping cart attached if he does not buy. Refer to Part 4 of this article series for use and advantages of optional One-to-Many associations. The primary advantage in using an optional One-to-One association would be avoiding null keys in a foreign key column. Does that mean we have to use an optional One-to-One association here to avoid nulls in a foreignkey column? Not necessarily. According to Part 1 of the article series we know a One-to-One association between objects is represented by foreign keys posted between tables. So just post the primarykey from the CUSTOMER table to the SHOPPINGCART table. So in a One-to-One association beteen Customer and ShoppingCart, the primarykey CUSTOMERID of the Customer Table will become a foreignkey column in SHOPPINGCART table. There won't be a null value for customers without a shopping cart simply because such a row will not be added to the SHOPPINGCART table. If we do it the other way, i.e post the foreign key into the CUSTOMER table using the primarykey SHOPPINGCARTID of the SHOPPINGCART table, then naturally, for customers without a shopping cart, we will have a null value for the foreignkey column SHOPPINGCARTID posted in the CUSTOMER table to realize the association. These are finer details that can enhance the quality without requiring extra effort. A bidirectional One-to-One association can be handled by using the attribute property-ref (Refer to Part 1 of the article series for more on this). If we capture this One-to-One association as an optional One-to-One association to strictly adhere to the domain, then we will have one more join table to deal with. Having fewer tables than classes, i.e a fine-grained approach, is better. But there is no such workaround for optional One-to-Many which should be coded as such.

The Many-To-Many association between ProductDescription and ShoppingCart is shown below in Figure 1. Looking at Figure 1, the main thing to see is that neither the ProductDescription nor ShoppingCart is referencing each other though they have a Many-To-Many association. Adhering to the tenets of OOAD, the association class ShoppingCartSelection splits this Many-To-Many association into two One-to-Many associations with itself and both these classes. Observe this in Figure 1 shown clearly with an Orange arrow. The next thing to observe is that the collection end of both the One-to-Many association is INVERSE as shown clearly with the purple underlines. If you look at the other end of the association, i.e the non-collection end formed by using a Many-to-One tag in the mapping file of the association class ShoppingCartSelection (the code is shown below), you will notice that even it is coded to obtain an inverse effect by using insert=false and update=false attributes. It would have been inferred immediately that we are restricting Nhibernate from generating automatic SQL statements for inserts and updates on both ends of the association. So how is this bidirectional association created and populated to the database? The explanation is given in the next paragraph of this article. For now observe the inverse on both ends in code snippet for the association class ShoppingCartSelection shown below and in Figure 1.

1.jpg

FIGURE 1

The code for the association class ShoppingCartSelection is given below:

public class ShoppingCartSelection
{
    public ShoppingCartSelection()
    {
        ShoppingCartSelectionId = new CompositeKey();
    }

    public ShoppingCartSelection(ProductDescription product, ShoppingCart cart, int quantity)
    {
        ShoppingCartSelectionId = new CompositeKey();
        
        // Set the composite keys
        ShoppingCartSelectionId.class1Key = product.ProductDescriptionId;
        ShoppingCartSelectionId.class2Key = cart.ShoppingCartId;
        
        // Set the associations
        product.CartSelectionsWithThisProduct.Add(this);
        cart.CartSelections.Add(this);
        
        CurrentProduct = product;
        ParentCart = cart;
        
        // SetProperty
        Quantity = quantity;
    }

    public virtual CompositeKey ShoppingCartSelectionId { get; set; }
    public virtual ProductDescription CurrentProduct { get; set; }
    public virtual ShoppingCart ParentCart { get; set; }
    public virtual int Quantity { get; set; }
}

The code for the mappingfile ShoppingCartSelection.hbm is given below:

<class name="ShoppingCartSelection" table="SHOPPINGCARTSELECTION">
    <composite-id name="ShoppingCartSelectionId" class="CompositeKey">
        <key-property column="PRODUCTDESCRIPTIONID" name="class1Key" access="field" type="long" />
        <key-property column="SHOPPINGCARTID" name="class2Key" access="field" type="long" />
    </composite-id>
    <property name="Quantity" column="QUANTITY" type="int" />
    <many-to-one name="CurrentProduct" column="PRODUCTDESCRIPTIONID" class="ProductDescription" insert="false" update="false" />
    <many-to-one name="ParentCart" column="SHOPPINGCARTID" class="ShoppingCart" insert="false" update="false" />
</class>

Both ends of the two One-to-Many bidirectional entity associations formed by the association class is given as inverse ends. From previous articles of this article series, we do know that NHibernate does not generate automatic insert and update statements when an end is marked inverse. So why map both ends inverse? As said before, we use NHibernate to capture a Many-To-Many association exactly as we think of in OOAD. We use an association class. The important thing is that the association class used does not use a surrogate key. It uses a composite key of the two foreignkeys posted from both ends of the association. This link is established in the constructor of the association class (as shown in the constructor of ShoppingCartSelection in the code above). Since both the associations are captured by this compositekey in the constructor of the association class, what is the need for insert and update statements for the One-to-Many associations between the association class and both the many end classes? Hence both ends of the association is marked inverse. Refer To FIGURE 2 below where I show the structure of the table formed for SHOPPINGCARTSELECTION. Note that both foreignkey columns together form the compositekey for this table and this is set in the constructor of the ShoppingCartSelection class and this link will have to be managed by code only as shown above. Since both One-to-Many associations are already captured as a composite key, we make both ends of the link as inverse and avoid NHibernate from generating automatic SQL statements.

shopping-cart-selection-table.jpg

FIGURE 2

All this is ok but where is the advantage in this approach? The advantages are that the Customer object can access a ShoppingKart object which in turn has its collection of ShoppingCartSelection instances associated which contains information of ProductDescriptions he has bought and can now set his buying priorities (according to his budget) to make an order from the cart selections by adding or discarding items. The biggest business value is in the other end of the association. The ProductDescription instance has its collection of ShoppingKartSelection instances which denotes all the ShoppingKartSelection instances that have this particular ProductDescription instance picked. Each ShoppingKartSelection instance has its association with a ShoppingKart instance which is associated with a Customer instance who picked the item and dropped it into a ShoppingKart. If the ProductDescription is of HighValue yielding highprofits, the ecommerce site can use this association to find which customer has dropped the high-value ProductDescription into his ShoppingKart and not given the Order and keep reminding him to purchase it (this entire order reminder scenario can be easily automated). All these instances and their associations can be persisted and fetched from the database using NHibernate without query but by objects only. We developers know the advantage of having everything accessible as objects. OOAD, if used correctly with business use cases analysed properly, yields pure value.

The code for the Customer class which has a bidirectional One-to-One association with a ShoppingCart is given below:

public class Customer
{
    public Customer()
    {
        CustomerPaidOrders = new List<PaymentApprovedOrder>();
    }

    public virtual long CustomerId { get; set; }
    public virtual string CustomerName { get; set; }
    public virtual Email EmailIdentity { get; set; }
    public virtual IList<PaymentApprovedOrder> CustomerPaidOrders { get; set; }
    public virtual ShoppingCart CustomerCart { get; set; }

    public virtual void AddPaidOrder(PaymentApprovedOrder order)
    {
        // SET ONE end of the association
        order.PaidByCustomer = this;

        // SET MANY end of the association
        CustomerPaidOrders.Add(order);
    }
}

The mapping file of Customer class is as follows:

<class name="Customer" table="CUSTOMER">
    <id name="CustomerId" column="CUSTOMERID" type="long" generator="native" />
    <property name="CustomerName" column="CUSTOMERNAME" type="string" />
    
    <component class="Email" name="EmailIdentity">
        <property name="EmailAddress" column="EMAILADDRESS" type="string" />
    </component>
    
    <list name="CustomerPaidOrders" cascade="save-update">
        <key column="CUSTOMERID" not-null="true" />
        <list-index column="PAIDORDER_LIST_POSITION" />
        <one-to-many class="PaymentApprovedOrder" />
    </list>
    
    <one-to-one class="ShoppingCart" name="CustomerCart" property-ref="CartOfCustomer" />
</class>

The Client Code to test this is as follows (where required, the additional information for other classes can be obtained from Article Part 1 to Part 4):

// Creating repositories for different entities
IRepository<ProductDescription> product_repo = new DBRepository<ProductDescription>();
IRepository<ShoppingCartSelection> cart_selection_repo = new DBRepository<ShoppingCartSelection>();
IRepository<ShoppingCart> cart_repo = new DBRepository<ShoppingCart>();
IRepository<Customer> customer_repo = new DBRepository<Customer>();

// Creating customer objects
Customer customer1 = new Customer { CustomerName = "AliceWonder" };
Customer customer2 = new Customer { CustomerName = "JimTreasure" };
Customer customer3 = new Customer { CustomerName = "OliverTwist" };

// Assigning email identities to customers
customer1.EmailIdentity = new Email { EmailAddress = "[email protected]" };
customer2.EmailIdentity = new Email { EmailAddress = "[email protected]" };
customer3.EmailIdentity = new Email { EmailAddress = "[email protected]" };

// Adding customers to the repository
customer_repo.addItem(customer1);
customer_repo.addItem(customer2);
customer_repo.addItem(customer3);

// Creating 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 = 60000, ProductName = "mobile" };
description2.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "GOOD PRODUCT. MUST BUY", ProductRating = 5.0 });
description2.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "Dull PRODUCT. Dont BUY", ProductRating = 0 });
description2.ProductUserReviews.Add(new ProductReview { UserEmailId = "[email protected]", UserComment = "OK PRODUCT. Can Buy", ProductRating = 3.0 });

// Adding product descriptions to the repository
product_repo.addItem(description1);
product_repo.addItem(description2);

// Customer is buying. So the first step is creating a shopping cart
ShoppingCart cart = new ShoppingCart(customer1);
cart_repo.addItem(cart);

ECommerceSellerSystem system = new ECommerceSellerSystem();

// Customer selects a product description to buy
ShoppingCartSelection selection1 = system.AddSelectionToCustomerCart(description1, cart, 1);
cart_selection_repo.addItem(selection1);

// Customer selects another product description to buy
ShoppingCartSelection selection2 = system.AddSelectionToCustomerCart(description2, cart, 2);
cart_selection_repo.addItem(selection2);

The results produced in ShoppingCartSelection by running this client code is shown in Figure 3:

3.jpg

FIGURE 3

Conclusion

So this sums up the Many-To-Many entity association coding in NHibernate. The next article will be on Inheritance persistence in Nhibernate. The way a database handles inheritance has caused bleeding in the hearts of many of us who have spent hours to capture a design that is polymorphic with extensive inheritance hierarchies only to find a db narrowing the design to rows, columns and keys. But a DB is pure business value and an absolute necessity in all enterprise platforms. Hence Nhibernate supports coding inheritance persistence very highly. Definitely in one method of coding inheritance in NHibernate which we will use, more than the other possible ways. We will see this in the next article. Enjoy NHibernate.

References

To gain a more comprehensive understanding of the subject, please read the next part,


Similar Articles