Creating a Web Application Using MVC, Unity and NHibernate – Part 2 nHibernate

Saturday, January 24, 2009

Introduction

In the last post I set up NHibernate and created a simple test to retrieve one record from the database.  Now I would like to join the News table and Author table together and then create a test to see if I can successfully retrieve a record that contains both data from both tables joined together.

If you recall my schema looks like this:

 

 

 

 

So the first thing I would like to do is change create the mapping and DTO class for the Author table, but before I can do that, I need to add a reference of the Iesi.Collections.dll external assembly to my projects so I can create the ISet<> collection object.  Since one author can have many news items, my author class will have a collection of news items.  Since each of these news items must be unique, the ISet collection object will not allow you to add duplicate news items.

The Author Mapping File and Class

The Author mapping file will be set up just like the News mapping file:

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="News.Core" namespace="News.Core.Dto">

    3   <class name="News.Core.Dto.AuthorDto, News.Core" table="Author">

    4     <id column="AuthorId" name="AuthorId" type="long" unsaved-value="0">

    5       <generator class="native"></generator>

    6     </id>

    7     <property column="UserName" name="UserName" type="string" not-null="true"/>

    8     <property column="FirstName" name="FirstName" type="string" not-null="true"/>

    9     <property column="LastName" name="LastName" type="string" not-null="true"/>

   10     <property column="Password" name="Password" type="string" not-null="true"/>

   11     <property column="EmailAddress" name="EmailAddress" type="string" not-null="true"/>

   12     <property column="DateAdded" name ="DateAdded" type="DateTime" not-null="true" />

   13     <property column="DateUpdated" name ="DateUpdated" type="DateTime" not-null="true" />

   14     <property column="IsActive" name="IsActive" />

   15 

   16     <set name="NewsItems" table="News" generic="true" inverse="true">

   17       <key column="AuthorId" />

   18       <one-to-many class="News.Core.Dto.NewsItemDto, News.Core"/>

   19     </set>

   20   </class>

   21 </hibernate-mapping>

 

The first thing I need to is add the hibernate-mapping tag with xmlns namespace set so I can get the intellisense.  Next I add the Author class, specifying its namespace and assembly location, and just like the NewsItem class I start mapping the fields to the class properties.

As seen from the schema shown above, the Author table has a foreign key relationship in the News table, so I need to tell NHibernate about that relationship.  So to map the relationship, I have added a “set” tag.  The set tag tells NHibernate that the Author class has a collection of NewsItems and each of these news items are unique.  Now, if I wanted the Author class to have a collection of NewsItems that were not unique then I would use the bag tag, but in my case the set tag is what I want.

Here’s what’s going on:

·          The set attribute name is the child class that will be contained in the Author class.

·          The table attribute specifies the child table in the database that has the foreign key relationship.  In my case it is the News table.

