Article series
Introduction
This is the fifth part of the article series and will comprise of two sections: 5A and 5B. Part 5A will concentrate on Many-to-Many entity associations and highlight an example commonly used but model and code are a little different. Part 5B will work on many-to-many entity association examples from our ecommerce 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 associations is the OOAD way of dealing with many-to-many associations 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 5A will reaffirm this and show how NHibernate is used to achieve persistence in this.
Figure 1
Using the code
Sample for a Many-to-Many Entity Association
The common scenarios of use for a 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 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 a many-to-many association. A Passenger can travel in many Trains. A Train can have many Passengers. So in a railway reservation system, both Train and Passenger are entity classes and the association between 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.
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 the passenger himself must book his ticket in advance and also a railway ticket which is booked in advance could have names for more than one passenger if listed so in the booking form (family bookings in one 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 the ticket in 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 travelling 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 association and using NHibernate for persisting it. The advantage of mapping the many-to-many entity class association between the registered user and the 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 the ticket will have a list of passengers 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 Reservation system) classes. The many-to-many entity association between Train
and User
(Registered User of Reservation system) is split to 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 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 the <set>
collection both in User
and Train
shown by the blue and purple arrows.
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 in correctly 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 Ticket
, observe that this composite key is set to the primary key values of User buying the Ticket and Train for which the ticket is bought. It captures the 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 = new CompositeKey();
Passengers = new List<Person>();
}
public Ticket(User user, Train train, IList<Person> passengers)
{
CompositeId = new CompositeKey();
ReservedByUser = user;
ReservedForTrain = train;
CompositeId.trainKey = train.TrainId;
CompositeId.userKey = user.UserId;
Iesi.Collections.Generic.ISet<Ticket> userList = user.UserBookings;
Iesi.Collections.Generic.ISet<Ticket> trainList = train.TrainBookings;
userList.Add(this);
trainList.Add(this);
TicketCounter++;
TicketNumber = TicketCounter.ToString();
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 a many-to-many association and used NHibernate according to this approach. According to this approach, the <many-to-many>
association between Train
and User
must be split to two <many-to-one>
associations with the Ticket
association class. The two <many-to-one>
mappings in the Ticket.hbm mapping file below shows 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>
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
). Those who have read the article series will know only one end has to be made inverse. Why are both ends 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 primarykey 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 in 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 "join" table for many-to-many associations but i find this solution more appropriate and object oriented.
The downloadable solution (MS 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 changes to run the code is to add a service based database to the project named RailwayReservationSystem. 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 gets added to your project space. Select it and you will see its properties in the Properties window of Solution Explorer. Just copy its full path and use it in the connection string for the .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 persistence of associations in objects using NHibernate. Check the persistence of Train
, User
, 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 many-to-many entity association exactly 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 like 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, all kinds of tickets in various domains, a subscription plan between consumer and services by service providers in a service provider system, 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 association classes in those domains for the listed many-to-many associations. So having NHibernate to map this many-to-many entity association with the respective association class for that domain is very useful. The next article 5B will discuss the many-to-many entity associations in our sample scenario. Enjoy NHibernate.