Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

QueryOver.Future/FutureValue does not load NoLazy children like QueryOver.SingleOrDefault()/List() #3565

Open
VoNhatSinh opened this issue Jun 18, 2024 · 2 comments

Comments

@VoNhatSinh
Copy link

VoNhatSinh commented Jun 18, 2024

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();
        }
    }
@VoNhatSinh
Copy link
Author

VoNhatSinh commented Jul 15, 2024

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 _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().

@VoNhatSinh
Copy link
Author

I think we can improve FutureValue to correct that, it's not simple to do so I can not fix it by myself.

@VoNhatSinh VoNhatSinh changed the title Future/FutureValue does not load NoLazy children QueryOver.Future/FutureValue does not load NoLazy children like QueryOver.SingleOrDefault()/List() Jul 15, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant