Article series
Introduction
After Part 1 which discussed One-to-One entity association, the next level of progression would have been Many-to-One mappings. A many-to-one mapping would require knowledge of collections (for the many end) and hence an intro for mapping collections is of paramount importance. So this article describes examples on mapping collections of valuetypes. First an example with a collection of strings which is not connected to an ecommerce scenario is shown which makes it very simple to understand the concepts of mapping a collection. Then it shall be applied to the e-commerce scenario to include certain improvements for mapping a collection of valuetypes. The collections we introduce are <SET>
and <LIST>
. NHibernate has a whole lot of collections to be mapped which can be checked out later by readers (with the official resources enlisted here), armed with the ideas presented here; but the most often used would be <LIST>
which also covers the mapping ideas for most collections.
Background
Resources
This official NHibernate Reference download covers full details for NHibernate. It is fairly elaborate and can be used for extensive reference. The link for it is: http://nhforge.org/doc/nh/en/index.html.
Description
In Part 1 of this article series it was mentioned Valuetypes do not have a table of their own and exist in the table of the entity on which they depend by allocating a column for each field of a valuetype in the entity's table (remember the example of Customer and Email in Part 1). So if there is a collection of valuetypes how can it be mapped? A collection cannot be accommodated in one column for each row as comma separated values (we will see the internals of object relational mapping for collections in detail in Part 3 of this article series while dealing with collections of entity types). NHibernate solves the problem by mapping a valuetypes collection to a database table called "COLLECTION TABLE". This article shows two such collection tables created for <set>
and <list>
and highlights the differences.
Set is a collection without any repeats and without any ordering information. List is a collection in which order is strictly preserved. So NHibernate must be capable of capturing this difference in how the ordering information is preserved in <list>
unlike a <set>
while creating collection tables for them. The first example (not connected to E-commerce scenario) shows a Person
class with a collection of favourite authors and a collection of favourite painters. The collection of favourite authors will be captured in a <set>
while a collection of favourite painters will be captured in <list>
. Figure 1 shows the C# code for the Person
class (top right side of the figure), the person table and the collection tables for authors and painters (top left side of the figure), and the Person.hbm mapping file (end of the figure). Now to the details of the figure and the various mappings.
Figure 1 - Note: I have removed the attribute "not-null=true" from <element column="AUTHORNAME"...>
in <set name="AuthorsSet">
to make the figure clear. I will explain more on this attribute for <set>
in the section titled "Structure of collection tables" later.
The C# code for the Person
class on the top right side of Figure 1 declares a SET collection for Author Names called AuthorsSet
. Look at the figure and follow the Blue arrow. You can see the mapping of the set collection from the C# code to the Person.hbm mapping file.
In the Person.cs file, the SET of Authornames is declared as:
public virtual Iesi.Collections.Generic.ISet<string> AuthorsSet { get; set; }
Note that it is Iesi.Collections.Generic.ISet<>
and not System.Collections.Generic
. The Iesi Collections DLL is available for download with the NHibernate library itself and a reference has to be set for it in the project. Also note that collections are declared by interfaces in classes and initialized in constructors with concrete types. This is mandatory.
In the Person.hbm mapping file, the SET of Authornames is mapped as follows:
<set name ="AuthorsSet" table ="AUTHORSSET">
<key column="PersonId"></key>
<element column ="AUTHORNAME" type="string" not-null="true"/>
</set>
So the Person.hbm mapping file indicates to NHibernate that the reference AuthorSet is a VALUETYPE SET because it is naming the collection table using the attribute table="AUTHORSET"
and also due to the tag <element>
in the <SET>
mapping. Further it indicates to NHibernate that the elements of this set are of type string
and the column name for the element in the collection table is given by column="AUTHORNAME"
. (Note: Just remember that for Entity associations of many-to-one or many-to-many, where entity collections are used, the tag <element>
will not be used inside <set>
, <list>
etc). Once NHibernate sees this in the mapping file, it creates a collection table for the set. Look at the Orange arrow in figure 1 which shows the collection table "AUTHORSET" created for the set "AuthorSet
" as declared in the Person.hbm mapping file with the column name set to be "AUTHORNAME" as mentioned in the .hbm file. The collection table shown in the figure is populated with values used for testing.
The C# class Person
also declares a System.Collections.Generic IList<string> PaintersList
for the list of Painters. Look at figure 1 and follow the Purple arrow. You can see the mapping of the List
collection from C# to the Person.hbm file.
In the Person.cs file, the LIST
of PainterNames is declared as:
public virtual IList<string> PaintersList { get; set; }
In the Person.hbm
file, the LIST
of PainterNames is mapped as follows:
<list name="PaintersList" table="PAINTERSLIST">
<key column ="PersonId"></key>
<list-index column ="PAINTERSLISTINDEX"></list-index>
<element column="PAINTERNAME" type ="string" />
</list>
Similar to the earlier case of SET of Authornames, the Person.hbm mapping file indicates to NHibernate that the reference PaintersList
is a Valutype list by naming the collection table using the attribute table="PAINTERSLIST"
and because of the tag <element>
in the <LIST>
mapping. Further it indicates to NHibernate that the elements of this set are of type "string
" and the column name for the element in the collection table is given by column="PAINTERNAME". Once NHibernate sees this in the mapping file, it creates a collection table for the List. Look at the Green arrow in figure 1 which shows the collection table "PAINTERSLIST" created for the list PaintersList
as declared in the Person.hbm mapping file. The collection table is populated with values used for testing.
The collection tables created for <SET>
and <LIST>
are shown below in figure 2. Note that there is a difference in the way NHibernate generates a collection table for a <SET>
and <LIST>
. Set has no order information. So Set just needs to preserve information for the elements in the AUTHORNAME column and to which person it belongs to in the PersonId column (see figure 2 - left side - AUTHORSSET table). The list contains an additional column called PAINTERSLISTINDEX
(see figure 2 - right side - PAINTERSLIST table) and it is specified in the Person.hbm mapping file. This column is required for a <list>
because a <list>
preserves ordering information and a zero based index can be used to retrieve an item from the list based on this ordering information
Figure 2
The values are populated according to the test code shown in Figure 3. Look at the order in which values are added in the code below in figure 3 for two persons, person 1 and person 2, and compare it with the values in table in Figure 2 above. Especially examine the values for PAINTERSLISTINDEX
. When painters were added for person 1, the value of PAINTERSLISTINDEX
starts with 0 and is incremented for each element added to the list of painters of person 1. When painters were added to person 2, the PAINTERSLISTINDEX
column gets reinitialized to 0 to denote that this is the first element for person 2 and increments by 1 for each element added to the painters list. Thus the ORDER information for a list is preserved. This is extremely important because it is quite possible a client code depends on the ordering of elements of a collection or an index for fast access to elements in a collection, in which case it should use a <LIST>
and not <SET>
.
Figure 3
The C# code to add to a Repository is shown below in Figure 4. We will look at better solutions to handle Repository methods in part 8 of this article series when we look at lazy initialization /fetches. For now, to know the problem in handling repository functionality this way, see Part 1 of this article series. We will see better solutions for this problem in Part 8 of the article series.
Figure 4
Structure of the collection tables
The structure of the collection table has interesting details which influence the design decisions to be made while coding using NHibernate. The structure of collection tables generated for the <SET>
and <LIST>
in the example completed previously is shown in Figure 5.
Figure 5
It is interesting to note that the PAINTERSLIST collection table generated for <list>
collections has a PRIMARYKEY which is a composite of PersonId and PAINTERSLISTINDEX (the index of the list). Later while explaining Many-To-One associations we will see that while handling collections with order information like <list>
shown here, because of this <LIST-INDEX>
element playing a crucial role in preserving order information and acting as part of a composite key, mapping files have to be written with additional care without which the order may be lost and errors may occur. Also note that in the <SET>
collection table, i.e., AUTHORSSET table has all columns to be set as primarykey to avoid repeats to conform to set definition. This is mandatory for set collections. Also note that as said in figure 1, the elements added for the set must have not-null=true
attribute set for the <element>
tag. Why? Because in a set we want only unique elements to be added and hence we make the element a part of the primary key. But the items added with the <element>
tag will be set as primary key only if its "not-null=true
" attribute is set (obviously because by DB fundamentals it is well known that a candidate key can become a primary key only if it is not null and distinct for each row). So in the case of <set>
it is mandatory to say not-null=true
like it is shown below in the code snippet:
<set name ="AuthorsSet" table ="AUTHORSSET">
<key column="PersonId"></key>
<element column ="AUTHORNAME" type="string" not-null="true"/>
</set>
In figure 1, if you see the mapping file at the bottom of the figure, I would have removed the not-null = true
attribute for the <set>
to ensure clarity in drawing the arrows. But it is required because we want the element added to a set to be unique. Hence in the structure of the collection table for <set>
, you will have both its foreignkey from the entity and the item in the <element>
set together as a composite key. Refer to figure 3 to see this in the AUTHORSSET table where the keys are shown clearly.
Using the code
Continuing with the e-commerce example
Now that an idea of collections has been established, the next step is to implement it in the ecommerce scenario. "To each product description in the website, a customer will be able to input reviews of the product with a rating of the product and useful comments on the products." Without ploughing to object oriented analysis details, we will safely conclude there are two classes for this scenario namely ProductDescription
and ProductReview
. While ProductDescription
is obviously an Entity because it has an independent lifetime and will be shared by different items that fit the same ProductDescription
, ProductReview
is definitely a Valuetype because it is always dependent on ProductDescription
. What business value does a Product Review has if the Product Description is removed from the website because it is discontinued by the company policy? A review for a product cannot be shared by other products. Hence when a product is discontinued, its reviews will also need to be dumped. So it is now confirmed that Product Review is valuetype. Also Product Review is definitely a Valuetype collection because more than one customer may leave a review. We know that Valuetype collections in NHibernate are stored in a separate table called collection table which is very convenient because we do not want ProductReview columns denormalizing the ProductDescription table. Earlier, we were mapping collections of string
s. Hence we used an <element>
tag with type=string to denote that items of the collection are of type string
. But now we have to map the class ProductReview
which is a type with its own set of properties like UserComment
, UserEmailId
, ProductRating
. NHibernate has a <composite-element>
tag to map a valuetype class as an element of a collection and all its properties are captured inside the <composite-element>
tag as <property>
. So in our example if ProductReview.cs is defined as follows:
In ProductReview.cs:
public class ProductReview
{
public ProductReview() { }
public virtual string UserComment { get; set; }
public virtual string UserEmailId { get; set; }
public virtual double? ProductRating { get; set; }
}
In ProductDescription.hbm, its mapping is defined as follows:
<list table="PRODUCTREVIEWS" name="ProductUserReviews">
<key column="PRODUCTDESCRIPTIONID" not-null="true"></key>
<list-index column="REVIEWLIST_POSITON"></list-index>
<composite-element class="ProductReview">
<property name="UserComment" type="string" column="USERCOMMENT"/>
<property name="UserEmailId" type="string" column="USEREMAILID"/>
<property name="ProductRating" type="double" column="USERRATING"/>
</composite-element>
</list>
In the code snippet above, since ProductReview
is a valuetype collection, it is declared with <list>
mapping in ProductDescription.hbm (ProductDescription
is the entity type on which the valuetype ProductReview
is dependent. So ProductReview
is mapped in the mapping file of ProductDescription
i.e., ProductDescription.hbm). Since it's a valuetype collection, its collection table is also defined as an attribute for list by saying table="PRODUCTREVIEWS" table. All the properties of the ProductReview
class are then wrapped inside a <composite-element>
tag. Now take a look at figure 6 below which shows the mapping of the ProductReview
class in the ProductDescription.hbm file. Look at the Orange arrow in figure 6. It shows the C# declaration of the ProductReview
collection, i.e., IList<ProductReview>
in ProductDescription
class and in the mapping file ProductDescription.hbm at the bottom of the figure, it is correspondingly defined by a <list>
tag on the other end of the orange arrow. Next look at the Green arrow which at the top end of the arrow shows the definition of ProductReview
in the C# class along with its properties and the other end of the arrow in the bottom defines the properties of ProductReview
wrapped inside a <composite-element>
in the ProductDescription.hbm mapping file. See that all the properties of the ProductReview
C# class at the top end are defined in the mapping file below as a <property>
wrapped inside the <composite-element>
tag. This was also shown in the code snippet.
Figure 6
The structure of the ProductReview valuetype collection table is shown below in Figure 7. Since the ProductReview
collection is a value type collection of type <LIST>
, REVIEWLIST_POSITION
is defined as shown in figure 7 to preserve the ordering information of elements in a list.
Figure 7 - PRODUCTREVIEW valuetype collection table
The ProductReview table with values populated with test data is shown below along with the client test code in Figure 8:
Figure 8 - PRODUCTREVIEWTABLE generated for the client code shown in figure.
<LIST>
is not only the most commonly used collection type but it also sets the stage for other collection types which you can explore.
Points of Interest
The way collections are mapped gives an idea for working with ManyToOne entity mappings next, though it is fully different from ValueType collections discussed here.
The foundation has been set to work on many-to-one mappings in part 3 of this 8 part article series next.