Understanding Lazy Loading Strategies for NHibernate(zhuan)

For some time I have put off implementing lazy-loading in my current project because (ironically) I am lazy. It is just conceptually easier to load the object you want and then move on. I think this due to a brief encounter I had with a NHibernate-created frankenproxy when I issued a 'ToString()' on one of the objects I had loaded and as far as I recollect I got the crazy proxy name instead of the override ToString() value I expected. Now I am sure now I had simply not overriden ToString() like I thought I had but I simply haven't had time to sit down and design as if my objects were lazy loaded.The notes here are what I picked up today so if you see something wrong please let me know!

The proxied instances returned by NHibernate will invoke the ToString(), Equals(), and GetHashCode() methods to the target object type if they have been overriden (which I hope is the case). This might be a concern since the proxies themselves would have these methods, but they are transparent in this regard.

The newer versions of NHibernate load lazily by default, so it is important to have made decisions and understand what your Unit of Work strategy is. Assuming this is an ASP.NET application, are you storing your ISession in the Context.Items and having the end of the Request flush your ISession? ( a common practice) This is relevant because it is important to know that if you are working with objects that have been loaded lazily after the session has been flushed will throw an NHibernate exception since the proxy can't invoke to its target without an ISession. If this comes up, you may reattach the object to the current ISession using session.Lock(myObject) . Personally, I use FlushMode.Commit mode since I like things to happen when I instruct them to and it is conceptually easier for me than to tie my .Flush() to an event.

There are a couple of options for determining the objects NHibernate will return from its lazy-loads.

#1

The most common is to simply mark the class with the 'lazy="true"' attribute or place 'default-lazy="true"' in the mapping declaration:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"assembly="@core.assembly@"

default-access="nosetter.camelcase-underscore" default-lazy="true">

Or

  <class name="Cei.eMerge.Core.Domain.Contacts.Contact" table="Contact"lazy="true" >

If you go this route, remember the following:

  • Have a default constructor that is at leastprotected visibility. Private will not be able to be proxied.
  • Properties and methods will need to be markedvirtual. This is because the proxy returned by NHibernate is a subclass of your object. This point might make you hesitate to go the lazy-loading route, but don't let it. The DDD purist might shudder because the persistence mechanism is somehow influencing the domain objects (of course the default constructor NHibernate requires is another violation of this). This is one of those decisions where your business objectives need to drive your decision instead of your technology objectives...it might make sense to avoid this, but be sure you weigh the performance benefit of lazy-loading in there, too.
  • This route allows you to use field access strategy for things like read-only collections and so on. I explain this in the next section.
#2

The other option is to tell NHibernate what interface to return for your proxy using the 'proxy="IMyInterface"' in the class declaration:

  <class name="Cei.eMerge.Core.Domain.Contacts.Contact" table="Contact"proxy="IContact" >

Remember this is you go this route:

Properties and methods don't need to be marked virtual.

  • This will impact the access strategies available to your objects' members. If you were to use 'access="field.camelcase-underscore"'  or 'access="nosetter.camelcase-underscore"' for one of the properties above Nhibernate will try to find a member such a name as "_fieldName" and will throw an exception because that doesn't exist in your interface. One solution would be to change the access strategy to "access="property"" (the default) and allow for an setter that has a "lower" visibility (C# 2.0 and higher only). That way, the visiblity is still restricted but NHibernate can set your properties' values. But wait...here again we are letting persistence decisions trickle into our domain objects. That may be fine, but it should still be a deliberate decision. For example, on thePerson implementation of IPerson we might have:

    /// <summary>

    /// The First Text of an Individual Contact. Such as "Franz" or "Harry".

    /// </summary>

    public virtual string FirstName

            {

    get

                {

    return _firstName;

                }

    protected set{ _firstName = value;}

            }

  • A final common issue is casting. If you lazyload an object and try to cast it to its inherited type, you will get a cast exception. Remember you are dealing with a proxied instance which is a subclass of your intended object...thus the casting exception.

The benefits of lazy-loading can be immense. Not only can you avoid the common select n+1 problem, but you can drastically reduce the size of the queries hitting your db. This will especially be important if you have, say, turned ViewState off and are hitting the DB for data with each request and caching isn't an option.

UPDATE: I goofed when I wrote this (late at night).Select N+1 is something to beware of when dealing with lazy loading. See this post by Oren Eini for ways to deal with this. Thanks Bill for catching my error!

posted @ 2009-09-22 09:00  pursue  阅读(385)  评论(0编辑  收藏  举报