Thursday, 11 December 2008

Fluent NHibernate introduction and quick start guide

I'm sure that lots of people is familiar with NHibernate and had a chance to work with it. I had a chance and I have to admit that it was a good time. But there are certain things which I hate about NHibernate like ... XML files, configuration, mappings ... to get it running tons of XML has to be produced.

Right, it's true that once this is done you don't have to go back there very often ... unless you want to do some refactoring. If you would like to rename some classes, move them to a different namespace or maybe rename some of the properties then you are in a big trouble ... Visual Studio and ReSharper will help you with refactoring of your code but you have to maintain XML files on your own.

Introduction to Fluent NHibernate

Apparently someone finally got pissed off and decided to change it ... goal was to get rid of all that maddening XML files. In other words, idea was to replace this:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
   3:      namespace="Eg" assembly="Eg">
   5:      <class name="Customer" table="Customers">
   6:          <id name="ID">
   7:              <generator class="identity" />
   8:          </id>
  10:          <property name="Name" />
  11:          <property name="Credit" />
  13:          <bag name="Products" table="Products">
  14:              <key column="CustomerID"/>
  15:              <one-to-many class="Eg.Product, Eg"/>
  16:          </bag>
  18:          <component name="Address" class="Eg.Address, Eg">
  19:              <property name="AddressLine1" />
  20:              <property name="AddressLine2" />
  21:              <property name="CityName" />
  22:              <property name="CountryName" />
  23:          </component>
  24:      </class>
  25:  </hibernate-mapping>

with that:

   1:  public CustomerMap : ClassMap<Customer>
   2:  {
   3:      public CustomerMap()
   4:      {
   5:          Id(x => x.ID);
   6:          Map(x => x.Name);
   7:          Map(x => x.Credit);
   8:          HasMany<Product>(x => x.Products)
   9:              .AsBag();
  10:          Component<Address>(x => x.Address, m =>
  11:                 {
  12:                     m.Map(x => x.AddressLine1);
  13:                     m.Map(x => x.AddressLine2);
  14:                     m.Map(x => x.CityName);
  15:                     m.Map(x => x.CountryName);
  16:                 });
  17:      }
  18:  }