·          Generic tells NHibernate that I want to use the ISet<T> collection  (Note:  This is a class that NHibernate provides is like the IList<T> class, but this class will not let you add a duplicate item to its collection.  I need to add a reference to the Iesi.Collections.dll assembly to use it.

·          Inverse tells NHibernate that I want the child class to control the relationship and not the parent.

·          The key column tells NHibernate what the foreign key field is in the child table.

·          And finally the one-to-many tag tells NHibernate what namespace and assembly the child class is in.

Woops!  Don't forget to make the mapping xml file an embedded resource.

Here is the class file.

    6     public class AuthorDto

    7     {

    8 

    9         public virtual long AuthorId { get; set; }

   10 

   11         public virtual string UserName { get; set; }

   12 

   13         public virtual string FirstName { get; set; }

   14 

   15         public virtual string LastName { get; set; }

   16 

   17         public virtual string Password { get; set; }

   18 

   19         public virtual string EmailAddress { get; set; }

   20 

   21         public virtual DateTime DateAdded { get; set;  }

   22 

   23         public virtual DateTime DateUpdated { get; set; }

   24 

   25         public virtual bool IsActive { get; set; }

   26 

   27         public virtual ISet<NewsItemDto> NewsItems { get; set; }

   28 

   29     }

 

Notice the ISet class?  Cool!

The News Mapping File and Class

The News class will slightly change.  Instead of holding the AuthorId field, the News class will be holding its parent Author class, so I will need to change the mapping file to reflect this also.

So here is the mapping file:

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Audubon.Core" namespace="Audubon.Core.Dto">

    3   <class name="Audubon.Core.Dto.NewsItemDto, Audubon.Core" table="News">

    4     <id column="NewsId" name="NewsId" type="long" unsaved-value="0">

    5       <generator class="native"></generator>

    6     </id>

    7     <many-to-one name="Author" column="AuthorId" not-null="true" class="Audubon.Core.Dto.AuthorDto, Audubon.Core" />

    8     <property column="DateAdded" name="DateAdded" type="DateTime" not-null="true"/>

    9     <property column="DateUpdated" name="DateUpdated" type="DateTime" not-null="true"/>

   10     <property column="DatePublished" name ="DatePublished" type="DateTime" not-null="true" />

   11     <property column="Title" name="Title" type="string" not-null="true"/>

   12     <property column="ShortDescription" name="ShortDescription" type="string" not-null="false"/>

   13     <property column="Body" name="Body" type="string" not-null="true"/>

   14     <property column="IsFrontPage" name="IsFrontPage"/>

   15     <property column="IsPublished" name="IsPublished"/>

   16 

   17   </class>

   18 </hibernate-mapping>

 

Notice I have changed the AuthorId property tag to a many-to-one tag which specified the Author class.

Here is the NewsItem class with the modification for Author property:

    5     public class NewsItemDto

    6     {

    7         public virtual long NewsId { get; set; }

    8 

    9         public virtual AuthorDto Author { get; set; }

   10 

   11         public virtual DateTime DateAdded { get; set; }

   12 

   13         public virtual DateTime DatePublished { get; set; }

   14 

   15         public virtual DateTime DateUpdated { get; set; }

   16 

   17         public virtual string Title { get; set; }

   18 

   19         public virtual string ShortDescription { get; set; }

   20 

   21         public virtual string Body { get; set; }

   22 

   23         public virtual bool IsFrontPage { get; set; }

   24 

   25         public virtual bool IsPublished { get; set; }

   26     }

 

The Test

Now before I get into the test, at this point I should probably acquaint myself with the idea of lazy loading verses eager loading.  If you look at the two class above, the Author class has a collection of NewsItems classes and each of the NewsItem classes has a Author class which has a collection NewsItem classes, etc, etc, etc…

To get around this circular mapping, NHibernate uses the Proxy Pattern so that NewsItem class is not actually loaded until asked for in the code.  I can either get all the collection classes up front (eager loading) or I can get the class when I need them (lazy loading).  David Hayden wrote a nice explanation about the ramifications of either one using LightSpeed that you can read here.

I state that now, because I want to test that if I search for an author using a user id that I will get a collection of news items back for that author.  So since I am using lazy loading, I am not actually going to make to database calls for the news items until I inspect the collection of news items in the authors object I get back.

So here is the test:

  104         [TestMethod]

  105         public void NHibernateCanGetNewsItemsBySpecifiedUserId()

  106         {

  107             const string userId = "testuser";

  108             var target = new NHibernateDataProvider(session);

  109             var authors = target.GetAuthorsBy(userId);

  110 

  111             Assert.IsTrue(authors.Count > 0, "Authors count is not greater than 0");

  112             Assert.IsTrue(authors[0].NewsItems.Count >0, "Author's news items count is not greater than 0");

  113             Assert.IsFalse(authors.Count > 1, "Retrieved too many authors");

 

I am testing that my function GetAuthorsBy will return a collection of NewsItems.

Here is the function:

   42         public IList<AuthorDto> GetAuthorsBy(string userId)

   43         {

   44             return _session.CreateCriteria(typeof(AuthorDto))

   45                 .Add(Restrictions.Eq("UserName", userId))

   46                 .SetResultTransformer(new DistinctRootEntityResultTransformer())

   47                 .List<AuthorDto>();

   48 

   49         }

This function is using the NHibernate API to query the Author table by passing the userid.

Here is what is happening here:

·          CreateCritera starts off by asking what class I want to return.  I am telling to return a collection of Authors.

·          To add the Where Clause, I use the Add extension function and pass in the Restrictions.Eq function passing the property name and matching value.

·          By default, it is possible for me to get author “A” and then get author “B” and then again get author “A” in the collection, each with their own set of news items.  To avoid this I want to combine all duplicates so they only show up once.  Essentially this would be the equivalent to the DISTINCT statement in SQL.  I can do this using the SetResultTransformer extension function passing in the DistinctRootEntityResultTransformer.  This function will take the collection and merge the duplicates together (this done in memory not in the database).  Ayende gives some other examples and explanations here.

·          Finally the List extension tells NHibernate that I want a strongly types IList<AuthorDTO> class.

So when the function is called the following query is made to the database.

NHibernate: SELECT this_.AuthorId as AuthorId1_0_, this_.UserName as UserName1_0_, this_.FirstName as FirstName1_0_, this_.LastName as LastName1_0_, this_.Password as Password1_0_, this_.EmailAddress as EmailAdd6_1_0_, this_.DateAdded as DateAdded1_0_, this_.DateUpdated as DateUpda8_1_0_, this_.IsActive as IsActive1_0_ FROM Author this_ WHERE this_.UserName = @p0; @p0 = 'testuser'

Then when the test does an assert on the NewsItem count the following query is made.

NHibernate: SELECT newsitems0_.AuthorId as AuthorId1_, newsitems0_.NewsId as NewsId1_, newsitems0_.NewsId as NewsId0_0_, newsitems0_.AuthorId as AuthorId0_0_, newsitems0_.DateAdded as DateAdded0_0_, newsitems0_.DateUpdated as DateUpda4_0_0_, newsitems0_.DatePublished as DatePubl5_0_0_, newsitems0_.Title as Title0_0_, newsitems0_.ShortDescription as ShortDes7_0_0_, newsitems0_.Body as Body0_0_, newsitems0_.IsFrontPage as IsFrontP9_0_0_, newsitems0_.IsPublished as IsPubli10_0_0_ FROM News newsitems0_ WHERE newsitems0_.AuthorId=@p0; @p0 = '1'

In the next post I will look at testing the Service layer using Rhino Mocks and possibly throw in my first example of how I am going to use Unity.

 

 

comments powered by Disqus