Skip to content
New issue

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

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

Already on GitHub? Sign in 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant