Click here to Skip to main content
Click here to Skip to main content

Object Relational Mapping (ORM) using NHibernate - Part 5B of 8: Coding Many-to-Many Entity Associations

, 21 May 2013
Rate this:
Please Sign up or sign in to vote.
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, and lazy initializations/fetches using NHibernate.

Article series

Introduction

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

Background

The background mentioned in Part 5A holds good here. Taking clues from it, what's worth remembering is: a Many-To-Many entity association in NHibernate can be represented exactly how a many-to-many association is split in OOAD (use an Association class and split it to two one-to-many associations).

Using the code

E-Commerce example scenario

The scenario that gives the example for a many-to-many entity association is,

When a customer visits an e-commerce site, he opens various product descriptions in 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 uses the ShoppingCart class as an Association class with a collection of shopping cart items in it (exactly like a Ticket Association class with a collection of passengers in Ticket in the previous article 5A). This is the usual practice but there is a better way to model this scenario.

Looking back at the domain, the shopping cart and the product description are 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 into it and a ProductDescription may be selected into many ShoppingCarts (each cart may belong 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 a 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 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 a many-to-many association between ShoppingCart and ProductDescription.

The next thing to be captured to complete this scenario is that a shopping cart is always associated to 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 the shopping cart and customer or else it is a one-to-one association. We use a one-to-one association between the customer and shopping cart. 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 anything. Refer to Part 4 of this article series for using and the 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 it mean we have to use an optional one-to-one association here to avoid nulls in the foreign key 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 primary key from the CUSTOMER table to the SHOPPINGCART table. In a one-to-one association between Customer and ShoppingCart, the primary key CUSTOMERID of the Customer table will become a foreign key column in the 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 primary key SHOPPINGCARTID of the SHOPPINGCART table, then naturally for customers without a shopping cart we will have a null value for the foreign key column SHOPPINGCARTID posted in the CUSTOMER table to realize the association. These are finer details that enhance the quality without taking an 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 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 the Orange arrow. The next thing to observe is that the collection end of both the one-to-many associations 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 (code 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 the code snippet for the association class ShoppingCartSelection shown below and in Figure 1.

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 mapping file 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>
      <key-property column="SHOPPINGCARTID" name ="class2Key" 
        access="field" type="long"></key-property>
    </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 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 foreign keys 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 composite key 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 are marked inverse. Refer to Figure 2 below where I show the structure of the table formed for SHOPPINGCARTSELECTION. Note that both foreign key columns together form the composite key 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.

Figure 2

All this is OK but where is the advantage in this approach? The advantages are that the Customer object can access the ShoppingKart object which in turn has its collection of ShoppingCartSelection instances associated which contains information of ProductDescriptions has been bought and can now set buying priorities (according to the user budget) to make an order from the kart 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 high profits, 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 with 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 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 the 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"></key>
      <list-index column ="PAIDORDER_LIST_POSITION"></list-index>
      <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 the required additional information for other classes can be got from articles Part 1 to Part 4).
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>();
Customer customer1 = new Customer { CustomerName = "AliceWonder"};
Customer customer2 = new Customer { CustomerName="JimTreasure" };
Customer customer3 = new Customer { CustomerName = "OliverTwist" };
customer1.EmailIdentity = new Email { EmailAddress = 
  "<a href="mailto:alice@wonderland.com">alice@wonderland.com</a>" };
customer2.EmailIdentity = new Email { EmailAddress=
  "<a href="mailto:jim@treasureisland.com">jim@treasureisland.com</a>" };
customer3.EmailIdentity = new Email { EmailAddress = 
  "<a href="mailto:olivertwist@london.com">olivertwist@london.com</a>" };
//customer 1 add to repository
customer_repo.addItem(customer1);
//Extra customers added to repository - these have no carts 
customer_repo.addItem(customer2);
customer_repo.addItem(customer3);
ProductDescription description1 = 
  new ProductDescription{ ManufacturerName="samsung",Price=60000,ProductName="mobile"  };
description1.ProductUserReviews.Add(new ProductReview { UserEmailId="<a href="mailto:a1@a.com",
  UserComment="GOOD">a1@a.com",
  UserComment="GOOD</a> PRODUCT.MUST BUY",ProductRating=5.0 });
description1.ProductUserReviews.Add(new ProductReview { UserEmailId = 
  "<a href="mailto:b1@b.com">b1@b.com</a>", 
  UserComment = "Dull PRODUCT.Dont BUY", ProductRating=0 });
description1.ProductUserReviews.Add(
  new ProductReview {UserEmailId="<a href="mailto:c1@c.com",
  UserComment="OK">c1@c.com",
  UserComment="OK</a> PRODUCT.Can Buy",ProductRating=3.0 });
ProductDescription description2 = new ProductDescription { ManufacturerName = 
  "nokia", Price = 60000, ProductName = "mobile" };
description1.ProductUserReviews.Add(new ProductReview { UserEmailId = 
  "<a href="mailto:a2@a.com">a2@a.com</a>", 
  UserComment = "GOOD PRODUCT.MUST BUY", ProductRating = 5.0 });
description1.ProductUserReviews.Add(new ProductReview { UserEmailId = 
  "<a href="mailto:b2@b.com">b2@b.com</a>", 
  UserComment = "Dull PRODUCT.Dont BUY", ProductRating = 0 });
description1.ProductUserReviews.Add(new ProductReview { UserEmailId = 
  "<a href="mailto:c2@c.com">c2@c.com</a>", 
  UserComment = "OK PRODUCT.Can Buy", ProductRating = 3.0 });
product_repo.addItem(description1);
product_repo.addItem(description2);        
//Customer is buying. So fist step is shopping cart created
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);
//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, cart,2);
//system adds selection to repository (adds to selection - viewable from cart&descriptions)
cart_selection_repo.addItem(selection2);

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

Figure 3

Conclusion

So this sums up the many-to-many entity association coding in NHibernate. The next article would 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 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 other possible ways. We will see this in the next article. Enjoy NHibernate.

License

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

About the Author

Software Developer developing in c#.net.

Comments and Discussions

 
GeneralMy vote of 4 PinmemberKlaus Luedenscheidt24-Oct-12 19:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 21 May 2013
Article Copyright 2012 by Anand Lakshminarasimhan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid