Problems with HasMany, Lazy loading and session scopes
I have a console application that uses NHibernate and ActiveRecord.
I am using the following detached criteria:
DetachedCriteria criteria = DetachedCriteria.For<ServiceOrder>();
criteria.Add(Restrictions.Lt("End", DateTime.Today.AddDays(3)));
criteria.Add(new Disjunction()
.Add(Restrictions.IsNull("StopReason"))
.Add(Restrictions.Eq("StopReason", ServiceStopReason.Worthiness)));
criteria.Add(new Disjunction()
.Add(Restrictions.IsNull("StopDate"))
.Add(Restrictions.EqProperty("StopDate", "End")));
And I am invoking the fetching using ActiveRecordMediator's FindAll() method.
In my model I have this property:[HasMany(Lazy = true)]
public virtual ISet<ServiceOrder> ServiceOrders
{
get { return serviceOrders; }
set { serviceOrders = value; }
}
Which I'm trying to access to in the following Linq query:
from serviceOrder in serviceOrderDataService.GetServiceOr开发者_如何学编程dersWithEndOfEntitlement()
let accountGetService = serviceOrder.AccountGetService
where accountGetService != null
let serviceOrders = accountGetService.ServiceOrders
where serviceOrders != null && serviceOrders.Count != 0
let isFutureServiceOrder = (from accountGetServiceServiceOrder in serviceOrders
where serviceOrder.Start.HasValue
&& accountGetServiceServiceOrder.End.HasValue
&& serviceOrder.Start > accountGetServiceServiceOrder.End
select accountGetServiceServiceOrder).Any()
where !isFutureServiceOrder
select serviceOrder;
But I am getting the following exception:
Initializing[Danel.Nursing.Model.AccountService#61786367-e8da-4929-b91b-a7497cf7db10]-failed to lazily initialize a collection of role: Danel.Nursing.Model.AccountService.ServiceOrders, no session or session was closed
Does this makes any sense? How do I solve it?
When using lazy loading, place all code that accesses lazily loaded relations within a single SessionScope. Otherwise you'll get exceptions (unless you manually reattach entities to the new session which is generally troublesome).
Another option is to eagerly fetch everything you need (as opposed to using lazy loading).
Here's a copy of a post I made to the NHUsers group (Link to the discussion, which discusses several other approaches as well)
I know you have a console app, and my app is WinForms, but the same approach should work for you as well. It's not ideal, but so far, it's working for me.
======================================================
Session management when using NHibernate, lazy loading, and WinForms seems to be something many people struggle with - I know I did!
Adding WinForms databinding to the mix is an additional complication in my project, which has a single main form centered around a third party grid control. The grid is databound to my object model, via the IBindingList interface.
After quite a bit of struggle and hair pulling, the approach that seems to be working is to have a MainSession for the main form that is kept open most of the time. This means that whenever the users navigate through the data with the grid, there's an open session to handle the lazy loads of data from the database.
For other operations (updates, deletes, etc), I disconnect the main session, create a new, throwaway session just for that operation, and then reconnect the main session.
This works, and if it helps somebody else, great. However, I don't fully trust it, and it seems every time I add a new capability (delete was the most recent one), it breaks and has to be tweaked again. If anyone has a better suggestion, I'd love to hear it.
Conversely, if it's OK, I'd like to know that too - I'll sleep better from now on.
Anyway, here's some code excerpted from my project to illustrate the approach.
/// <summary>
/// NHibernate session factory used by the application
/// </summary>
private static ISessionFactory _sessionFactory;
/// <summary>
/// NHibernate session used by MainForm. Kept open most of the time to facilitate
/// lazy loading of data. Disconnected and Reconnected when app writes
/// new data to database
/// </summary>
private static ISession _mainSession;
/// <summary>
/// Get the main database session, creating it if necessary
/// </summary>
/// <returns></returns>
private static ISession GetMainSession()
{
if (_sessionFactory == null)
{
CreateSqliteSessionFactory();
}
if (_mainSession == null)
{
_mainSession = _sessionFactory.OpenSession();
}
return _mainSession;
}
private static void DisconnectMainSession()
{
GetMainSession().Disconnect();
}
private static void ReconnectMainSession()
{
GetMainSession().Reconnect();
}
/// <summary>
/// Save measurement to DB. Does not check for duplicates
/// </summary>
/// <param name="measurement"></param>
public static void SaveMeasurement(object measurement)
{
DisconnectMainSession();
using (var session = _sessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
session.Save(measurement);
transaction.Commit();
session.Close();
}
catch (Exception e)
{
if (transaction != null) transaction.Rollback();
throw new ApplicationException(
"\r\n SaveMeasurement exception:\r\n\r\n", e);
}
finally
{
ReconnectMainSession();
}
}
}
精彩评论