Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Getting Started with NHibernate: Part III

    When mapping objects to the database in NHibernate you can either use XML files or decorate your classes with mapping attributes (NHibernate.Mapping.Attributes).  I've used both but I usually choose XML files simply as a matter of personal preference.  Either method works well and both provide many options for defining the relationship between class and table and configuring it's function.  I'll be using XML files in this tutorial but may work in some mapping attributes later in the process. 

    To get intellisense working with the mapping files you can copy the nhibernate-*.xsd files from the NHibernate source (NHibernate\src\NHibernate\) to C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas (or wherever you installed VS).  There are a few tools that can help by generating the mapping files based on an existing database schema, one that I've had the most success with is MyGeneration (try the template by lujan99).

    As I go through each mapping I'll point out how certain features work but for detailed information on the available options check the official documentation here.  Each of these mapping files will follow the naming convention classname.hbm.xml as this is what NHibernate expects to find when the SessionFactory is initialized (more on this in Part IV).

 

    BlogPost mapping: 

<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHBlog.Domain" namespace="NHBlog.Domain"> <class name="BlogPost" table="BlogPost"> <id name="ID" column="ID" type="int"> <generator class="native" /> </id> <property name="Title" column="Title" type="string" not-null="true"></property> <property name="Body" column="Body" type="string" not-null="true"></property> <property name="PublishedOn" column="PublishedOn" type="DateTime"></property> <many-to-one name="PublishedBy" column="PublishedByID" class="BlogUser" not-null="true" /> <bag name="Comments" cascade="delete" inverse="true" > <key column="BlogPostID" /> <one-to-many class="Comment" /> <loader query-ref="GetCommentsByBlogPostID"/> </bag> </class> <sql-query name="GetCommentsByBlogPostID"> <load-collection alias="Comments" role="BlogPost.Comments" /> exec GetCommentsByBlogPostID :blogPostID </sql-query> </hibernate-mapping>

    At the beginning of the file I'm setting the assembly and namespace values.  They aren't required, but if they are not set you will need to fully qualify any class names used including the assembly ( i.e., NHBlog.Domain.BlogPost, NHBlog.Domain).  The class element tells NHibernate what the name of the class is and which table that class maps to, in this case both are named BlogPost.  The id element defines the primary key of the table and which property in the class it maps to.  It's also includes the type (.net data type) and which class is generating these values.  NHibernate includes many different generator classes.  In our example we are using the identity function in SQL Server so the generator is set to native. 

    Each BlogPost will also have a collection of Comments seen here mapped using the bag element.  This mapping is a bit different than normal in that the<loader query-ref="GetCommentsByBlogPostID"/> attribute specifies which query to use to load the collection of comments, in this case a stored procedure.  At the bottom of the file a sql-query is defined to execute the stored procedure GetCommentsByBlogPostID and pass in the value blogPostID.  The load-collection element instructs NHibernate what type of entities to expect as the result of this query.

    Why am I using a stored procedure here?  I don't have to, I could load the collection like any other but doing it this way serves two purposes; to demonstrate the use of stored procedures (a new feature in NHibernate 1.2), and to assist in loading the comments in a threaded conversational manner (see the requirements in Part II).  GetCommentsByBlogPost uses a feature in Sql Server 2005 (and Sql Express 2005) called Common Table Expressions.  More information can be found here.

 

    BlogUser mapping: 

<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHBlog.Domain" namespace="NHBlog.Domain"> <class name="BlogUser" table="BlogUser"> <id name="ID" column="ID" type="int"> <generator class="native" /> </id> <property name="FirstName" column="FirstName" type="string" not-null="true"></property> <property name="LastName" column="LastName" type="string" not-null="true"></property> <property name="Alias" column="Alias" type="string"></property> <property name="EmailAddress" column="EmailAddress" type="string" not-null="true"></property> <property name="CreatedOn" column="CreatedOn" type="DateTime"></property> <bag name="BlogPosts"> <key column="PublishedByID" /> <one-to-many class="BlogPost" /> </bag> <bag name="Comments"> <key column="MadeByID" /> <one-to-many class="Comment" /> </bag> <bag name="BlogRoles" table="UserRole"> <key column="BlogUserID" /> <many-to-many class="BlogRole" column="BlogRoleID" /> </bag> </class> </hibernate-mapping>

    In BlogUser we have three collections mapped; BlogPosts, Comments, and BlogRoles.  The first two are one-to-many relationships meaning that a single BlogUser can have posted many BlogPosts and made many Comments.  The last is a many-to-many relationship meaning that a BlogUser can belong to many BlogRoles and a BlogRole can contain many BlogUsers.  The table UserRole defines this relationship in the database.  The mapping instructs NHibernate what type of class makes up this collection, which table to use to look up the collection members and which column holds the key for each.

 

    BlogRole mapping: 

