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 ProductDescription
s selected into it and a ProductDescription
may be selected into many ShoppingCart
s (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();
ShoppingCartSelectionId.class1Key = product.ProductDescriptionId;
ShoppingCartSelectionId.class2Key = cart.ShoppingCartId;
product.CartSelectionsWithThisProduct.Add(this);
cart.CartSelections.Add(this);
CurrentProduct = product;
ParentCart = cart;
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 ProductDescription
s 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)
{
order.PaidByCustomer = this;
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_repo.addItem(customer1);
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);
ShoppingCart cart = new ShoppingCart(customer1);
cart_repo.addItem(cart);
ECommerceSellerSystem system = new ECommerceSellerSystem();
ShoppingCartSelection selection1 = system.AddSelectionToCustomerCart(description1, cart,1);
cart_selection_repo.addItem(selection1);
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:
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.