views:

26

answers:

1

I just started with NHibernate, created my mappings using fluent NHibernate as follows:

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Id(x => x._id, "Id");
        Map(x => x._KdNr, "KdNr");
        Map(x => x._Name, "Name");
        HasMany(x => x._Contact)
            .Table("Contacts")
            .KeyColumn("FKCustomerID")
            .LazyLoad();
    }
}


public class ContactMap : ClassMap<Contact>
{
    public ContactMap()
    {
        Id(x => x._id, "Id");
        Map(x => x._Name, "Name");
    }
}

And to save new records works also:

    public static void AddCustomer(Customer cust)
    {
        using (var session = SessionFactory.Instance.OpenSession())
        {
            session.Save(cust);
            session.Save(cust._Contact);
            session.Flush();
        }
    }

Then I tried to select the customer I added using:

        using (var session = SessionFactory.Instance.OpenSession())
        {
            try
            {
                var v = session.CreateQuery("from Customer").List<Customers>();
                return (List<Customer>)v;
            }
            catch
            {
                session.Close();
                throw;
            }
            finally
            {
                session.Disconnect();
            }
        }
    }

The Customer also is loaded fine, but the contacts inside are not referenced with the following error:

Initializing[fnh.DataModel.Customer#d2f2d1c5-7d9e-4f77-8b4f-9e200088187b]-failed to lazily initialize a collection of role: fnh.DataModel.Kunde._Contact, no session or session was closed

But I cannot understand where the error comes from because the session is closed after executing my HQL error...

+1  A: 

The problem is that the contacts are lazy loaded, ie. the collection is not fetched from the database in the initial query. I presume that you are passing the objects directly to the view rather than using a viewmodel? You have a few options, each have their drawbacks.

  1. Keep the session open while the view is being process (so called Open Session In View approach). You are probably closing the NH session in the controler at the moment right?

  2. Eager load the whole object graph using .Not.Lazyload() on the contacts. (Not recommended)

  3. Copy all the data you need to a view model in the controller, and pass this to the view. Use automapper to help you with this.

Update in response to comment:

There is still great benefit in leaving collections lazy loaded. Sure, in this context you don't benefit from lazy loaded contacts on the customer object, becase you need to use them. But in another context you might only need the customer name and Id - and you can rest assured that this wont generate a big query with joins etc.

To utilize Open Session in View you don't have to pass your session to the view explicitly, rather you just pass the object as before, but leave the session open - NH will automatically generate a new query when you try to access the contacts collection. (This works becuase the customer object is still 'attached' to an open session behind the scenes). The only difference here is that you need to close the session not in the controller (where it is currently being closed explicitly with the .close() or implicitly with 'using'). Regarding where to open and close the session - there are different approaches:

  • In the global.asax - open in Application_BeginRequest and close in Application_EndRequest (see: this article) I'd recomend starting with this for the sake of simplicity if you want to do Open Session in View.

  • In a http module (basically the same as the last, but modularised) (see this article)

With these last two, you are probably thinking 'But that will mean creating a session for every page request!' - and you would be right, but really how many pages are not going to go to the DB? plus session creation is lightweight once the session factory is created.

  • Using an Attribute decorating the action method (you basically do the same thing as the last two, but this way you can choose which actions open an NH session). Close the session in OnActionExecuted if you want it closed after the action has completed. Close in OnResultExecuted if you want open session in view.

Of course, many people don't like Open Session in View, becuase you cannot control what queries are being generated purely in the controller - the view is triggering many queries, this can cause unpredictable performance.

3 is subtely different from 2, because in situations where you don't need the lazy loaded collection it is not fetched from the DB. For instance, in this case you might be copying to a ViewModel with the full object graph, but in another situation you might use a ViewModel with just the customer name and Id - in this case the joins for the contacts collection would not needlessly be executed. In my opinion 3 is the proper way to do things - but you end up creating many more objects (becuase of the view objects).

UpTheCreek