Object Relational Mapping (ORM) Using NHibernate - Part 2 of 8

This article provides examples of mapping collections of Value Types.

Coding value type Collections in NHibernate

PART 3

INTRODUCTION

After part 1 which discussed One-to-One entity association, the next level of progression would have been many-to-one mapping. The 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 of mapping collections of value types. 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 ecommerce scenario to include certain improvements for mapping a collection of value types. The collections we introduce are <SET> and <LIST>. NHibernate has many 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.

RESOURCES

This official NHibernate Reference download covers the full details for NHibernate. It's 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 that value types 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 value type in the entity's table (remember the example of Customer and Email in part 1). So if there is a collection of value types how can it be mapped? A collection cannot be accomodated in 1 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 value types collection to a database table called "COLLECTION TABLE". This article shows two such collection tables created for <set> and <list> and highlights the differences.
A Set is a collection without any repeats and without any ordering information. A 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 a <list> unlike a <set> while creating collection tables for them.

The first example (not connected to an ECommerce 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 a <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), the Person.hbm mapping file (end of the figure). Now to the details of the figure and various mappings.

Coding-Many-to-One-Mapping-In-NHibernate.jpg

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 in the top-right side of the Figure 1 declares a SET collection for Author Names called AuthorsSet. Look at Figure I and follow the BLUE Arrow. You can see the mapping of the set collection from 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 initiliazed in constructors with concrete types. This is mandatory.

In 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 value type SET because it is naming the collection table using the attribute table="AUTHORSET" and also due to the tag <element> in <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 in <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 .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 the 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 value type 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 the <SET> and <LIST> are shown below in Figure 2. Note that there is a difference in the way nhibernate generates collection table for <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 was 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.

Table-with-PAINTERSLISTINDEX-column.jpg

Figure 2

The values are populated according to the testcode shown in Figure 3. Look at the order in which values are added in 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 person1, the value of PAINTERSLISTINDEX starts with 0 and is incremented for each element added to the list of painters of person1. When painters were added to person2, 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 ordering of elements of collection or an index for fast access to elements in a collection, in which case it should use a <list> and not <set>.

Repository-in-NHibernate.jpg

Figure 3

The C# code to add to the 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.

Problem-in-NHibernate.jpg

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.

STRUCTURE-OF-THE-COLLECTION-TABLES-in-NHibernate.jpg

Figure 5

It's 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 association 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. the 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 a set must have the not-null attribute set to true 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 the primarykey only if its "not-null" attribute is set to true (obviously because by db fundamentals it's 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 its 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's 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.

CONTINUING WITH ECOMMERCE 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 diving into 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 various items that fit the same ProductDescription, Product Review is definitely a value type because it is always dependent on Product Decryption. What business value does a Product Review have if the Product Description is removed from a website because it is discontinued by 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 a Product Review is a value type. Also Product Review is definitely a value type Collection because more than one customer may leave a review. We know that value type 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 strings. Hence we used a <element> tag with type=string to denote that items of collection are of type string. But now we have to map class "ProductReview" which is a type with its own set of properties like UserComment, UserEmailId and ProductRating. NHibernate has a <composite-element> tag to map a value type class as an element of 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>

Then ____.

In the code snippet above, since ProductReview is a value type collection, it is declared with a <list> mapping in ProductDescription.hbm. (ProductDescription is the entity type on which the value type ProductReview is dependent. So ProductReview is mapped in the mapping file of ProductDescription i.e ProductDescription.hbm). Since it's a value type collection, its collection table is also defined as an attribute for a list by saying table="PRODUCTREVIEWS" table. All the properties of ProductReview class is 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 the 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 that defines the properties of the ProductReview wrapped inside a <composite-element> in the ProductDescription.hbm mapping file. See that all the properties of the Product Review C# class at the top end are defined in the mapping file below as <property> wrapped inside the <composite-element> tag. This was also shown in the code snippet.

Mapping-in-NHibernate.jpg

Figure 6

The structure of the ProductReview value type collection table is shown below in Figure 7. Since the ProductReview collection is a value type collection of type <LIST>, the REVIEWLIST_POSITION is defined as shown in Figure 7 to preserve the ordering information of the elements in a list.

PRODUCTREVIEW-VALUETYPE-COLLECTION-TABLE.jpg

Figure 7 - PRODUCTREVIEW value type COLLECTION TABLE.

The ProductReview table with values populated with test data is shown below along with the client test code in Figure 8:

PRODUCTREVIEWTABLE-GENERATED-FOR-THE-CLIENT-CODE-SHOWN-IN-FIGURE.jpg

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.

CONCLUSION

The foundation has been set to work on many to one mappings in Part 3 of this 8 part article series next.

Prerequisites

Object relational mapping ORM using Nhibernate Part 1of 8