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

Fetching lazy loaded component causes n + 1 query when querying a subclass abstraction #3289

Closed
nfplee opened this issue Apr 20, 2023 · 5 comments

Comments

@nfplee
Copy link
Contributor

nfplee commented Apr 20, 2023

I've found that when doing a LINQ query and fetching a component it seems to work fine when querying a class, but if I try to query the interface the class implements then the fetch doesn't appear to work.

For example say I have the following:

public class Entity : IEntity
{
    public virtual int Id { get; set; 
    public virtual string Name { get; set; }
    public virtual Component Component { get; set; }
}

public class SubEntity : Entity, ISubEntity
{
    public virtual bool SomeProperty { get; set; }
}

public class Component
{
    public virtual string Field { get; set; }
}

public interface IEntity
{
    int Id { get; set; }
    string Name { get; set; }
    Component Component { get; set; }
}

public interface ISubEntity : IEntity
{
    public bool SomeProperty { get; set; }
}

Which is mapped like so:

protected override HbmMapping GetMappings()
{
    var mapper = new ModelMapper();
    mapper.Class<Entity>(rc =>
    {
        rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
        rc.Property(x => x.Name);
        rc.Component(x => x.Component);
    });
    mapper.JoinedSubclass<SubEntity>(rc =>
    {
        rc.EntityName(typeof(ISubEntity).FullName);
        rc.Key(k => k.Column("Id"));
        rc.Property(x => x.SomeProperty);
    });
    mapper.Component<Component>(rc =>
    {
        rc.Property(x => x.Field);
        rc.Lazy(true);
    });

    return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

Now if I was to say the following, the database would be hit once:

var result = session.Query<SubEntity>()
    .Fetch(e => e.Component)
    .ToList();

// Make sure the component field is executed.
foreach (var entity in result)
{
    var foo = entity.Component?.Field;
}

However if I say the following, the database would be hit for each entity in the result:

var result = session.Query<ISubEntity>()
    .Fetch(e => e.Component)
    .ToList();

// Make sure the component field is executed.
foreach (var entity in result)
{
    var foo = entity.Component?.Field;
}

Edit: See below for a test case.

@nfplee nfplee changed the title Fetching lazy loaded component causes n + 1 query when querying an abstraction Fetching lazy loaded component causes n + 1 query when querying a subclass abstraction Apr 20, 2023
@nfplee
Copy link
Contributor Author

nfplee commented May 9, 2023

@bahusoid any chance you could have a look into this? I've tried myself by cloning the code and downloading the appropriate SDK. I've managed to replicate the issue but having stepped through the code I see in the Loader.cs (at which point the SQL has been generated) it includes the select for the lazy loaded property when querying over SubEntity (but not ISubEntity), however I can't see which file(s) convert the LINQ expression to the SQL.

Also I'm trying to produce a test case and while I can step over the code and see in the SQL profiler the issue, I can't see how to get a count of the total queries which are executed (even after turning on the statistics).

I'd appreciate any help as this is the first time I've dived into the NHibernate code and it's a bit alien to me.

It's also worth pointing out that if I use FetchLazyProperties then this problem doesn't happen.

@bahusoid
Copy link
Member

bahusoid commented May 9, 2023

any chance you could have a look into this?

Yeah, when I have time (not this month)

@nfplee
Copy link
Contributor Author

nfplee commented May 9, 2023

After debugging I noticed the main different is the following line:

https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs#L53

Returns null when resultOperator.RelationMember.ReflectedType is IEntity.

However, it returns the appropriate metadata when the resultOperator.RelationMember.ReflectedType is Entity.

@nfplee
Copy link
Contributor Author

nfplee commented May 10, 2023

Here's a test case:

GH3289.zip

@bahusoid
Copy link
Member

Fixed by #3320

@fredericDelaporte fredericDelaporte added this to the 5.4.3 milestone Jun 20, 2023
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