Using Fluent NHibernate’s Automapper

In recent weeks, I have been thinking about using Fluent NHibernate’s automapper in one of my projects. I had used before in several past pet projects, but never thought of investigating just how it worked and how it can be overridden. I also wanted to investigate how could use Fluent Nhibernate as a migration utility, and explore using DTOs with NHibernate. The code that resulted from my investigation can be found on Github. Below are some of my observations:

Fluent for migration

Fluent Nhibernate comes with the SchemaUpdate class that you could utilize for migrating your database. The code would like:

new SchemaUpdate(your_configuration).Execute(true,false);

The problem with that approach however, from my investigations, is that it is limited. For example, when updating fields, it will insert NULLs in fields where you’d want it to have a default value. There’s no easy to perform a rollback either. This is quite understandable as NHibernate was meant to be an ORM. As such, it may not easily be used in conjunction with a migration utility. Instead, I decided to go with migratordotnet, which is better suited for my needs. You can expect to see a post on this in the coming weeks.

Using the automapper

For my configuration and session management needs, I implemented a class conveniently called “SessionManager” seen below

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using System.IO;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;
using DataLayer.Domains;

namespace DataLayer.ORM {

  public interface ISessionManager {
    ISessionFactory CreateNewDatabaseSessionFactory(bool inMemory = false);
    ISession OpenSession();
    Configuration Configuration { get; }
  }

  public class SessionManager : ISessionManager {

    public ISessionFactory CreateNewDatabaseSessionFactory(bool inMemory=false) {

      sessionFactory = BuildMapppings(inMemory)
            .ExposeConfiguration(BuildSchema)
            .BuildSessionFactory();
      return sessionFactory;

    }

    public ISession OpenSession() {
      return SessionFactory.OpenSession();
    }

    private SessionManager() {
    }

    private static SessionManager sessionManager;
    public static SessionManager Instance {
      get {
        if (sessionManager == null) sessionManager = new SessionManager();
        return sessionManager;
      }
    }

    private AutoPersistenceModel model;
    private AutoPersistenceModel Model {
      get {
        if (model == null) {
          model = new AutoPersistenceModel();
          model.IgnoreBase<BaseEntity>()
               .IgnoreBase<BrandedStoreItem>()
               .IgnoreBase<StoreItem>();
        }

        return model;
      }
    }

    private static Configuration configuration;
    public Configuration Configuration { get { return configuration; } }
    private void BuildSchema(Configuration config) {
      configuration = config;
      new SchemaExport(config)
        .Create(false, true);
    }

    private FluentConfiguration BuildMapppings() {
      return BuildMapppings();
    }

    private static bool isInMemoryDatabase;
    private static ISessionFactory sessionFactory;
    private ISessionFactory SessionFactory {
      get {
        if (sessionFactory == null && !isInMemoryDatabase) {
          sessionFactory = BuildMapppings(isInMemoryDatabase)//.ExposeConfiguration((c) => configuration = c)
                                            .BuildSessionFactory();
        }
        return sessionFactory;
      }
    }

    private FluentConfiguration BuildMapppings(bool inMemory=false) {
      isInMemoryDatabase = inMemory;
      var assembly = typeof(BaseEntity).Assembly;
      return Fluently.Configure()
                      .Database(
                         Config(inMemory)
                      )
       .Mappings(m => m.AutoMappings.Add(Model.AddEntityAssembly(assembly)
                                                              .Where(w => !w.IsAbstract && w.Namespace.Equals(typeof(Vendor).Namespace))
                                                              .Conventions.AddAssembly(assembly)
                                                              .UseOverridesFromAssembly(assembly)
                                                              ));
    }

