NHibernate -failed to lazily initialize a collecti

2020-05-31 05:45发布

问题:

I have the following seemingly simple scenario, however I'm still pretty new to NHibernate.

When trying to load the following model for an Edit action on my Controller:

Controller's Edit Action:

public ActionResult Edit(Guid id)
{
    return View(_repository.GetById(id));
}

Repository:

public SomeModel GetById(Guid id)
{
    using (ISession session = NHibernateSessionManager.Instance.GetSession())
        return session.Get<SomeModel >(id);
}

Model:

public class SomeModel
{
    public virtual string Content { get; set; }
    public virtual IList<SomeOtherModel> SomeOtherModel { get; set; }
}

I get the following error:

-failed to lazily initialize a collection of role: SomeOtherModel, no session or session was closed

What am I missing here?

回答1:

The problem is that you create and also close the session in you models GetById method. (the using statement closes the session) The session must be available during the whole business transaction.

There are several ways to achieve this. You can configure NHibernate to use the session factories GetCurrentSession method. See this on nhibernate.info or this post on Code Project.

public SomeModel GetById(Guid id)
{
    // no using keyword here, take the session from the manager which
    // manages it as configured
    ISession session = NHibernateSessionManager.Instance.GetSession();
    return session.Get<SomeModel >(id);
}

I don't use this. I wrote my own transaction service which allows the following:

using (TransactionService.CreateTransactionScope())
{
  // same session is used by any repository
  var entity = xyRepository.Get(id);

  // session still there and allows lazy loading
  entity.Roles.Add(new Role());

  // all changes made in memory a flushed to the db
  TransactionService.Commit();
}

However you implement it, sessions and transactions should live as long as a business transaction (or system function). Unless you can't rely on transaction isolation nor rollback the whole thing.



回答2:

You need to eagerly load the SomeOtherModel collection if you intend to use it before closing the session:

using (ISession session = NHibernateSessionManager.Instance.GetSession())
{
    return session
        .CreateCriteria<SomeModel>()
        .CreateCriteria("SomeOtherModel", JoinType.LeftOuterJoin)
        .Add(Restrictions.Eq(Projections.Id(), id))
        .UniqueResult<SomeModel>();
}

By default FluentNHibernate uses lazy loading for collection mappings. Another option is to modify this default behavior in your mapping:

HasMany(x => x.SomeOtherModel)
    .KeyColumns.Add("key_id").AsBag().Not.LazyLoad();

Note that if you do this SomeOtherModel will be eagerly loaded (using an outer join) every time you load the parent entity which might not be want you want. In general I prefer to always leave the default lazy loading at the mapping level and tune my queries depending on the situation.



回答3:

"If we want to access the order line items (after the session has been closed) we get an exception. Since the session is closed NHibernate cannot lazily load the order line items for us. We can show this behavior with the following test method"

[Test]
[ExpectedException(typeof(LazyInitializationException))]
public void Accessing_customer_of_order_after_session_is_closed_throws()
{
  Order fromDb;
  using (ISession session = SessionFactory.OpenSession())
      fromDb = session.Get<Order>(_order.Id);

  // trying to access the Customer of the order, will throw exception
  // Note: at this point the session is already closed
  string name = fromDb.Customer.CompanyName;
}

"Eagerly loading with the NHibernateUtil class If you know you need have access to related objects of the order entity you can use the NHibernateUtil class to initialize the related objects (that is: to fetch them from the database)."

[Test]
public void Can_initialize_customer_of_order_with_nhibernate_util()
{
    Order fromDb;

    using (ISession session = SessionFactory.OpenSession())
    {
       fromDb = session.Get<Order>(_order.Id);

       NHibernateUtil.Initialize(fromDb.Customer);
    } 

    Assert.IsTrue(NHibernateUtil.IsInitialized(fromDb.Customer));
    Assert.IsFalse(NHibernateUtil.IsInitialized(fromDb.OrderLines));

}

Reference: http://nhibernate.info/doc/howto/various/lazy-loading-eager-loading.html