<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHBlog.Domain" namespace="NHBlog.Domain"> <class name="BlogRole" table="BlogRole"> <id name="ID" column="ID" type="int"> <generator class="native" /> </id> <property name="Name" column="Name" type="string" not-null="true"></property> <bag name="BlogUsers" table="UserRole"> <key column="BlogRoleID" /> <many-to-many class="BlogUser" column="BlogUserID" /> </bag> </class> </hibernate-mapping>

     BlogRole has a collection of BlogUsers which represents the other side of the many-to-many relationship defined in BlogUser. 

 

   Comment mapping:

<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHBlog.Domain" namespace="NHBlog.Domain"> <class name="Comment" table="Comment"> <id name="ID" column="ID" type="int"> <generator class="native" /> </id> <property name="Subject" column="Subject" type="string" not-null="true"></property> <property name="Body" column="Body" type="string" not-null="true"></property> <property name="MadeOn" column="MadeOn" type="DateTime"></property> <many-to-one name="MadeBy" column="MadeByID" class="BlogUser" not-null="true" /> <many-to-one name="InReplyTo" column="InReplyToID" class="Comment" /> <many-to-one name="BlogPost" column="BlogPostID" class="BlogPost" not-null="true" /> <bag name="Replies"> <key column="InReplyToID" /> <one-to-many class="Comment" /> </bag> <property name="IsVisible" column="IsVisible" type="Boolean"></property> <property name="Depth" column="Depth" type="int" insert="false" update="false"></property> <loader query-ref="GetCommentByID"/> </class> <sql-query name="GetCommentByID"> <return class="Comment"></return> exec GetCommentByID :id </sql-query> </hibernate-mapping>

    A Comment has a reference to the BlogUser who made it (MadeBy), the Comment it is replying to (InReplyTo), if any, the BlogPost it is a comment on (BlogPost), and a collection of comments replying to it (Replies).  Also, notice the "Depth" property doesn't allow inserting or updating of this value.  That's because this value/column doesn't actually exist in the database, it's generated when loading a collection of comments.  If you load an individual Comment this value will always be zero.  This mapping is also using a stored procedure but in this case it will be used when loading a comment by id. 

 

    GetCommentsByBlogPostID stored procedure code: 

CREATE PROCEDURE [dbo].[GetCommentsByBlogPostID] ( @blogPostID int = 0 ) AS SET NOCOUNT ON; WITH CommentList ( ID, [Subject], Body, MadeOn, MadeByID, InReplyToID, BlogPostID, IsVisible, Depth, sortcol) AS ( SELECT ID, [Subject], Body, MadeOn, MadeByID, InReplyToID, BlogPostID, IsVisible, 0, CAST(ID AS VARBINARY(900)) FROM Comment WHERE (InReplyToID IS NULL) AND (BlogPostID = @blogPostID) UNION ALL SELECT c.ID, c.Subject, c.Body, c.MadeOn, c.MadeByID, c.InReplyToID, c.BlogPostID, c.IsVisible, cl.depth + 1, CAST(cl.sortcol + CAST(c.ID AS BINARY(4)) AS VARBINARY(900)) FROM Comment AS c INNER JOIN CommentList AS cl ON c.InReplyToID = cl.ID) SELECT ID, [Subject], Body, MadeOn, MadeByID, InReplyToID, BlogPostID, IsVisible, Depth FROM CommentList ORDER BY sortcol RETURN

 

    GetCommentByID stored procedure code: 

CREATE PROCEDURE dbo.GetCommentByID( @id int ) AS SET NOCOUNT ON SELECT ID, Subject, Body, MadeOn, MadeByID, InReplyToID, BlogPostID, IsVisible, 0 as Depth FROM Comment WHERE ID = @id RETURN

 

    In Part IV we will configure NHibernate and begin building the supporting framework needed to use what we have done so far.


Posted 05-23-2007 1:11 PM by anortham
Attachment: NHBlog.Domain.zip

[Advertisement]

Comments

Derik Whittaker wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 7:40 AM

Alan,

As a complete newbe to NHibernate I have to ask 2 things.  