    private IPersistenceConfigurer Config(bool inMemory = false) {

      IPersistenceConfigurer config;
      if (inMemory) {
        config = SQLiteConfiguration.Standard.ConnectionString(System.Configuration.ConfigurationManager.ConnectionStrings["Sqlite_InMemory"].ConnectionString);
      }
      else {
        config = MsSqlConfiguration.MsSql2005.ConnectionString(System.Configuration.ConfigurationManager.ConnectionStrings["Data"].ConnectionString);
      }

      return config;
    }

  }

}
For the automapping code, you only need to care about Line 86-98. You will notice that I am ignoring the the abstract classes, and only including the entities inside a particular namespace. This is because the Automapper get confuse when you have a bunch of abstract classes and base classes. There is a method called “IgnoreBase” that’s meant to avoid some of these issues, but it does not work when you have a bunch of abstract classes related to each other as you could see from my source code on github.
I also use an InMemory database for some of my tests. It would appear that these can mess up tests as the configuration often gets lost, which why I would suggest using a file database or setting aside a test database to minimize your headaches.

It is still feasible to use conventions with the automapper. Among the conventions I tried out are:
– Making the entities names plural in the database (e.g. Shoe will become Shoes, Currency will become Currencies)
– Adding an X in the name of join tables (e.g. Table1XTable2)
– Setting the cascade properties for HasOne, HasMany and HasReference relationships. This one is almost a must-have as the Automapper does not set the cascade properties for you. So if you find yourself dealing with a bunch “transient object” errors, you know what to do 🙂

I would strongly suggest writing tests to verify that the mappings created by the automapper are as you expected. You might find it useful to implement a custom equality comparer to avoid errors similar to this one:  For property ‘Currency’ expected same element, but got different element of type ‘DataLayer.Domains.Currency’


This code below worked for my needs.
[Test]
public void Can_Map_Vendor() {
      new PersistenceSpecification<Vendor>(session)
          .CheckProperty(e => e.Id, (long)1)
          .CheckList(e => e.BagsSold, new List<Bag>())
          .CheckList(e => e.BooksSold, new List<Book>())
          .CheckList(e => e.ShoesSold, new List<Shoe>())
          .CheckProperty(e => e.IsOfficialReseller, false)
          .CheckProperty(e => e.Name, "")
          .CheckProperty(e => e.Phone, "")
          .CheckProperty(e => e.Rating, 1.0)
          .CheckProperty(e => e.Email, "")
          .VerifyTheMappings();

    }

    class CustomEqualityComparer<T> : IEqualityComparer where T : BaseEntity {
      public bool Equals(object x, object y) {
        if (x == null || y == null) {
          return false;
        }
        if (x is T && y is T) {
          return ((T)x).Id == ((T)y).Id;
        }
        return x.Equals(y);
      }

      public int GetHashCode(object obj) {
        return obj.GetHashCode();
      }
    }

Overriding the Automapper

I wanted to try out overriding the automapper so it adds a composite key to the class mapping. To do that, I tried the code below

public class CurrencyOverride: IAutoMappingOverride<Currency> {
    public void Override(FluentNHibernate.Automapping.AutoMapping<Currency> mapping) {
      mapping.CompositeId().KeyProperty(e => e.Id)
                            .KeyProperty(e => e.Name);
    }
  }

For this to work, another class mapping needed to change since I was now referencing two keys. To handle this case, I tried the code below

//Code below wou;d have added Foreign key. The problem is that it led to this type of error:
  //Foreign key ** must have same number of columns as the referenced primary key **
  public class BookOverride : IAutoMappingOverride<Book> {
    public void Override(FluentNHibernate.Automapping.AutoMapping<Book> mapping) {
     mapping.HasOne<Currency>(e => e.Currency).ForeignKey("Id").ForeignKey("Name");//.Add("Id", "Name");
    }
  }

This lead to more issues that I had expected. In the end, I decided to do override something that is not tied to multiple tables. Below is the code I used to make a field unique.

public class CurrencyOverride: IAutoMappingOverride<Currency> {
    public void Override(FluentNHibernate.Automapping.AutoMapping<Currency> mapping) {
      mapping.Map(e => e.Name).Unique();
    }
  }

If your intent is to create a composite key while still using the automapper, it is preferable to create a custom Key class. This is just a hunch as I haven’t tried, but I believe it would work.

I will talk about using DTOs with NHibernate in a later blog post.

Advertisements
Using Fluent NHibernate’s Automapper

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s