Thursday, March 19, 2009

Fluent NHibernate Configured Via Dependency Injection

I remember having to reference this “best practice” post when setting up a new project utilizing NHibernate.  What a nightmare.  Thankfully, there’s been some really positive activity on the Fluent NHibernate project, and configuration has become much more straightforward.  In this post I am going to share my experience configuring NHibernate using:

Fluent NHibernate
Ninject
MySQL (Configuring any common database platform is trivial once you’ve seen it done)

To get my head around the basic configuration, I read the tutorial at the Fluent NHibernate site: http://fluentnhibernate.org/

After reading the tutorial, I had a good idea of the external dependencies I’d need to “inject” into my app.

Configuring the Repository

The primary point of entry for any data transferred to and from my database is via my repository.  NHibernate’s ISession is what I will interface with to make queries, so I’m going to need that object created and injected into my repository.

Example of a simple repository:

    public class Repository : IRepository

    {

        private ISession _session;

 

        public Repository(ISession session)

        {

            _session = session;

        }

 

        public void Save<T>(T entity) where T : DomainEntity

        {           

        }

 

        public T Load<T>(int id) where T : DomainEntity

        {

        }

 

        public IQueryable<T> Query<T>() where T : DomainEntity

        {

        }

 

        public IQueryable<T> Query<T>(Expression<Func<T, bool>> where) where T : DomainEntity

        {

        }

    }

The constructor of my class takes an instance of ISession.  This is where the dependency will be injected into my Repository class.

Widdling down the “Best Practice” article

The most important part of an NHibernate configuration is your management of the SessionFactory and Session objects.  The SessionFactory object is expensive to create, and should be made a Singleton.  The ISession object should be retained for the lifecycle of the current request if you’re working on a web application.  For a non-web project the lifecycle of the ISession should be kept around one per thread.

Many (if not all) of the major dependency injection frameworks offer this type of “lifecycle management” out of the box.  Ninject is no exception; here is how I accomplished it:

    internal class IoCConfiguration : StandardModule

    {

        public override void Load()

        {

            Bind<IRepository>().To<Repository>();

            Bind<INHibernateSessionFactoryBuilder>().To<NHibernateSessionFactoryBuilder>().Using<SingletonBehavior>();

 

            Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<INHibernateSessionFactoryBuilder>()

                .GetSessionFactory()

                .OpenSession())

                .Using<OnePerRequestBehavior>();

        }

    }

Due to the expressive nature of the API, this code is really self-explanatory.  Just for fun, compare this code with the mess from the NHibernate best practices article I mentioned eariler (http://www.codeproject.com/KB/architecture/NHibernateBestPractices.aspx). 

For completeness, here is the code to configure NHibnerate via Fluent Nhibernate for a MySql database:

    public class NHibernateSessionFactoryBuilder : INHibernateSessionFactoryBuilder

    {

        private ISessionFactory _sessionFactory;

 

        public ISessionFactory GetSessionFactory()

        {

            if(_sessionFactory == null)

            {

                _sessionFactory = CreateSessionFactory();

            }

 

            return _sessionFactory;

        }

 

        private ISessionFactory CreateSessionFactory()

        {

            return Fluently.Configure()

                .Database(MySQLConfiguration.Standard.ConnectionString(c => c.FromAppSetting("DBConnString")))

                .Mappings(m => m.AutoMappings.Add(AutoPersistenceModel.MapEntitiesFromAssemblyOf<DomainClass>()

                                                .Where(w => w.BaseType == typeof(DomainEntity))

                                                .ConventionDiscovery.Add<PrimaryKeyConvention>()

                                                .WithSetup(convention =>

                                                {

                                                    convention.FindIdentity = t => t.Name == "Id";

                                                    convention.IsBaseType = type => type == typeof (DomainEntity);

                                                }

                                                )))

                .BuildSessionFactory();

        }

    }

This is all best explained by the Fluent Nhibernate wiki (which is excellently written).

All this is a first swag, and I’d love to hear how it could be improved.  Please leave a comment if you’ve got any questions or if you know of a way it can be better!

4 comments:

Jon Kruger said...

Looks great! That's pretty much the same thing that we're doing on our project.

IoC containers make things so much easier. I don't know what I did without them.

Tim said...

Reading through the Fluent nH wiki, it sounded like the Id discovery was automatic. Did you have to add the Primary Key Convention part?

Great post, Steve. I'll be using this one in the near future.

Steve Horn said...

@Tim

The conventions are only necessary if you want to deviate from the default conventions. Fluent Nhibernate's default is for the Id column to be called "Id" in the database. I happened to want the primary key called "EntityNameId". Here's the code for that class:

public class PrimaryKeyConvention : IIdConvention
{
public bool Accept(IIdentityPart target)
{
return true;
}

public void Apply(IIdentityPart target)
{
target.ColumnName(target.Property.ReflectedType.Name + "Id");
}
}

Ben Clark-Robinson said...

Top post, it's a real help. But I'm somewhere between lost and getting-it. I think I'm ok with the individual parts (Fluent, DI, MVC), but I'm lost at how they [glue] come together. You're post is coming close to what I need.

I've looked at OSS implementations but they're to advanced, and posts that show the NHibernate implementation in a UnitTest project don't go far enough.

Do you have a solution file that went with this post so I can see it as a whole?