Before reading this article, please go through the following articles:
-
-
-
-
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. The 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 ecommerce scenario considered for all articles in this series.
The most interesting of all entity association 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 acheive persistence for a many-to-many association is our point of interest.
BACKGROUND
The NHibernate way of dealing with many-to-many entity association 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 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 acheive persistence in this.
FIGURE 1
SAMPLE FOR MANY-TO-MANY ENTITY ASSOCIATION
The common scenarios of use for Many-To-Many association is that a particular type of system may offer many services, each of which have 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 is countless like passengers and railways (or could be other transport services), moviegoers and multiplexes, hotelrooms and roomoccupants etc.
Let us consider the example of railways whch 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 which 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 the ticket in many trains and a train getting booked by many registered users is 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 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 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 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.
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 the Ticket observe that this composite key is set to the primarykey 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 association. 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 suprises 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 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 code snippet above and in the 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 impotantly, 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 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 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 changes 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 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 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 "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 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 like we saw here and captures the required information precisely. The rental between houses and tenants in rental agency system, lease between landowners and leasees in a real estate system, many kinds of tickets in various domains, subscription plan between consumer 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 to 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.