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

NH-3047 - Lazy=no-proxy ignores join fetch #1267

Closed
nhibernate-bot opened this issue Oct 12, 2017 · 3 comments · Fixed by #3347
Closed

NH-3047 - Lazy=no-proxy ignores join fetch #1267

nhibernate-bot opened this issue Oct 12, 2017 · 3 comments · Fixed by #3347

Comments

@nhibernate-bot
Copy link
Collaborator

nhibernate-bot commented Oct 12, 2017

Boris Drajer created an issue — 4th February 2012, 10:55:04:

I found two issues but I put them both here because I think they are related.

  1. Setting the value of a property mapped as lazy="no-proxy" while the session is disconnected triggers a LazyInitializationException. (Also, setting it while the session is open doesn't throw the exception but a subsequent getting of the property with disconnected session does.)

  2. Doing join fetch on a lazy="no-proxy" property doesn't seem to have any effect, either in HQL, Criteria API or QueryOver

It seems to me that in both cases the property does get set, but the field interceptor ignores this and throws the exception upon GET although it shouldn't.

All of this was done on .Net 3.5 SP1, SQL Server 2005 and 2008.

Here's a couple of unit tests to demonstrate this - add them to NHibernate.Test/GhostProperty/GhostPropertyFixture.cs:

[Test]
public void AcceptPropertySetWithTransientObject()
{
    Order order = null;

    using (ISession s = OpenSession())
    {
        order = s.Get<Order>(1);
    }

    var newPayment = new WireTransfer();

    Assert.DoesNotThrow(() => order.Payment = newPayment);
    
    Assert.AreSame(order.Payment, newPayment);
}

[Test]
public void WillFetchJoinInSingleHqlQuery()
{
    Order order = null;

    using (ISession s = OpenSession())
    {
        order = s.CreateQuery("from Order o left join fetch o.Payment where o.Id = 1").List<Order>()[0];
    }

    Assert.DoesNotThrow(() => { var x = order.Payment; });
}

[Test]
public void WillFetchJoinInAdditionalHqlQuery()
{
    Order order = null;

    // load the order...
    ISession s = OpenSession();
    order = s.CreateQuery("from Order o where o.Id = 1").List<Order>()[0];
    s.Disconnect();

    Assert.Throws(typeof(LazyInitializationException), () => { var y = order.Payment; });

    s.Reconnect();
    // ... then join-fetch the related payment
    s.CreateQuery("from Order o left join fetch o.Payment where o.Id = 1").List<Order>();
    s.Close();

    Assert.DoesNotThrow(() => { var x = order.Payment; });
}

[Test]
public void WillFetchJoinWithCriteria()
{
    Order order = null;

    // load the order...
    ISession s = OpenSession();

    var query = s.CreateCriteria<Order>();
    query.Add(NHibernate.Criterion.Expression.Eq("Id", 1));
    order = query.List<Order>()[0];
    s.Disconnect();

    Assert.Throws(typeof(LazyInitializationException), () => { var y = order.Payment; });

    s.Reconnect();

    // ... then join-fetch the related payment
    var query2 = s.CreateCriteria<Order>();
    query2.Add(NHibernate.Criterion.Expression.Eq("Id", 1));
    query2.SetFetchMode("Payment", FetchMode.Eager);
    query2.List();
    s.Close();

    Assert.DoesNotThrow(() => { var x = order.Payment; });
}

Now, I did some research on the matter and have a couple of ideas why this is... Note that I am by no means an expert on NHibernate internal logic, so take this with a big grain of salt (two grains, even :)).

It seems to me that the AbstractFieldInterceptor class (located in NHibernate/Intercept) isn't no-proxy-aware (or not enough), it looks as though it treats the issues I mentioned in a "lazy=proxy" fashion. For one, it doesn't discern between setting and getting properties: with lazy=proxy this makes sense because accessing a property on a proxy triggers the initialization of the proxy, and it doesn't matter if you called GET or SET. With lazy=no-proxy, accessing a property initializes the property and not its owner, and I think setting the property should be allowed at all times. The interceptor should remember that the property was set and treat it as initialized.

I managed to get the first unit test working by adding a new input parameter to the AbstractFieldInterceptor.Intercept method: this parameter states whether it is GET or SET that we're intercepting. Since the method is called only from one class, this isn't too hard or too risky, but I'm unsure how this affects Hibernate compatibility... At the top of the method, I added logic to treat each SET access on a no-proxy property as an initialization:

public object Intercept(object target, string fieldName, object value, bool isSetAccessor)
{
    if (this.unwrapProxyFieldNames.Contains(fieldName))
    {
        if (isSetAccessor)
        {
            if (!loadedUnwrapProxyFieldNames.Contains(fieldName))
            {
                loadedUnwrapProxyFieldNames.Add(fieldName);
            }
            return InvokeImplementation;
        }
        // get accessor
        else if (loadedUnwrapProxyFieldNames.Contains(fieldName))
        {
            return InvokeImplementation;
        }
    }
    
    <... the rest of the method ...>

The remaining problem here is that the interceptor is created after the properties are initialized on an object, so it cannot intercept that SET call and join fetch still doesn't work. I worked around this issue by adding !value.IsProxy() to some of the conditions above, but I'm sure it's not a regular solution. If someone can give me a hint towards a proper way to resolve this stuff, I'm willing to give it a try - although I suspect for someone "in the know" it would be easier to implement the solution than to explain it :).

@fredericDelaporte
Copy link
Member

The first point, 'Setting the value of a property mapped as lazy="no-proxy" while the session is disconnected triggers a LazyInitializationException', is obsoleted by #1943.

@fredericDelaporte fredericDelaporte changed the title NH-3047 - Lazy=no-proxy doesn't allow setting the property value and ignores join fetch NH-3047 - Lazy=no-proxy ignores join fetch Dec 25, 2018
@fredericDelaporte
Copy link
Member

The second point has been fixed by #1947.

@maca88
Copy link
Contributor

maca88 commented Jan 13, 2019

#1947 fixed only the case when an entity is not yet loaded which means that the last two tests (WillFetchJoinWithCriteria and WillFetchJoinInAdditionalHqlQuery) would still fail. In order to support them we would need to add an additional logic to Loader.InstanceAlreadyLoaded, similar what was done for #1922.

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