views:

614

answers:

3

I'm developing a website with ASP.NET MVC, NHibernate and Fluent Hibernate and getting the error "no session or session was closed" when I try to access a child object.

These are my domain classes:

public class ImageGallery {
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual IList<Image> Images { get; set; }
}

public class Image {
    public virtual int Id { get; set; }
    public virtual ImageGallery ImageGallery { get; set; }
    public virtual string File { get; set; }
}

These are my maps:

public class ImageGalleryMap:ClassMap<ImageGallery> {
    public ImageGalleryMap() {
        Id(x => x.Id);
        Map(x => x.Title);
        HasMany(x => x.Images);
    }
}

public class ImageMap:ClassMap<Image> {
    public ImageMap() {
        Id(x => x.Id);
        References(x => x.ImageGallery);
        Map(x => x.File);
    }
}

And this is my Session Factory helper class:

public class NHibernateSessionFactory {
    private static ISessionFactory _sessionFactory;
    private static ISessionFactory SessionFactory {
        get {
            if(_sessionFactory == null) {
                _sessionFactory = Fluently.Configure()
                    .Database(MySQLConfiguration.Standard.ConnectionString(MyConnString))
                    .Mappings(m => m.FluentMappings.AddFromAssemblyOf<ImageGalleryMap>())
                    .ExposeConfiguration(c => c.Properties.Add("hbm2ddl.keywords", "none"))
                    .BuildSessionFactory();
            }
            return _sessionFactory;
        }
    }
    public static ISession OpenSession() {
        return SessionFactory.OpenSession();
    }
}

Everything works fine, when I get ImageGallery from database using this code:

IImageGalleryRepository igr = new ImageGalleryRepository();
ImageGallery ig = igr.GetById(1);

But, when I try to access the Image child object with this code

string imageFile = ig.Images[1].File;

I get this error:

Initializing[Entities.ImageGallery#1]-failed to lazily initialize a collection of role: Entities.ImageGallery.Images, no session or session was closed

Someone know how can I fix this?

Thank you very much!

Edit

My GetById method is:

    public ImageGallery GetById(int id) {
        using(ISession session = NHibernateSessionFactory.OpenSession()) {
            return session.Get<ImageGallery>(id);
        }
    }
+2  A: 

The session used to get the ig must be alive when accessing ig.Images[1]

I usually do this by instantiating the session before all repository calls, passing the session reference to the the repository constructor and using that reference inside the repository class

Catalin DICU
How and where do you instantiate session before repo calls? On the controller? And when do you close the session? Thank you!
MCardinale
+5  A: 

Presumably, your GetById is closing the session. This could be explicit, or via a using statement.

A better approach to session management is the Open Session in View pattern. NHibernate sessions are cheap to create, so create one at the start of each request, and close it at the end.

// in global.asax.cs

public void Application_Start()
{
    BeginRequest += delegate {
        CurrentSessionContext.Bind( sessionFactory.OpenSession());
    };

    EndRequest += delegate {
        var session = sessionFactory.GetCurrentSession();
        if (null != session) {
            session.Dispose();
        }
        CurrentSessionContext.Unbind(sessionFactory);
    };
}

// in your NHibernateSessionFactory class
public static ISession OpenSession() {
    return SessionFactory.GetCurrentSession();
}

Using a DI container, we can have the session injected with instances scoped per-request.

// ninject example
Bind<ISession>()
    .ToMethod( ctx => sessionFactory.GetCurrentSession() )
    .InRequestScope();
Lachlan Roche
+1 for *Open Session in View* pattern, I couldn't remember the name.
R0MANARMY
One thing about that pattern that strikes me is that (depending on FlushMode) you could modify your entity in the View rendering code. Is that just the risk you take for the convenience of not projecting everything to DTOs?
dotjoe
Thank you very much. I asked another question (http://stackoverflow.com/questions/2525630/) to somebody help do this, because I don't know how.
MCardinale
+2  A: 

I'm not sure if this applies here, but this is (or at least used to be) a common problem in Java MVC frameworks. It usually happens when create the session inside your action. When your action finishes executing, the session object goes out of scope and the session is closed/flushed. Then when the view tries to render the collection of objects you retrieved, it tries to load the lazy-loaded collection using a session that's been closed.

If you're using dependency injection in your project you could have your DI framework instantiate the session for you and pass it as constructor argument into your controller. You could also specify that the DI container should scope the NHibernate session to be the same as your HTTP Request. This will keep the session alive until the request ends (view has finished rendering).

I've used the Autofac as our DI container on a project and work and have been very happy with it.

R0MANARMY