This is the final post in a series of three where I'm going to see how we can change the NerdDinner project to use Fluent NHibernate instead of LINQ to SQL:
Introduction
In the first post we took a look at the domain model of NerdDinner.com, and recreated and improved the code that was auto generated by the LINQ to SQL designer. In the second post we saw how to map this domain model to the database using Fluent NHibernate. In this final post we are going to take a look at the required changes to the rest of the application.
Session per request
In order for the repository to use NHibernate it needs to get a hold of an ISession. In this application I have chosen to follow the session per request pattern, in which a session is created at the beginning of the HTTP request, and closed at the end of the HTTP request. I also wanted to keep the changes to the NerdDinner project as small as possible; hence the entire infrastructure for NHibernate is isolated in a single class:
public class NHibernateSessionPerRequest : IHttpModule
{
private static readonly ISessionFactory _sessionFactory;
static NHibernateSessionPerRequest()
{
_sessionFactory = CreateSessionFactory();
}
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
public static ISession GetCurrentSession()
{
return _sessionFactory.GetCurrentSession();
}
public void Dispose() { }
private static void BeginRequest(object sender, EventArgs e)
{
ISession session = _sessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
private static void EndRequest(object sender, EventArgs e)
{
ISession session = CurrentSessionContext.Unbind(_sessionFactory);
if (session == null) return;
try
{
session.Transaction.Commit();
}
catch (Exception)
{
session.Transaction.Rollback();
}
finally
{
session.Close();
session.Dispose();
}
}
private static ISessionFactory CreateSessionFactory()
{
string connString = "NerdDinnerConnectionString";
FluentConfiguration configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(
x => x.FromConnectionStringWithKey(connString)))
.ExposeConfiguration(
c => c.SetProperty("current_session_context_class", "web"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Dinner>());
return configuration.BuildSessionFactory();
}
}
Some comments about the infrastructure:
- It implements the IHttpModule, in order to plug into the request handling pipeline.
- Since it is expensive to create an ISessionFactory you only want to create this once. This is why the factory is stored in a static field that is initialized by the static constructor.
- At the beginning of the HTTP request the ISession is created and "stored" using the CurrentSessionContext.Bind method.
- Other code can get a hold of this session by calling the GetCurrentSession method.
- At the end of the HTTP request we "remove" the session by using the CurrentSessionContext.Unbind method. We also commit any changes, and close the session.
There are some drawbacks to this approach. We create a session for every request, even if it is not needed. However, a session is very lightweight, and we can easily tweak this behavior later. Another drawback is that we commit the transaction at the end of the HTTP request. In a more complex business application you probably want more control over your unit of work.
In order to activate this IHttpModule we need to add the following to httpModules/modules section of the Web.config:
<add name="NHibernateSessionPerRequest" type="NerdDinner.Models.NHibernateSessionPerRequest" />
Changes to the repository
In the DinnerRepository we are mainly going to use LINQ to NHibernate to replace the LINQ to SQL queries:
public class DinnerRepository : IDinnerRepository
{
public ISession Session
{
get { return NHibernateSessionPerRequest.GetCurrentSession(); }
}
public IQueryable<Dinner> FindAllDinners()
{
return Session.Linq<Dinner>();
}
public IQueryable<Dinner> FindUpcomingDinners()
{
return from dinner in FindAllDinners()
where dinner.EventDate >= DateTime.Now
orderby dinner.EventDate
select dinner;
}
public IQueryable<Dinner> FindByLocation(float latitude, float longitude)
{
return Session.CreateSQLQuery(
@"SELECT d.*
FROM Dinners d
JOIN NearestDinners(:Latitude, :Longitude) n
ON d.DinnerId = n.DinnerId
WHERE EventDate >= :EventDate
ORDER BY EventDate Desc")
.AddEntity(typeof(Dinner))
.SetDouble("Latitude", latitude)
.SetDouble("Longitude", longitude)
.SetDateTime("EventDate", DateTime.Now)
.List<Dinner>().AsQueryable();
}
public Dinner GetDinner(int id)
{
return Session.Linq<Dinner>()
.SingleOrDefault(d => d.DinnerID == id);
}
public void Add(Dinner dinner)
{
Session.SaveOrUpdate(dinner);
}
public void Delete(Dinner dinner)
{
Session.Delete(dinner);
}
}
The LINQ to SQL and NHibernate Linq syntax are similar, so the changes to this class were fairly simple. However in the FindByLocation method I had to create a custom SQL query, because I don't think (not sure) that LINQ to NHibernate supports calling the database function NearestDinners.
Update: It seems that NHibernate Linq supports database functions. However I couldn't find much information about the functionality except for one simple example in the source code.
The ISession is retrieved by calling the GetCurrentSession() method. In an application with an IoC container I would prefer to inject the session in the constructor.
finally {}
In the final post in this series we have taken a look at the necessary changes to the infrastructure. We used a session per request pattern to create the session, and LINQ to NHibernate in the repository. As you hopefully have seen it was not very difficult to replace LINQ to SQL with Fluent NHibernate. As a benefit the domain part of the application has been greatly improved over the auto generated code from the LINQ to SQL designer.
If you liked this post then please shout and kick me :)