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

Coding Many-to-Many Entity Associations

This is the fifth part of the article series. The fifth part is comprised of 2 sections: 5 - A and 5 - B. Part 5 - A article will concentrate on Many-to-Many Entity Association with a commonly used example but the model and code is a little different. Part 5 - B will work on many-to-many entity association examples from our e-commerce scenario considered for all articles in this series.

The most interesting of all entity associations is the Many-To-Many entity association. Yet it is avoided by most. The many-to-many entity association in NHibernate can be done exactly in the way it is captured in OOAD. How NHibernate can be used to achieve persistence for a many-to-many association is our point of interest.

Background

The NHibernate way of dealing with many-to-many entity associations could be inclined to follow the usual route that OOAD takes, as mentioned earlier. Use an association class and split the many-to-many association into two one-to-many association is the OOAD way of dealing with the many-to-many association in objects (shown in Figure 1). So by following this, our many-to-many entity association becomes a mapping of two one-to-many entity associations which we already studied in Part 3 of this article series. If modeled correctly for a domain scenario, a many-to-many entity association encapsulates and captures all information required of the link clearly on an association class and the most unique thing about the OOAD way of handling a many-to-many association is that this association class is not an artificial class created exclusively to split a many-to-many association but most often it exists naturally as part of the domain itself. The sample provided in Part 5 - Section A will reaffirm this and shows how NHibernate is used to achieve persistence in this.

Manytomany

Figure 1

Sample For Many-to-Many Entity Association

The common scenario of use for the Many-To-Many association is that a particular type of system may offer many services, each of which has many users and the user may have the choice of using a particular type of service among the many services in the system. The allotment of a user to a service is to be captured. Examples of this scenario are countless like passengers and railways (or could be other transport services), moviegoers and multiplexes, hotel rooms and room occupants, etc. Let us consider the example of railways which best captures the scenario of many-to-many association. A passenger can travel in many trains. A train can have many passengers. So in a railway reservation system, both a train and a passenger are entity classes and the association between the passenger and train is many-to-many. The association class is the "Ticket" which breaks this many-to-many association and binds a particular instance of a passenger to a particular instance of a train. Also a train now has many tickets (one-to-many) and a passenger could have many tickets to many trains (one-to-many). Every developer knows this scenario because it is the most commonly used scenario. Our topic of discussion is to reveal how to use NHibernate to map a many-to-many entity association.

So our sample scenario is a console application to persist the many-to-many association in an online train ticket reservation. We will model the scenario slightly differently because, in normal practice, it is not necessary for the passenger himself to book his ticket in advance and also a railway ticket that is booked in advance could have names for more than one passenger if listed so in a booking form (family bookings in 1 ticket with entire family names). So in our online ticket reservation, a registered user is allowed to book a ticket for many trains. A train will have tickets booked by many registered users. Hence the association between a registered user wishing to book a ticket on many trains and a train getting booked by many registered users is a many-to-many entity association. The user submits a form that contains the list of passengers traveling and a particular train he needs to book the ticket for. The association class is the online "Ticket" which will associate a particular user booking the ticket and the particular train for which the ticket is booked with the list of passengers for that ticket as enlisted by the registered user in the booking form. The tickets are issued by the class TicketCounter which is the reservation system issuing the ticket. We will ignore the date and time of the train and differentiate a train only by name as our interest is limited to many-to-many entity associations and using NHibernate for persisting it. The advantage of mapping the many-to-many entity class association between registered users and trains is, the user instance will have the collection of tickets booked by that particular registered user, and the train will have the collection of tickets booked for that particular train and ticket will a have list of passenger and other information pertaining to a passenger all persisted by NHibernate.

Now refer to Figure 2. It shows the mapping for the Train and User (registered user of the Reservation System) class. The many-to-many entity association between Train and User (registered user of the Reservation System) is split into two one-to-many associations by the association class "Ticket" and this is shown by the orange arrow. Also note that as expected now neither Train nor User is referencing each other though they have a many-to-many association between them in the domain. They only have a reference for a one-to-many association with the association class "Ticket" which is represented in C# code by the ISet<Ticket collection and mapped to <set collection both in the User and Train shown by the blue and purple arrows.

