Friday, January 09, 2009

Integrating Fluent NHibernate and the Windsor NHibernate Facility

Our current project uses NHibernate as its ORM. Until recently we've been using the default hbm mapping files, but we've been suffering for some time from Fluent NHibernate envy. So now we've decided to take the plunge and migrate our project to code based mapping. My esteemed colleague Keith Bloom has been doing most of the work for this, and this post is simply me taking a free ride on all his hard work. Thanks Keith :)

I'm not going to describe Fluent NHibernate here. You can check out the project page if you want to see what it's all about. Suffice to say that it's a really nice API to describe object-relational mapping. Once you've defined your mappings, it's a simple case of applying them to the NHibernate configuration with a handy extension method:

var cfg = new Configuration()
	.Configure()
	.LoadMappingAssembly(Assembly.LoadFrom("Name of assembly that contains your maps."));

However we're using Windsor's very convenient NHibernate integration facility in our project. It does all the configuration and session management for us, so we don't have to worry about it. The problem is, that because it handles it for us, there's not an immediately obvious place to access the configuration to apply the Fluent NHibernate mappings.

It turns out that the simplest way of doing this is to write your own implementation of IConfigurationBuilder. This is the NHibernate facility class that actually creates the NHibernate configuration:

public class FluentNHibernateConfigurationBuilder : IConfigurationBuilder
{
	public Configuration GetConfiguration(IConfiguration facilityConfiguration)
	{
	    var defaultConfigurationBuilder = new DefaultConfigurationBuilder();
	    var configuration = defaultConfigurationBuilder.GetConfiguration(facilityConfiguration);
             configuration.AddMappingsFromAssembly(Assembly.LoadFrom("Name of assembly that contains your maps."));
	    return configuration;
	}
}

Note that we're simply deferring the creation of the NHibernate configuration to the DefaultConfigurationBuilder and then adding the call to to AddMappingsFromAssembly before passing it on. All we have to do now is configure the facility to use our new configuration builder:

<facility id="nhibernate"
          isWeb="false"
          type="Castle.Facilities.NHibernateIntegration.NHibernateFacility, Castle.Facilities.NHibernateIntegration"
          configurationBuilder="Keith.WindsorNHibernate.Services.FluentNHibernateConfigurationBuilder, Keith.WindsorNHibernate">
  <factory id="nhibernate.factory">
    <settings>
      <item key="show_sql">true</item>
      <item key="connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
      <item key="connection.driver_class">NHibernate.Driver.SqlClientDriver</item>
      <item key="dialect">NHibernate.Dialect.MsSql2005Dialect</item>
      <item key="connection.connection_string">Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True</item>
    </settings>
  </factory>
</facility>

Keith has kindly allowed me to post his test solution which you can download below. You'll need sqlexpress with a copy of Northwind to run it.

http://static.mikehadlow.com/Keith.WindsorNHibernate.zip

14 comments:

Alex said...

About sample Keith.WindsorNHibernate.zip.

I want to use my extended Repository, for example:

public class MyCustomerRepository :NHibernateRepository[of Customer] {
IList[of Customer] GetPerson(string name) {
// extended code
}
}

where [of Customer] is generic params, i cant post tags.

[b]How can I register my extended repository in Windsor Castle ?[/b]
How I can get this component by windsor?

Mike Hadlow said...

Hi Alex, just configure the container so that when you ask for IRepository'1[[Your.Namespace.Customer]] the container gives you Your.Namespace.MyCustomerRepository. It should look something like this:

<component
id="generic.repository"
service="Keith.WindsorNHibernate.Repositories.IRepository`1[[Your.Namespace.Customer]], Keith.WindsorNHibernate"
type="Your.Namespace.MyCustomerRepository, Your.Assembly"
lifestyle="transient" />

Alex said...

Thanx, yes it works fine
But I lost extended methods.

For example:

public class CustomerRepository : NHibernateRepository'1[Customer] {

IList'1[Customer] GetCustomerByName(string name) {
IList list = new IList'1[Customer]();
// some code to find needed customers
return list;
}
--------------------
This my windsor configuration:

container = new WindsorContainer();
container.Register(
Component.For(typeof(IRepository'1)).ImplementedBy(typeof(NHibernateRepository'1)).LifeStyle.Transient.Named("generic.repository"),
Component.For(typeof(IRepository'1[Customer])).ImplementedBy'1[CustomerRepository]().LifeStyle.Transient.Named("customer.repository")
);

Well, when I get this component by windsor:

var customerRepository = container.GetComponent[[IRepository'1[Customer]]();

I dont see the method GetCustomerByName :( and cant execute this for find needed customers.
What elegant solution for this?

Mike Hadlow said...

Hi Alex,

If you don't need the polymorphism, just register the CustomerRepository as itself:

Component.For<CustomerRepository>().LifeStyle.Transient.Named("customer.repository")

var customerRepository = container.GetComponent<CustomerRepository>();

Alex said...

I think there is more elegant solution :-)
Your solution works and I will do also.
Thanx very much! :)

Pete said...

Your post is awsome. I went from knowing zero about NHibernate, Windsor, or fluent to a working example. I want to add AutoMapping but am stuck. I read James Gregory Fluent NHibernate: Configuring your application. He has an example of config for Mapping + AutoMapping. I think if I adjust GetConfiguration routine your example app will do AutoMapping. Not sure how to add m.AutoMapping.Add to the GetConfiguration routine.

Here is James's config for both mapping and AutoMapping.

var sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m =>
{
m.FluentMappings
.AddFromAssemblyOf[YourEntity]();

m.AutoMappings.Add(
// your automapping setup here
AutoPersistenceModel.MapEntitiesFromAssemblyOf[YourEntity]()
.Where(type => type.Namspace.EndsWith("Entities")));
})
.BuildSessionFactory();

Mike Hadlow said...

Hi Pete,

I haven't played with the auto mapping Fluent interface yet (yes, I know, pull your socks up:).

After a brief read about it, I'm starting to wonder if the Fluent NHibernate interface and the Windsor facility really make much sense together, since they're doing much the same thing (configuration).

What's really needed is a revised NHibernate facility that leverages the fluent configuration.

Anonymous said...

If I use Nhibernate Facility why I can build database schema ?

Björn said...

Thanks for the info! I had some issues trying to find the IConfigurationBuilder interface though. Until I updated my Castle files from trunk, seems like it was internal before? Might be useful information for others if they try to do the same :)

Björn said...

I'm using automapping from fluent nhibernate with castle nhibernate facility. The "trick" is really just to use the extension method configuration.AddAutoMappings() instead of using AddMappingsFromAssembly().

I'm using it like this:
configuration
.AddAutoMappings(
AutoPersistenceModel
.MapEntitiesFromAssemblyOf<Headline&rt;()
.Where(t => t.Namespace == "Incendi.Models")
.WithConvention(
convention =>
convention.IsComponentType =
t => t.Namespace == "Incendi.Models.Components"));

Mike Hadlow said...

Hi Bjorn,

Thanks for that bit of advice, very handy:)

estuardogt said...

Great Job... thanks for this post!!!
Best regards since Guatemala

Bas said...

great just what i was looking for

Anonymous said...

It's not clear from your example but I imagine you can replace some (or all) of the other config you have in "nhibernate.factory" session eg Dialect/Driver etc