More Fun with Fluent NHibernate Automapping

Simple example of using Fluent NHibernate Automapping

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!

By Peter Bromberg   Popularity  (6257 Views)