2rev

Figure 2

The C# code snippet below shows the Ticket. cs class. Note that the ticket class uses a composite key. All along we have advocated the policy of using surrogate keys with generators and never composite keys. But a many-to-many association is an exception to this practice because a composite key fits incorrectly here. This composite key class has been defined by us separately. In the downloadable project, you will see this CompositeKey class in the Ticket.cs C# file. In the constructor of the Ticket observe that this composite key is set to the primary key values of the user buying the ticket and the train for which the ticket is bought. Captures domain scenario for a Ticket correctly. Then the associations have to be set in the constructor for the two bidirectional one-to-many associations. Each ticket will have the list of passengers with information captured as filled out by the Registered User in the booking form.

public class Ticket
{
 public Ticket() 
{
 // CompositeID is the composite key class defined separately
 CompositeId = new CompositeKey();
 // PASSENGERS LIST PER TICKET
  Passengers = new List<Person>();
 }
 public Ticket(User user, Train train, IList<Person> passengers)
 {
 // CompositeID is the composite key from Train and Passenger
  CompositeId         = new CompositeKey();
 //SET ASSOCIATION END
  ReservedByUser      = user;
  ReservedForTrain    = train;
 //SET COMPOSITEE KEY
  CompositeId.trainKey = train.TrainId;
  CompositeId.userKey = user.UserId;
  //OTHER ENDS OF ASSOCIATION
  Iesi.Collections.Generic.ISet<Ticket> userList = user.UserBookings;
  Iesi.Collections.Generic.ISet<Ticket> trainList = train.TrainBookings;
  //SET OTHER ASSOCIATION END
   userList.Add(this);
   // CAN COMBINE PREVIOUS lines as user.UserBookings.Add(this) 
   //SET OTHER ASSOCIATION END
  trainList.Add(this);
  TicketCounter++;
 TicketNumber = TicketCounter.ToString();
 //ADD PASSENGERS TO TICKET
  Passengers = passengers;
}
 public virtual CompositeKey CompositeId { get; set;
 public virtual string TicketNumber { get; set; }
 public virtual User ReservedByUser { get; set; }
 public virtual Train ReservedForTrain { get; set; }
 protected virtual int TicketCounter { get; set; }
 public virtual IList<Person> Passengers { get; set; }
 }

The mapping code snippet Ticket.hbm is shown below. There are no surprises here. The only new tag in the mapping code is the use of the <composite-id tag for the composite key defined for the Ticket class. But what is best captured in the mapping file of the association class is the two <many-to-one tags with the Train class and the User class. Remember that we have taken the OOAD approach to deal with the many-to-many association and use NHibernate with this approach. According to this approach, the <many-to-many association between Train and User must be split into two <many-to-one associations with the "Ticket" association class. The two <many-to-one mappings in the Ticket.hbm mapping file below show clearly how this is done in NHibernate.

<class name="Ticket" table="TICKET" >
<composite-id name="CompositeId" class="CompositeKey">
<key-property name="userKey" access="field" column="USERID" type="long"/>
<key-property name="trainKey" access="field" column="TRAINID" type="long"/>
</composite-id>
<property name="TicketNumber" type="string" column="TICKETNUMBER" not-       null="true" />
<many-to-one class="User" name="ReservedByUser" not-null="true" unique="true"        insert="false" update="false">
<column name="USERID"></column>
</many-to-one>
<many-to-one class="Train" name="ReservedForTrain" not-null="true"        unique="true" insert="false" update="false">
<column name="TRAINID"></column>
</many-to-one>
<list table="TICKET_PASSENGERS" name="Passengers" cascade="save-update">
 <key not-null="true">
 <column name="USERID"></column>
<column name="TRAINID"></column>
</key>
<list-index column="PASSENGER_LIST_POSITION"></list-index>
<one-to-many class="Person"/>
</list>
</class>
<class name="Ticket" table="TICKET" >
<composite-id name="CompositeId" class="CompositeKey">
<key-property name="userKey" access="field" column="USERID" type="long"/>
<key-property name="trainKey" access="field" column="TRAINID" type="long"/>
</composite-id>
<property name="TicketNumber" type="string" column="TICKETNUMBER" not-       null="true" />
<many-to-one class="User" name="ReservedByUser" not-null="true" unique="true"        insert="false" update="false">
<column name="USERID"></column>
</many-to-one>
<many-to-one class="Train" name="ReservedForTrain" not-null="true"        unique="true" insert="false" update="false">
<column name="TRAINID"></column>
</many-to-one>
<list table="TICKET_PASSENGERS" name="Passengers" cascade="save-update">
<key not-null="true">
<column name="USERID"></column>
<column name="TRAINID"></column>
</key>
<list-index column="PASSENGER_LIST_POSITION"></list-index>
<one-to-many class="Person"/>
</list>
</class>

Do note the fact that for both the one-to-many associations shown in the code snippet above and in Figure 2, we have put both ends of the association of collections to be inverse (by using insert=false and update=false, inverse=true). People who have read the article series will know only one end has to be made inverse. Why both ends are made inverse here? Because we use a transaction in code (IssueTicket method) to ensure that inserts to Ticket, User, and Train tables are atomic. Also most importantly, the primary key of both tables i.e. User and Train have been made the composite key in the association class thus representing the associations and forming a composite key. Hence there is no need to automatically generate additional SQL statements on both ends of the association. So they are made inverse at both ends. The structure of this solution can be understood more clearly in Microsoft Visual Studio 2012 when you look at the structure of the table formed for the Ticket - association class. It will show the composite key made from two primary keys which are also part of the two associations very clearly. Use the downloaded code supplied in Visual Studio 2012 to observe this. This solution is good. There is another way of using the "join" table but I find this solution more appropriate and objects oriented.

The downloadable solution (Microsoft Visual Studio 2012 Trial Version) with this article contains the client code also. They are console-based projects used to show the many-to-many entity association explained here. The change to run the code is to add a service-based database to the project named RailwayReservationSystem project. The steps to do it is familiar to all but I will just describe it to complete the article: right-click on the RailwayReservationSystem project. In the popup menu that appears select "Add" then select "New Item". The wizard for adding the new item will open. In this select "Service-based Database" and click the OK button. A dialog will open later which you can cancel. Now a new database is added to your project space. Select it and you will see its properties in the properties windows of Solution Explorer. Just copy its full path and use it in the connection string for .config configuration files in the RailwayReservationSystem project and in the app. config file in the client project. We can see the Tickets, Users, and Trains persisted in the respective tables in the database you added in VS2012 quite clearly when the client project is run. Do not use the sample download project provided for experimenting with database fetches. Fetches will be covered in Article 8. For now, enjoy experimenting with the persistence of associations in objects using NHibernate. Check the persistence of Train, User, and Ticket to DB using the "SQL" menu in VS2012 and by writing a SQL query in the editor available from this menu itself.

Conclusion

It is a huge advantage to code NHibernate's many-to-many entity association exactly in the way we model it in OOAD. The reason was mentioned in the article itself. The association class used in OOAD to break the many-to-many association is not an artificial class used only for this purpose. It exists as part of every domain itself as we saw here and captures the required information precisely. The rental between houses and tenants in a rental agency system, the lease between landowners and leasees in a real estate system, many kinds of tickets in various domains, subscription plans between consumers and services by service providers in service provider systems, room booking/room occupancy between hotel rooms and occupants in hotel systems, consultation record between doctors and patients in Hospital Systems are all examples of an association class in those domains for the listed many-to-many associations. So having NHibernate map this many-to-many entity association with the respective association class for that domain is very useful. The next article 5 - B will discuss the many-to-many entity associations in our sample scenario. Enjoy NHibernate.  

Reference

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


Similar Articles