NHibernate is a flexible and adaptable framework; and when you understand the way
it works and how to utilize its strengths, it can bring significant benefits
to your projects. But being flexible and adaptable comes at a cost. Developers
who are new to this ORM framework often claim that NHibernate has a steep learning
curve, and they are correct –mastering NHibernate does require significant study.
Like any other tool, you need to get the facts and carefully consider where you'll
invest your limited time. One of my personal 2010 goals is to become completely
comfortable with both NHibernate and Fluent.NHibernate. I made this decision
after a great deal of thought and study. My conclusions were that NHibernate
is still years ahead of competing technologies and is likely to remain so for
the foreseeable future for .NET developers. I could have decided to invest my
time studying ASP.NET MVC, but I decided that NHibernate is much more important
for me.
Fortunately, there are some new approaches including Fluent NHibernate, which is
now in its 1.0 RTM iteration, that can make basic NHibernate projects very easy
to implement and which can give you a taste of how powerful NHibernate really
is, but with only a very modest investment of effort. That’s the purpose of this
article.
To begin with, this tutorial focuses on the “Top Down” development paradigm. As Microsoft
.NET Framework developers, most of what we have learned and most of the tools
we have been given (LINQ To SQL, Entity Framework, DataSets, and so on) have
been what we call “Data-centric” – meaning that you start with the database schema,
and build everything around that. This is a methodology that can quickly create
difficulties when one wants to approach business problem solutions in a true
OOP – object oriented manner. With NHibernate and Fluent NHibernate, you do not
need “Visual Designers” because you can start with your domain model and not
have to worry about what the database will look like. And, you don't need to
stab your fingers anymore with XML Mapping files!
So for the time being, let us suspend judgment on how to engineer an application
and focus on our business domain and its entities first. We’ll concern ourselves
with the persistence layer later. You’ll soon see that with a combination of
NHibernate and the Fluent NHibernate offering, we can actually leave the implementation
of the persistence layer entirely to NHibernate.
NHibernate will not only take our business domain and automatically create and execute
the SQL Server Schema to facilitate persistence of our domain and its objects,
it will do it automatically -- and present us with a ready – to – use programming
environment with which to work with our objects in a natural, intuitive, object-oriented
way.

