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 with Select and Fetch not working correctly #2698

Open
mm-development opened this issue Mar 9, 2021 · 6 comments
Open

QueryOver with Select and Fetch not working correctly #2698

mm-development opened this issue Mar 9, 2021 · 6 comments

Comments

@mm-development
Copy link

I have found that sometimes fetching lazy properties doesn't work and property is still lazy. I've prepared a test code to reproduce this issue:

public class EntityA
    {
        public EntityA()
        {
            Items = new HashSet<EntityB>();
        }
        public virtual Guid Id { get; set; }
        
        public virtual string Name { get; set; }
        
        public virtual ICollection<EntityB> Items { get; set; }
    }

    public class EntityB
    {
        public virtual Guid Id { get; set; }
        
        public virtual string Name { get; set; }
    }

    public class EntityAMapping:ClassMapping<EntityA>
    {
        public EntityAMapping()
        {
            Id(x => x.Id, map => map.Generator(Generators.GuidComb));

            Property(x => x.Name, map =>
            {
                map.Length(256);
                map.NotNullable(true);
            });
            
            Set(x => x.Items, v =>
            {
                v.Cascade(Cascade.None);
                v.Key(x =>
                {
                    x.Column("RiskId");
                    x.NotNullable(true);
                });
                v.Lazy(CollectionLazy.Extra);
                v.Fetch(CollectionFetchMode.Subselect);
            }, h => h.ManyToMany(m => m.Column("RiskAreaId")));
        }
    }
    
    public class EntityBMapping:ClassMapping<EntityB>
    {
        public EntityBMapping()
        {
            Id(x => x.Id, map => map.Generator(Generators.GuidComb));

            Property(x => x.Name, map =>
            {
                map.Length(256);
                map.NotNullable(true);
            });
        }
    }
    
    [TestFixture]
    public class EmptyTest:NHibernateTestFixtureBase
    {
        [Test]
        public void Test()
        {
            using (var session = _sessionFactory.OpenSession())
            {
                var entityB = new EntityB();
                entityB.Name = "Name B";
                session.Save(entityB);
            
                var entityA = new EntityA();
                entityA.Name = "Name A";
                entityA.Items.Add(entityB);
                session.Save(entityA);
                session.Flush();
            }
            
            using (var session = _sessionFactory.OpenStatelessSession())
            {
                var obj=session.QueryOver<EntityA>().SingleOrDefault();
                Assert.IsFalse(NHibernateUtil.IsInitialized(obj.Items));
                
                obj=session.QueryOver<EntityA>().Fetch(SelectMode.Fetch,x=>x.Items).SingleOrDefault();
                Assert.IsTrue(NHibernateUtil.IsInitialized(obj.Items));
                
                obj=session.QueryOver<EntityA>().Fetch(SelectMode.Fetch,x=>x.Items)
                    .Select(Projections.RootEntity())
                    .SingleOrDefault();
                Assert.IsTrue(NHibernateUtil.IsInitialized(obj.Items)); //exception. Items not fetched!
                
                obj=session.QueryOver<EntityA>()
                    .Select(Projections.RootEntity().SetFetchLazyProperties())
                    .SingleOrDefault();
                Assert.IsTrue(NHibernateUtil.IsInitialized(obj.Items)); //exception. Items not fetched!
                
                obj=session.QueryOver<EntityA>().Fetch(SelectMode.Fetch,x=>x.Items)
                    .Select(Projections.RootEntity().SetFetchLazyProperties())
                    .SingleOrDefault();
                Assert.IsTrue(NHibernateUtil.IsInitialized(obj.Items)); //exception. Items not fetched!
            }
        }
    }

In this example if we have a QueryOver with a Select, then there is no way to eager fetch properties. Only way to force Items collection to be eager loaded is to change a mapping and set NoLazy instead of Extra.

If you need more info, please let me know

@bahusoid
Copy link
Member

bahusoid commented Mar 9, 2021

It's by design - projections completely overwrite SELECT statement. So all fetch requests won't be included in SELECT.

Use the following query to fetch root entity with lazy properties + Items collection:

session.QueryOver<EntityA>()
                    .Fetch(SelectMode.FetchLazyProperties, x => x)
                    .Fetch(SelectMode.Fetch, x => x.Items)
                    .TransformUsing(Transformers.DistinctRootEntity)
                    .SingleOrDefault();

@mm-development
Copy link
Author

I can understand that this is by design but in this case, why eager loading from mapping works in Select? For me this is not very consistent behaviour. I would rather prefer opposite approach: if we overwrite Select that eager loading from mapping doesn't work (by design) but if you force eager in your query by Fetch() method that this fetch will work.
And about your query. Unfortunately this not helping me because my real query is much more complex. And I have to overwrite Select

@bahusoid
Copy link
Member

bahusoid commented Mar 10, 2021

why eager loading from mapping works in Select

Are you sure it's loaded in single query? I think your collection is loaded in separate query in this case.

Also just in case SetFetchLazyProperties and SelectMode.FetchLazyProperties, is about lazy scalar properties. References and collections are not fetched by it. I see no lazy scalar properties in your mappings.

@mm-development
Copy link
Author

Are you sure it's loaded in single query? I think your collection is loaded in separate query in this case.

All fetches are loaded in separate SQL queries (query per object type). But from NHibernate user point of view this is done automatically and in one NHibernate query.

I see no lazy properties in your mappings.

As I mentioned, my test code is simple to show the problem I've encountered. In my real query I have many references and collections I need to fetch. To illustrate my real case: imaging that in my class Entity A I have a few collections like Items, my EntityB has also collection like Items and a few references and so on.
Now I'm trying to create a Select part and include all required objects and probably this is doable but I would be nice to use NHibnerate mechanisms to fetch additional data specified by Fetch().

@bahusoid
Copy link
Member

why eager loading from mapping works in Select

Then answer is simple - it has nothing to do with Select. Mapping eager loading works for entity no matter how it's loaded via session.Get or via query with/without Select.

Fetches for projections are not supported.

@mm-development
Copy link
Author

Ok, thanks for the answer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants