When it comes to NHibernate no matter what you do, generally there are many different approaches you can take. If you are thinking about writing a query you can use QueryOver, Linq, Criteria or Hql. What I'll be discussing in this post is how I prefer to set up mappings, which is using the mapping by convention method. While it might not be the most optimal way of doing things when working with legacy databases, setting up conventions to reuse across domain models helps to save a lot of time as well as make things easier to maintain.
A full explaination of the mapping by convention method is beyond this blog post, but you can get a good reference to it by checking out this article.
What I wanted to cover is an implementation that is very simple, but could save your team a lot of headaches down the line if your data layer continues to grow. If you take a look at how I've set up my projects, I was able to separate them based on the type of content I wanted to store:
The approach I'm about to show has allowed me to include these projects as needed into separate solutions. I just add the project in and during site startup I'm able to use dependency injection to discover different pieces of each module to correctly wire up the mappings.
Here is where it all begins:
using NHibernate.Mapping.ByCode;
namespace FastTrack.Domain.Interfaces
{
public interface IPersistenceConfiguration
{
void Configure(ConventionModelMapper mapper);
}
}
I've set up this interface to have one method on it, this method takes in the ConventionModelMapper
and from there adds any configuration needed for this project.
Here is an example from the FastTrack.GeoManager project:
using FastTrack.Domain.Interfaces;
using FastTrack.GeoManager.DataModels;
using NHibernate.Mapping.ByCode;
namespace FastTrack.GeoManager.Infrastructure
{
public class PersistenceConfiguration : IPersistenceConfiguration
{
public void Configure(ConventionModelMapper mapper)
{
mapper.Class<City>(map =>
{
map.ManyToOne(x => x.State, m =>
{
m.Column("StateId");
});
});
mapper.Class<State>(map =>
{
map.ManyToOne(x => x.Country, m =>
{
m.Column("CountryId");
});
});
}
}
}
There wasn't a lot of mappings I had to update since I'm using mapping by convention, I only have to map what I couldn't initially set up "by convention."
Next is how I was able to set this up using Autofac. Here is where all of the implementations are registered:
builder.RegisterAssemblyTypes(assemblies)
.AssignableTo<IPersistenceConfiguration>()
.AsImplementedInterfaces()
.InstancePerDependency();
I had this set up in an Autofac Module. From there I was able to inject all of the implemenations into the class that I use to register my database:
public class RegisterDatabase : IRunAtInit
{
private readonly IEnumerable<IPersistenceConfiguration> _persistenceConfigurations;
public RegisterDatabase(IEnumerable<IPersistenceConfiguration> persistenceConfigurations)
{
_persistenceConfigurations = persistenceConfigurations;
}
public void Execute()
{
var configuration = new NHibernate.Cfg.Configuration();
var mapper = new ConventionModelMapper();
mapper.GetEntityMappings();
foreach (var persistenceConfiguration in _persistenceConfigurations)
{
persistenceConfiguration.Configure(mapper);
}
var hbmMapping = mapper.CompileMappingFor(allEntities);
configuration
.SessionFactoryName("FastTrack")
.SetProperty(NHibernate.Cfg.Environment.ConnectionString, _configuration.GetConnectionString("FastTrack"))
.SetProperty(NHibernate.Cfg.Environment.Dialect, "NHibernate.Dialect.MsSql2012Dialect")
.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, "NHibernate.Driver.Sql2008ClientDriver, NHibernate")
.SetProperty(NHibernate.Cfg.Environment.Isolation, "ReadUncommitted")
.SetProperty(NHibernate.Cfg.Environment.FormatSql, Boolean.FalseString)
.SetProperty(NHibernate.Cfg.Environment.GenerateStatistics, Boolean.FalseString)
.SetProperty(NHibernate.Cfg.Environment.Hbm2ddlKeyWords, Hbm2DDLKeyWords.None.ToString())
.SetProperty(NHibernate.Cfg.Environment.PrepareSql, Boolean.TrueString)
.SetProperty(NHibernate.Cfg.Environment.PropertyUseReflectionOptimizer, Boolean.TrueString)
.SetProperty(NHibernate.Cfg.Environment.QueryStartupChecking, Boolean.FalseString)
.SetProperty(NHibernate.Cfg.Environment.ShowSql, Boolean.FalseString)
.SetProperty(NHibernate.Cfg.Environment.StatementFetchSize, "100")
.SetProperty(NHibernate.Cfg.Environment.UseProxyValidator, Boolean.FalseString)
.SetProperty(NHibernate.Cfg.Environment.UseSecondLevelCache, Boolean.TrueString)
.SetProperty(NHibernate.Cfg.Environment.UseSqlComments, Boolean.FalseString)
.SetProperty(NHibernate.Cfg.Environment.UseQueryCache, Boolean.TrueString)
.SetProperty(NHibernate.Cfg.Environment.WrapResultSets, Boolean.TrueString)
.SetProperty(NHibernate.Cfg.Environment.CacheProvider, "NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache")
.AddMapping(hbmMapping);
}
}
Pretty straightforward stuff, but hopefully this can be a launching off point for any other developer thinking of making their data layer more modular.