Challenging non-local session scope (session-per-request)

I posted the following on the nhusers mailing list, and Steve Bohlen was nice enough to weigh in.  I was hoping to get broader feedback on this, so I’m posting it here. 

======================================================

I want to challenge a presumed best practice with NHibernate.  I’m challenging it honestly given my experience using it and helping many clients use it as well.

On the official NHibernate community website, nhforge.org, there is an article on session-per-request.  I have used this technique for most of the years I’ve used NHibernate.  I have been able to use it effectively.  But, I couldn’t get over the fact that all of our clients have problems with this and end up in situations where lazy-load queries happen at all layers of the application.

My problem, then, is that this pattern, while technically sound and useful, introduces complexity that requires a deeper understanding of NHibernate to manage.  Because of this, we have begun disposing of the session after each and every transaction.  See here:

public Employee GetByUserName(string userName)
{
    using (ISession session = DataContext.GetSession())
    {
        IQuery query =
            session.CreateQuery(
            "from Employee emp left join fetch emp.Roles where emp.UserName = :username");
        query.SetParameter("username", userName);
        var match = query.UniqueResult<Employee>();
        return match;
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Here, I get the employee, dispose of the session, and pass the object back.  For each variation of loading, we have a different method that eager fetches the right level of association or collection.  This way, the application has the data it needs, and there aren’t any lazy-load exceptions.

This approach simplified the code in a few ways:

  • Each query to the database is known, and there is a method for each
  • All queries are executed while the call stack is in the data layer (no UI-level lazy loading)
  • It forces the developer to think about each data scenario

In coaching and training other developers (we actually teach NHibernate in our Agile Boot Camp training class), we have come up with a simple message for how to think about NHibernate and other ORMs.

  1. When moving from stored procedures to an ORM, don’t forget what was good about sprocs (you had a list of every query that would be run against your database)
  2. Use an ORM for just Object to Relational Mapping.  That is the core strength.  The other features, like lazy-loading, can be useful, but they also add complexity
  3. Keep all your relationships and collections mapped as lazy, but don’t allow them to lazily-load
  4. Dispose of a session immediately after using it.
  5. Keep the NHibernate reference out of the your core library and behind data access interfaces.

I’ll pause here because there is nothing wrong with session-per-request technically.  I successfully used it on many projects.  The problem is that it is complicated.  I understand it just fine, but I’m also an NHibernate expert.  Lots of other developers just want to use it and be done with it.  They don’t want surprises.  Elevating the session past local scope up to a global variable brings unintended consequences.  When developing, I might dereference another collection on an object, and NHibernate happily runs another select statement against my database.  As that developer, I don’t have profiler perpetually open to see this happen.  There is absolutely no signal that I just put something undesirable in my application.  Developers need fast feedback on what they are doing.  If there was no session hanging around, the lazy-load invocation would throw and excepting right then.  The developer would then go back and modify the query or decide that we need a new one.  We have used session-per-transaction on two projects now, and it hasn’t led to any other difficulties, so I view it very favorably.

A "best practice" should generally lead a developer to a desirable result.  I think that here, the best practice needs to lead to simplicity, even while the "advanced" practice may still be session-per-request.

I have not drawn a final conclusion, but given the bad effects I’ve seen from this, wouldn’t it be a simpler (better) practice to just dispose of the session immediately and create a few more explicit repository methods for different loading levels? 

I appreciate any discussion that arises from this.  The best place would be on the NHibernate Users mailing list.