QueryOver.Future/FutureValue does not load NoLazy children like QueryOver.SingleOrDefault()/List()
Please see the attached file for a reproducible project (also includes database script, and unit test) FutureDoesNotLoadNoLazyChildNHibernate.zip
Steps to reproduce:
In the context of my example project, I have a Post table and a Comment table. A Post has a one-to-many relationship with Comment. The PostMap is configured to use CollectionLazy.NoLazy for Comments.
I wrote four unit tests in the attached project to demonstrate an issue. I use Future/FutureValue to get Posts/Post, and I expected the related child Comments to be loaded and usable outside the session. However, it throws a LazyInitializationException.
I then tried another approach using QueryOver.List (which returns a list of entities) or QueryOver.SingleOrDefault, and in this case, I can use the Comments as expected.
Please see my comment below for the reason and discuss that. Thank you a lot.
public class TestLoadingNoLazy
{
private ISessionFactory _sessionFactory;
[OneTimeSetUp]
public void OneTimeSetUp()
{
var config = new Configuration();
config = config.DataBaseIntegration(db =>
{
db.ConnectionString = @"Server=localhost;Database=NoLazyNHibernate;Integrated Security=true;";
db.Dialect<MsSql2012Dialect>();
db.Driver<Sql2008ClientDriver>();
});
var mapper = new ConventionModelMapper();
mapper.AddMapping<PostMap>();
mapper.AddMapping<CommentMap>();
var hbmMappings = mapper.CompileMappingForAllExplicitlyAddedEntities();
config.AddMapping(hbmMappings);
_sessionFactory = config.BuildSessionFactory();
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
_sessionFactory.Close();
}
[Test]
public void LoadListPosts__UsingFuture__ThrowLazyInitializationException()
{
// Arrange
IList<Post> postFuture;
using (var session = _sessionFactory.OpenSession())
{
postFuture = session.QueryOver<Post>()
.Where(x => x.Title == "Title")
.Future().ToList();
}
// Action
Func<string> action = () => postFuture[0].Comments[0].Content;
// Assert
action.Should().ThrowExactly<LazyInitializationException>();
}
[Test]
public void LoadListPosts__UsingList__LoadCommentsSuccessfully()
{
// Arrange
IList<Post> postList;
using (var session = _sessionFactory.OpenSession())
{
postList = session.QueryOver<Post>()
.Where(x => x.Title == "Title")
.List();
}
// Action
Func<string> action = () => postList[0].Comments[0].Content;
// Assert
action.Should().NotThrow();
}
[Test]
public void LoadOnePost__UsingFutureValue__ThrowLazyInitializationException()
{
// Arrange
Post postFutureValue;
using (var session = _sessionFactory.OpenSession())
{
postFutureValue = session.QueryOver<Post>()
.Where(x => x.Title == "Title")
.FutureValue().Value;
}
// Action
Func<string> action = () => postFutureValue.Comments[0].Content;
// Assert
action.Should().ThrowExactly<LazyInitializationException>();
}
[Test]
public void LoadOnePost__UsingSingleOrDefault__LoadCommentsSuccessfully()
{
// Arrange
Post postFutureValue;
using (var session = _sessionFactory.OpenSession())
{
postFutureValue = session.QueryOver<Post>()
.Where(x => x.Title == "Title")
.SingleOrDefault();
}
// Action
Func<string> action = () => postFutureValue.Comments[0].Content;
// Assert
action.Should().NotThrow();
}
}
I found where the Session handles NoLazy properties when executing a query from QueyOver.List(). It is in the Loader.cs#DoQueryAndInitializeNonLazyCollections() persistenceContext.InitializeNonLazyCollections(); And for QueryOver.FutureValue(), the process is totally different. We basically build a list of IQueryBatchItem from ICriteria. Then add to QueryBatch. Whenever we try to get a Value, the QueryBatch will loop through list List<IQueryBatchItem> _queries, and use a different approach to load all itemsQueryBatch.Execute() => ExecuteBatched(). In conclusion, FutureValue uses a different approach to load data without loading NoLazyCollection directly as does with List().
I think we can improve FutureValue to correct that, it's not simple to do so I can not fix it by myself.