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

Properties with lazy="no-proxy" get eager loaded under certain conditions #1795

Open
ketzak opened this issue Jul 13, 2018 · 2 comments
Open

Comments

@ketzak
Copy link

ketzak commented Jul 13, 2018

Hello,

I'm currently working on a project in the following environment:

  • NHibernate version: 5.1.3.
  • NET version : .NET Framework 4.6.1.
  • Database : SQL Server 2012.

I have an XML mapping file that looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping …>
  <joined-subclass name="A" table="A" extends="A_Base">
    <key column="Id" />
    <many-to-one name="B" column="B_Id" class="B" lazy="no-proxy" />
  </joined-subclass>
</hibernate-mapping>

The B class is a base class from which several other classes derive, so I’m using the "no-proxy" attribute value. While using the SQL Profiler, I’ve noticed that the following code:

session.Query<A>().Where(x => x.B.Code == "TX").ToList();

Will generate a first SQL query to fetch the corresponding A instances, but will then make many queries to fetch B instances. Upon further inspection, I found that:

  • The problem does not appear if I use the default lazy-loading behaviour.
  • Not all B instances are fetched: only those that appear more than once in the collection returned by the LINQ query are eager loaded.

I looked in the NHibernate source and here is what seems to happen:
For an instance of B with the Id "123" that appears twice in the collection returned by the LINQ query, a first call is made to NHibernate.Event.Default.DefaultLoadEventListener.ProxyOrLoad. The method contains the following code:

// look for a proxy
object proxy = persistenceContext.GetProxy(keyToLoad);
if (proxy != null)
{
	return ReturnNarrowedProxy(@event, persister, keyToLoad, options, persistenceContext, proxy);
}
else
{
	if (options.IsAllowProxyCreation)
	{
		return CreateProxyIfNecessary(@event, persister, keyToLoad, options, persistenceContext);
	}
	else
	{
		// return a newly loaded object
		return Load(@event, persister, keyToLoad, options);
	}
}

Here, the proxy variable is null, so CreateProxyIfNecessary is called. However, CreateProxyIfNecessary will execute the following line of code:

persistenceContext.AddProxy(keyToLoad, (INHibernateProxy)proxy);

Which means that on the second call to ProxyOrLoad, the proxy variable is not null and ReturnNarrowedProxy is called, which contains this code:

ILazyInitializer li = castedProxy.HibernateLazyInitializer;
if (li.Unwrap)
{
	return li.GetImplementation();
}

It appears that Unwrap is set to true for properties with the lazy="no-proxy" attribute (the logic is apparently in the ClassBinder.InitLaziness(HbmLaziness? laziness, ToOne fetchable, bool defaultLazy) method).
Ultimately, the call to GetImplementation will call the following line of code:

_target = _session.ImmediateLoad(_entityName, _id);

This will then result in a SQL query to fetch the element.
Is there something that mandates that these elements have to be eager loaded when they appear more than once in a collection, or is it an incorrect behavior ?

@fredericDelaporte
Copy link
Member

I think it is a bug.

@hazzik
Copy link
Member

hazzik commented May 2, 2021

Related to #1041

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