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

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

By , 23 Nov 2012
 

PART 1

PART 2

PART 3

PART 4

PART 5 A

PART 6

PART 7

Introduction

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

Background

The background mentioned in Article PART 5 A holds good here. Taking clues from it, what's worth remembering is: Many-To-Many entity association in NHibernate can be represented exactly how a many-to-many association is split in OOAD (Use 1 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 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 use ShoppingCart class as Association class with a collection of shoppingcart items in it (exactly like Ticket Association class with collection of passengers in Ticket in the previous Article 5 A) . 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 is associated by the link ShoppingCartSelection which represents a particular item selected by the customer and dropped into particular ShoppingCart. So, a ShoppingCart may have many ProductDescriptions selected into it and a ProductDescription may be selected into 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 association with itself. Primary concern before finalizing ShoppingCartSelection as association class should be is it Entity class or Value type based on ShoppingCart. Order will be based on ShoppingCartSelection and when a order is made a shopping cart will cease to exist but its ShoppingCartSelection must live beyond it inside a Order for further processing like Payment and Shipping. Hence ShoppingCartSelection is Entity class only and can be finalized as an association class for 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 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 a 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 using and advantages of optional one-to-many associations. The primary advantage in using optional one-to-one association would be avoiding null keys in foreign key column. Does it mean we have to use optional one-to-one association here to avoid nulls in foreignkey column? Not necessarily. According to part1 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 CUSTOMER table to SHOPPINGCART table. So in a one-to-one association beteen Customer and ShoppingCart, the primarykey CUSTOMERID of Customer Table will become a foreignkey column in SHOPPINGCART table. There wont be a null value for customers without shopping cart simply because such a row will not be added to 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 shopping cart we will have a null value for foreignkey column SHOPPINGCARTID posted in CUSTOMER table to realize the association. These are finer details that enhances quality of without taking a extra effort. A bidirectional one-to-one association can be handled by using the attribute property-ref (Refer part 1 of the article series for more on this). If we capture this one-to-one association as 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 association with itself and both these classes. Observe this in Figure 1 shown clearly with Orange arrow. 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 many-to-one tag in 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 end of the association. So how is this bidirectional association created and populated to database?Explanation is given in the next paragraph of this article. For now observe the inverse on both ends in code snippet for 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 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>
      <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 a 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 a 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 end 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 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 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.

FIGURE 2

 All this is ok but where is the advantage in this approach? The advantages are that the Customer object can access ShoppingKart object which inturn 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 a 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 highprofits, the ecommerce site can use this association to find which customer has dropped the highvalue 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 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 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 required additional information for other classes can be got from Article Part 1 to Part4).
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

Points of Interest

Conclusion

So this sums up the many-to-many entity association coding in NHibernate. 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 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.

PART 1

PART 2

PART 3

PART 4

PART 5 A   

PART 6

PART 7

License

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

About the Author

Anand Lakshminarasimhan
India India
Member
Software Developer developing in c#.net.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 4memberKlaus Luedenscheidt24 Oct '12 - 19:50 

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 23 Nov 2012
Article Copyright 2012 by Anand Lakshminarasimhan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid