views:

140

answers:

3

Hi all

As part of my endless NHibernate-inspired DAL refactoring purgatory, I have started to use the Repository pattern to keep NHibernate at arms length from my UI layer. Here's an example of a Load method from a repository.

public StoredWill Load(int id)
{
  StoredWill storedWill;
  using (ISession session = NHibernateSessionFactory.OpenSession())
  {
    storedWill = session.Load<StoredWill>(id);
  }
  return storedWill;
}

I love the fact that my website doesn't know what an ISession is.

Naturally, I started getting lazy initialisation exceptions because the method above doesn't load the StoredWill, it just returns a proxy. When you access the proxy's properties, you get the exception because you are ro longer within the scope of the ISession. I laughed out loud when I realised what was happening.

I have fixed this with:

public StoredWill Load(int id)
{
  StoredWill storedWill;
  using (ISession session = NHibernateSessionFactory.OpenSession())
  {
    storedWill = session.Load<StoredWill>(id);
    string iReallyCouldntCareLess = storedWill.TestatorLastName;
  }
  return storedWill;
}

But it all seems a little silly. Does anyone use a slightly more elegant pattern?

Love you guys.

David

+1  A: 

Check out the ncommon framework; it has a nice implementation of fetching strategies that are perfect for this sort of task. You can read more about how this is implemented on the author's blog.

I should also note that your session usage is a little off...you should really be controlling the lifecycle of your session higher up (say at the controller or httpmodule level depending on your front-end). Opening a new session for each repository action is a major anti-pattern in the nhibernate world.

DanP
This thing about repository actions instantiating their own ISessions being an antipattern. Can you elaborate? I thought ISessions were cheap?
David
Oh, I see, you then lose the dirty checking.
David
@David: a lot more than that, really...you lose the first level cache (identity map), delayed write operations, lazy loading (as you've found), etc...
DanP
Then how does this work with the repository pattern? Is it impossible to combine the benefits of the ISession with the Repository pattern? (Perhaps this needs to be a new topic).
David
@David: As Jamie Idle has suggested above, you need to inject an ISession instance into your repositories; check out a more complete project that makes use of these patterns..something like sharp architecture will give you a good reference implementation.
DanP
Then we're not fully separated, the UI project code needs to directly access repository implementation code. But okay, thank you for the heads-up.
David
@David: you should be injecting repository interfaces at the UI level as well, you should never be "newing" up repository instances for a variety or reasons...
DanP
I'm not sure I know what you mean. The UI layer code uses only repository interfaces. Concrete implementations of the repositories are provided by dependency injection. The new keyword is not used.
David
So the ISession required by the repository gets injected by the container...problem solved...
DanP
So I just need to create an interface for which the ISession is the concrete implementation?
David
+1  A: 
public StoredWill Load(int id)
{
  StoredWill storedWill;
  using (ISession session = NHibernateSessionFactory.OpenSession())
  {
    storedWill = session.Load<StoredWill>(id);
    // force an eager load within the session
    NHibernateUtil.Initialize(storedWill.TestatorLastName);
  }
  return storedWill;
}
anthony
No disrespect, but how is that an improvement on what I originally posted?
David
Do you mean NHibernateUtil.Initialize(storedWill)? At least then you're not calling an arbitrary property.
David
It wasn't clear to me if you wanted to preload all fields or one field. It's an improvement because you don't have to define a string and is therefore less "silly". Depending on your desired outcome and the structure of your persistent classes, you may also be interested in adding 'default-lazy="false"' to your NHibernate mapping.
anthony
Can you preload just one field?! Surely NH will retrieve all data for the entity in one command?
David
it's fun to make assumptions isn't it?
anthony
Is there a message behind your oblique remark? :)
David
+3  A: 

Use dependency injection and pass an ISession to your repository classes through their constructors. That's the only way to allow multiple repositories to participate in the same transaction.

Your website should have some knowledge of an ISession because that's where the transaction boundaries are defined. You can use session-per-request pattern so that the ISession is only referenced in an HttpRequest module or Global.asax. Or you can use a framework or embrace NHibernate and control transactions on the pages.

Jamie Ide
I'm really confused now. I thought the point of the repository pattern was to hide the ORM implmementation from the UI layer?
David
It hides the details, but you need to inform the repository which unit-of-work its participating in. You can use the ISession as a unit-of-work container or wrap it in your own unit-of-work implementation.
Jamie Ide