(example from James Gregory's blog)

In this form you can refactor your code as much as you like, it won't break your mappings. Your application is much more readable and maintainable. Thanks to Fluent NHibernate (FN) you can forget about XML files.

Quick Start Guide

We are still waiting for the first official release of Fluent NHibernate (FN) so at the moment the only option to get it is to use SVN client, check out the project and build dll on your own.

Having FluentNHibernate.dll you can simply add it to your project and start using it.

To make it easier I have prepared very basic web application which has FN set up, you can download it here. It uses popular AdventureWorks database.

Steps to get FN working:
  1. First of all you need to configure NHibernate which includes general configuration (connection string, dialect, driver etc) and mappings. In terms of general configuration, you have two options:
    • configure NHibernate without any XML files

         1:  // of course, in real life, connection string should be externalized
         2:  const string connectionString =
         3:  @"Data Source=MAREK-PC\SQLEXPRESS;Database=AdventureWorks;User Id=marek;Password=marek;Network Library=DBMSSOCN;Max Pool Size=400;";
         5:  // configure nhibernate
         6:  Configuration config = MsSqlConfiguration.MsSql2005
         7:    .ConnectionString.Is(connectionString)
         8:    .UseReflectionOptimizer()
         9:    .ShowSql()
        10:    .ConfigureProperties(new Configuration());
        12:  // load mappings from this assembly
        13:  config.AddMappingsFromAssembly(Assembly.GetExecutingAssembly());
        15:  // build factory
        16:  ISessionFactory sessionfactory = config.BuildSessionFactory();

    • or you can decide to keep nhibernate.cfg.xml:

         1:  // read hibernate.cfg.xml
         2:  Configuration config = new Configuration().Configure();
         4:  // load mappings from this assembly
         5:  config.AddMappingsFromAssembly(Assembly.GetExecutingAssembly());
         7:  // build factory
         8:  ISessionFactory sessionfactory = config.BuildSessionFactory();

  2. Mappings, in this case my goal was to keep this example as simple as possible and elaborate it in future therefore for now only one table is mapped:
    • this is a POCO object representing a product:

         1:  public class Product
         2:  {
         3:      public virtual int Id { get; set; }
         4:      public virtual string Name { get; set; }
         5:      public virtual string ProductNumber { get; set; }
         6:      public virtual bool MakeFlag { get; set; }
         7:      public virtual bool FinishedGoodsFlag { get; set; }
         8:      public virtual string Color { get; set; }
         9:      public virtual int SafetyStockLevel { get; set; }
        10:      public virtual int ReorderPoint { get; set; }
        11:      public virtual int StandardCost { get; set; }
        12:      public virtual int ListPrice { get; set; }
        13:      public virtual String Size { get; set; }
        14:      public virtual int DaysToManufacture { get; set; }
        15:      public virtual String ProductLine { get; set; }
        16:      public virtual String Class { get; set; }
        17:      public virtual String Style { get; set; }
        18:      public virtual DateTime SellStartDate { get; set; }
        19:      public virtual DateTime SellEndDate { get; set; }
        20:      public virtual DateTime ModifiedDate { get; set; }
        21:  }
    • and mapping:

         1:  public ProductMap()
         2:  {
         3:      // table name is different then class name
         4:      WithTable("Production.Product");
         6:      Id(x => x.Id, "ProductID");
         8:      Map(x => x.SellEndDate);
         9:      Map(x => x.ReorderPoint);
        11:      Map(x => x.Name).WithLengthOf(50).Not.Nullable();
        12:      Map(x => x.ProductNumber).WithLengthOf(25).Not.Nullable();
        13:      Map(x => x.MakeFlag).Not.Nullable();
        14:      Map(x => x.FinishedGoodsFlag).Not.Nullable();
        15:      Map(x => x.Color).WithLengthOf(15);
        16:      Map(x => x.SafetyStockLevel).Not.Nullable();
        17:      Map(x => x.StandardCost).CanNotBeNull().CustomSqlTypeIs("money");
        18:      Map(x => x.ListPrice).CanNotBeNull().CustomSqlTypeIs("money");
        19:      Map(x => x.Size).WithLengthOf(5).Not.Nullable();
        20:      Map(x => x.DaysToManufacture).Not.Nullable();
        21:      Map(x => x.ProductLine).WithLengthOf(2);
        22:      Map(x => x.Class).WithLengthOf(2);
        23:      Map(x => x.Style).WithLengthOf(2);
        24:      Map(x => x.SellStartDate).Not.Nullable();
        25:      Map(x => x.ModifiedDate).Not.Nullable();
        26:  }

      In this case table name is different then class name that is why I had to specify it but in general FN assumes that class name equals table name. The same approach is applied for properties and columns. Again you can specify that a column has different name then a corresponding property if necessary. Also it's not required to specify generator type for primary key column. FN checks property type and uses default one.

  3. Testability - significant advantage is that you don't have to remember about keeping XML files up-to-date with your code and database schema, but also FN offers number of ways to facilitate creation of unit tests and integration test. I will cover that in a next post. (edit: post regarding integration tests)

I encourage you to keep an eye on this project. In a meantime you can start playing with the Fluent NHibernate by checking the sample web application.

The AdventureWorks database you can download from Codeplex.

(EDIT: Examples in this post have been updated on 8.02.2009 to reflect changes in Fluent NHibernate API)

Related articles:


Aleks said...

It's nice you liked it :) If you liked, than also Sharp Architecture might interest you. It is a project that glues Fluent NHibernate with Asp.Net MVC, IOC container Castle Windsor and Nhibernate.Validator

Marek Blotny said...

Hi Aleks, I have seen Sharp Architecture before ... probably another interesting project, but as you know, we don't use ASP.NET MVC, at least not yet, therefore I decided to check only Fluent NHibernate for now.

On the other hand I would like to take a look on ASP.NET MVC soon so maybe I can check Sharp Architecture in the same time ;)

Anonymous said...

nice post :) i've been watching FN for quite some time - maybe it's finally time to try it.

James Radford said...

same here, experimenting with it and trying to get it working on my machine and struggling at the moment! Hopefully I'll get it working with this article! Thanks