This example is deliberately simple and exercises the default AutoMapping conventions
in order to make it easy to understand and use. Be assured that with Fluent NHibernate
you have the power to override and customize these conventions to adapt your
business model to your back-end persistence schema –-whether it already exists,
or if you want NHibernate to create it -- in many different ways -- through the
Fluent configuration and mapping interfaces. But in this case, understand that
we will start with our Domain model. Our database will actually be CREATED from
this model by NHibernate. If we improve or change our model, all we need to do
is enable the Schema Export Create method to make our database automatically
adapt to our changes as we develop our application.
We will create a simple Quotations application. It will have an Author class, a Quote
class, and a Subject class. These classes will be created as POCOs - “Plain Old
CLR Objects”. They will have no attributes and no external dependencies on any
other objects – just a pure business domain model that is easy to understand
and program against. If we decide to add a new Quotation to our database, with
a new Author and Subject, persisting these new objects to the database will be
both easy and natural, and NHibernate will take care of all the backend details
for us.
A Quotation class will have both an Author property (of type “Author”) and a Subject
property (of type “Subject”) -- not an AuthorId and SubjectId field as you would
think in the "data-centric" development paradigm. We’ll ask Fluent
NHibernate to reflect on these three classes, create an appropriate SQL Server
schema, execute it against an empty QUOTATIONS Database, and then provide us
with all the runtime business objects that NHibernate offers with which to handle
both our business logic and database persistence. No fuss, no muss. That’s the
power!
First, lets have a look at the Entities in our domain model:
namespace QuotesApp
{
[Serializable]
public partial class Author
{
public virtual System.String AuthorFirstName { get; set; }
public virtual System.Int32 Id { get; set; }
public virtual System.String AuthorInfo { get; set; }
public virtual System.String AuthorLastName { get; set; }
public virtual System.String AuthorName { get; set; }
}
}
namespace QuotesApp
{
[Serializable]
public partial class Quote
{
public virtual System.Boolean Approved { get; set; }
public virtual System.Int32 Id { get; set; }
public virtual System.String quotation { get; set; }
public virtual QuotesApp.Author Author { get; set; }
public virtual QuotesApp.Subject Subject { get; set; }
}
}
namespace QuotesApp
{
[Serializable]
public partial class Subject
{
public virtual System.String SubjectText { get; set; }
public virtual System.Int32 Id { get; set; }
}
}
Note that the Quote class has a type Author property and a type Subject property.
So for a new complete Quote, we need to first create an Author and a Subject
, persist them (or retrieve them, if they exist), and assign them to the new
Quote class instance. NHibernate will take care of resolving these into their
respective back-end database objects.
There are other ways to handle this - for example, our Author class could have an
IList<Quote> collection property of Quotes, and this would give us access
to every Quote that had this particular Author. NHibernate is perfectly capable
of handling this, complete with lazy loading if we so desire, but that is beyond
the scope of a "basics" article. The important thing to take away is
that NHibernate is sophisticated enough to give you the freedom and power to
build complex business domain models with multiple relationships between the
entities, and be able to persist them transparently and effectively.
The only other proviso here is to declare your properties as virtual – which is a
good programming practice anyway.
Now here is the Program.cs class which glues everything together:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Criterion;
using NHibernate.Tool.hbm2ddl;
namespace QuotesApp
{
class Program
{
private static NHibernate.ISession session;
static void Main(string[] args)
{
var factory = CreateSessionFactory();
var session = factory.OpenSession();
// first, let's make an author:
Author author = new Author();
author.AuthorInfo = ".Net developer extraordinaire";
author.AuthorFirstName = "Peter";
author.AuthorLastName = "Bromberg";
author.AuthorName = "Peter Bromberg";
ITransaction trans = session.BeginTransaction();
// now let's create a subject
Subject subject = new Subject();
subject.SubjectText = ".NET Witticisms";
// Now finally a Quote, complete with our author and subject
Quote quotation = new Quote();
quotation.Author = author;
quotation.Subject = subject;
quotation.quotation = "Nullable Object must have a value";
// need to save our Author and subject first - this provides their PK Id's
session.Save(author);
session.Save(subject);
session.SaveOrUpdate(quotation);
// make a new quote, using the same author and subject
Quote quote2 = new Quote();
quote2.Approved = true;
quote2.quotation = "Objects may appear closer than they really are.";
quote2.Author = author;
quote2.Subject = subject;
session.SaveOrUpdate(quote2);
trans.Commit();
// Now let's get some results:
ITransaction transaction = session.BeginTransaction();
var subjects = session.CreateCriteria(typeof(QuotesApp.Subject))
.SetMaxResults(10)
.List<QuotesApp.Subject>();
transaction.Commit();
Console.WriteLine("=========SUBJECT QUERY RESULTS=====");
foreach (var subj in subjects)
{
Console.WriteLine("Subject: " +subj.SubjectText );
}
// Now let's get quotes using a "LIKE" expression:
ITransaction transaction2 = session.BeginTransaction();
var quotes = session.CreateCriteria(typeof(QuotesApp.Quote)).Add( Expression.Like("quotation", "%object%"))
.SetMaxResults(10)
.List<QuotesApp.Quote>();
transaction2.Commit();
Console.WriteLine("=========QUOTE QUERY RESULTS=======");
foreach (var quoty in quotes)
{
Console.WriteLine("Subject: " + quoty.Subject.SubjectText + ": Quotes: " + quoty.quotation);
Console.WriteLine("Author: " + quoty.Author.AuthorName + " " + quoty.Author.AuthorInfo);
Console.WriteLine("==================================================================");
}
Console.WriteLine("Done. Any key to quit.");
Console.ReadLine();
}
private static ISessionFactory CreateSessionFactory()
{
IPersistenceConfigurer persistenceConfigurer;
persistenceConfigurer =
MsSqlConfiguration.MsSql2008.ConnectionString(c => c.Is(@"server=(local);database=Quotations;Integrated Security=SSPI"));
AutoPersistenceModel model = new AutoPersistenceModel();
Assembly asm = Assembly.GetExecutingAssembly();
return Fluently.Configure()
.Database(persistenceConfigurer)
.Mappings(m => m.AutoMappings.Add(
model.AddEntityAssembly(asm)
//This is redundant as everything is in one assembly here, included for clarity
.Where(t => t.Namespace.StartsWith("QuotesApp"))
))
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private static void BuildSchema(Configuration config)
{
// HOW TO:
// First, create a new SQL Server database, "QUOTATIONS".
// change first parameter of .Create method to true to see schema, second to true
to execute the SQL and // create the schema
// in your database - automapped from your POCO domain.
// after first run and the database is created, set the second parameter to false
// so it doesn't try to recreate the database schema!
new SchemaExport(config)
.Create(true,true);
}
}
}
Most of this is self – documenting so I won’t belabor you with details, but what
happens here is as follows:
1) We create a Session Factory which builds the SQL Server persistence configurer,
and adds the Fluent.NHibernate Automappings from the specified assembly.
2) The persistence configurer exposes the Schema via the BuildSchema method, which,
if the parameters are both “true”, not only creates the SQL Schema but also executes
it against our empty database.
3) We open an NHibernate Session via the factory.OpenSession() method.
4) We create a new Author and Subject and Save them so that they are persisted and
have an Id property,
5) We create a new Quote, assign the Author and Subject to the new Quote, and Save
it via the SaveOrUpdate method.
6) As a proof of concept, we query the database to return some data populated into
our POCO objects.
Look carefully at your generated SQL Server schema - you'll notice that the Automapping
defaults expect the Primary key of each table to be "Id" and that the
Quote table has Author_id and Subject _Id columns. These are the defaults, but
using the Fluent NHibernate overrides, you can easily change them. Also note
that there is NO XML anywhere - Fluent NHibernate can create our persistence
mappings directly from the types in the model themselves.
Now let's say, for example, that I am not happy with the default 255 character length
of a string column and I want to override this in the automapping. What I can
do is create a custom StringColumnLengthConvention class that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.AcceptanceCriteria;
using FluentNHibernate.Conventions.Inspections;
using FluentNHibernate.Conventions.Instances;
namespace Conventions
{
public class StringColumnLengthConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(string))
.Expect(x => x.Length == 0);
}
public void Apply(IPropertyInstance instance)
{
instance.Length(4000);
}
}
}
I would put this under a different namespace so the automapper doesn't attempt to
map it, then add this into my Automappings like this:
model.AddEntityAssembly(asm).Conventions.Add(new StringColumnLengthConvention())
.Where(t => t.Namespace.StartsWith("QuotesApp")))
And voilà! Now I have 4000 character NVARCHAR fields. Of course there are many built-in overrides
and as can be seen above, it is easy to create our own.
You can download the complete Visual Studio 2008 solution here. This includes (in the \debug\bin folder) all the referenced assemblies required
for operation. The only thing you may need to do is change the connection string
to match your environment.
Here are some useful resources to help you get started with NHibernate and Fluent
NHibernate:
Fluent NHibernate
NHibernate
Ayende Rahien (Oren Eini)
NHibernate in Action (Manning) - you really want this!
Fluent NHibernate Wiki (highly recommended)
NHibernate LINQ
And finally, thanks to James Gregory, without whose tireless efforts we'd still be writing HBM Xml mapping files!