1) What do I name my xml config files?  Does it even matter

2) Where do you suggest i put them in my solution?  Does it even matter

Derik

anortham wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 9:35 AM

Derik,

In the next article I'll cover this in a more detail, it will be all about NHibernate configuration, setting up Visual Studio, and getting a unit test project running.  

The quick answer is name the files classname.hbm.xml, put them in the assembly with your domain class files and set the build action to embedded resource.

Billy McCafferty wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 11:33 AM

Taking this a bit further, put your HBM file in the exact same folder of the object it describes.  So if you have Customer.cs in a folder called Organization in your project, then Customer.hbm.xml should be in that same folder, right next to Customer.cs.  This makes it very easy to switch between the HBMs and the domain objects they describe.

jhunter wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 12:08 PM

When I use bag to try and map to a collection I get an exception that says:

Unable to cast object of type 'NHibernate.Collection.PersistentBag' to type 'ClassLibrary1.EmailAddressCollection'

EmailAddressCollection is just a class that inherites List<EmailAddress> is there some other way I have to implement a collection in the object?

anortham wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 12:38 PM

jhunter,

In the BlogPost class above, the collection of comments in the mapping is implemented in BlogPost using .net 2 generics as:

public virtual IList<Comment> Comments {get;set;}

anortham wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 12:54 PM

I've attached a zip file containing the domain project I was using for the article.

PartialClass wrote re: Getting Started with NHibernate: Part III
on 05-24-2007 11:02 PM

Good work Alan.

At last, i have started to learn some NHibernate :)

I am looking forward to next "episodes".

I am also thinking of re-compiling these tutorials in more "user (developer) friendly" way.

I am sure there will not be any copy rights to these tutorials :)

anortham wrote re: Getting Started with NHibernate: Part III
on 05-25-2007 8:54 AM

PartialClass,

No copyrights :)

anortham wrote re: Getting Started with NHibernate: Part III
on 05-25-2007 11:41 AM

Billy, Derik,

I updated the article to clarify the naming convention for the mapping files.

Michel Kommers wrote re: Getting Started with NHibernate: Part III
on 07-10-2008 5:06 PM

Very nice sample application, but have an error when using membership profile with encrypted password

Chris May wrote NHibernate Tutorials
on 09-16-2008 3:17 PM
JavaRanch wrote re: Getting Started with NHibernate: Part III
on 02-10-2009 12:43 AM

could u plz send me the difference between <bag> element

and the <property > element in .hbm.xml and when to use these tags

Yazid wrote re: Getting Started with NHibernate: Part III
on 04-23-2009 7:35 AM

Hello,

I would like to take to this opportunity to ask a question about one-to-many. I ahve two classes:

GeneralInformation and famousPlacesInLondon

public class GeneralInformation

{

       private int id;

       public virtual int Id

       {

           get { return id; }

           set { id = value; }

       }

       private IList<FamousPlacesInLondon> famousPlacesOutLondon;

       public virtual IList<FamousPlacesInLondon> FamousPlacesOutLondon

       {

           get { return famousPlacesOutLondon; }

           set { famousPlacesOutLondon = value; }

       }

}

public class FamousPlacesInLondon

{

       private int id;

       public virtual int Id

       {

           get { return id; }

           set { id = value; }

       }

       private string link;

       public virtual string Link

       {

           get { return link; }

           set { link = value; }

       }

       private string title;

       public virtual string Title

       {

           get { return title; }

           set { title = value; }

       }

   }

}

My mapping are as follows:

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

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="Model">

 <class name="Model.GeneralInformation,Model" table="GeneralInformation">

   <id name="Id">

     <generator class="native" />

   </id>    

   <bag name="FamousPlacesInLondon" table="FamousPlacesInLondon" cascade="all">

     <key column="GeneralInformationId" />

     <one-to-many class="Model.FamousPlacesInLondon,Model"/>

   </bag>    

 </class>

</hibernate-mapping>

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

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model"  namespace="Model" >

 <class name="Model.FamousPlacesInLondon,Model" table="FamousPlacesInLondon" >

   <id name="Id">

     <generator class="identity" />

   </id>

   <property name="Link" column="Link" type="string" />

   <property name="Title" column="Title" type="string" />    

 </class>

</hibernate-mapping>

If I fill the GeneralInformation with data and do a save using NHibernate, everything gets inserted into the appropriate table, except the ForeignKey GeneralInformationId. What is wrong?

TIA

Yaz

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)