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

Before reading this article, Please go through the Previous parts,

Introduction

After part 1, which discussed the 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 that are not connected to an e-commerce 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 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 of NHibernate. It's fairly elaborate and can be used for extensive reference. The link for it is https://nhibernate.info/

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 accommodated 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 an <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 favorite authors and a collection of favorite painters. The collection of favorite authors will be captured in a <set>, while a collection of favorite 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), and the Person.hbm mapping file (end of the figure). Now to the details of the figure and various mappings.

CodingManytoOnea

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 about 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 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 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.

Note. Collections are declared by interfaces in classes and initiated 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 ="AUTHOR NAME" 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 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 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 a collection table for <set> and <list>. The Set has no order information. So Set just needs to preserve information for the elements in the AUTHOR NAME column and to which person it belongs in the PersonId column (see Figure 2 - left side - authors set table). The list contains an additional column called painterslistindex (see Figure 2 - right side - painters list 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.

Tablewithpainterlistindex

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 code below in Figure 3 for two persons, person 1 and person 2, and compare it with the values in the 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 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 painter's 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>.

FRepositoryinNHibernate

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.

ProbleminNHibernate

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.

Collectiontable

Figure 5

It's interesting to note that the PAINTERSLIST collection table generated for <list> collections has a PRIMARY KEY 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 this <LIST-INDEX> element plays 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 the <SET> collection table (i.e. the AUTHORSSET table) has all columns to be set as primary keys to avoid repeats to conform to the 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 primary key 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 foreign key 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 E-Commerce Example

Now that an idea of collections has been established, the next step is to implement it in the e-commerce scenario.

"To each product description on 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 with 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 the 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 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, 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 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:

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 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 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 at the bottom defines the properties of the ProductReview wrapped inside an <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

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.

Productviewtable

Figure 7 - PRODUCT REVIEW 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:

Productviewtablegenrated

Figure 8 - Product review table generated for the client code shown in the 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


Similar Articles