0

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

David
  • 15,750
  • 22
  • 90
  • 150

4 Answers4

4

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
  • 48,427
  • 16
  • 81
  • 117
  • I'm really confused now. I thought the point of the repository pattern was to hide the ORM implmementation from the UI layer? – David Jul 21 '10 at 12:15
  • 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 Jul 21 '10 at 12:47
1

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
  • 6,310
  • 4
  • 40
  • 68
  • This thing about repository actions instantiating their own ISessions being an antipattern. Can you elaborate? I thought ISessions were cheap? – David Jul 21 '10 at 12:17
  • Oh, I see, you then lose the dirty checking. – David Jul 21 '10 at 12:17
  • @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 Jul 21 '10 at 13:36
  • 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 Jul 21 '10 at 13:52
  • @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 Jul 21 '10 at 14:35
  • 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 Jul 21 '10 at 14:40
  • @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 Jul 21 '10 at 14:44
  • 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 Jul 21 '10 at 15:05
  • So the ISession required by the repository gets injected by the container...problem solved... – DanP Jul 21 '10 at 15:17
  • So I just need to create an interface for which the ISession is the concrete implementation? – David Jul 22 '10 at 07:59
1
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
  • 40,424
  • 5
  • 55
  • 128
  • No disrespect, but how is that an improvement on what I originally posted? – David Jul 19 '10 at 19:14
  • 1
    Do you mean NHibernateUtil.Initialize(storedWill)? At least then you're not calling an arbitrary property. – David Jul 19 '10 at 19:16
  • 1
    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 Jul 19 '10 at 19:38
  • Can you preload just one field?! Surely NH will retrieve all data for the entity in one command? – David Jul 21 '10 at 12:13
  • it's fun to make assumptions isn't it? – anthony Jul 21 '10 at 22:47
0

Part of you problem is that you're using Session.Load() instead of Session.Get().

see this article by Ayende Rahien - The difference between Get, Load and querying by id for a in-depth description of the two.

When you call Session.Load() you're telling nhibernate to create a proxy object of the Id that you supply. When doing this nhibernate will not actually call the database to retrive the data. This means that if you Session.Load() with something that does not exits in the database it will throw a Exception. Because you're closing your session before your accessing the object you can't access the data because the proxys session is now closed.

A simple fix would be to change your code to use Session.Get(). This will load your StoredWill class from the database and fill your object with the data that it needs. Note however if you have any inner classes or collections inside you object it will simply create proxies for those. If you need to get everything in one go, you can using one of the many query mechanisims to eager load the parts you need or use projections.

I hope this makes sense :)

Almond
  • 1,563
  • 1
  • 14
  • 20
  • Although your answer would fix the issue described, there is a deeper problem of design. Read DanP's answer and the following comments for more info. – David Feb 24 '11 at 09:30