NH4 LINQ Query using wrong column for one-to-one

183 views Asked by At

I have one-to-one relationship between two tables, and I want to be able to create a LINQ query that will return the "parent" tables where there is something in the child table. The problem is that the query NH is generating is checking to see if the parent table's ID is not null (and it never is) rather than joining to the child table. This is regardless of whether I use lazy or non-lazy loading. I'm using custom automap conventions with an override, but here's the HBM XML that gets generated:

Mapping for Abstract Class, Concrete Class that is Parent

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" name="AbstractClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" table="ABSTRACT_CLASS">
    <id name="AbstractClassId" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ABSTRACT_CLASS_ID" />
      <generator class="identity" />
    </id>
    <joined-subclass name="ConcreteClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" table="CONCRETE_CLASS">
      <key>
        <column name="ABSTRACT_CLASS_ID" />
      </key>
      <one-to-one cascade="none" class="AuxiliaryClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" name="AuxiliaryClass" property-ref="Foo" />
    </joined-subclass>
  </class>
</hibernate-mapping>

Mapping for Child Table

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" name="AuxiliaryClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" table="AUXILIARY_CLASS">
    <id name="AuxiliaryClassiD" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="AUXILIARY_CLASS_ID" />
      <generator class="identity" />
    </id>
    <many-to-one class="ConcreteClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" name="Foo">
      <column name="FOO_ABSTRACT_CLASS_ID" />
    </many-to-one>
  </class>
</hibernate-mapping>

Class Definitions

public abstract class AbstractClass
{
    public virtual Int32? AbstractClassId { get; set; }
}

public class ConcreteClass : AbstractClass
{
    public virtual AuxiliaryClass AuxiliaryClass { get; set; }
}

public class AuxiliaryClass
{
    public virtual Int32? AuxiliaryClassId { get; set; }

    public virtual ConcreteClass Foo { get; set; }
}

The LINQ query that isn't work is:

nh.Query<ConcreteClass>().Where(cc => cc.AuxiliaryClass != null);

The query that's being generated is:

select
    concretecl0_.CONCRETE_CLASS_ID as CONCRETE1_0_
from
    CONCRETE_CLASS concretecl0_
inner join
    ABSTRACT_CLASS concretecl0_1_
on
    concretecl0_.ABSTRACT_CLASS_ID=concretecl0_1_.ABSTRACT_CLASS_ID
where
    concretecl0_.ABSTRACT_CLASS_ID is not null

If I turn off lazy loading, the joins out to the auxiliary table but still compares the concrete class's table's ID to null.

edit

Per @Suhas's suggestion:

Try changing your Linq query to nh.Query(cc => cc.AuxiliaryClass.AuxiliaryClassId > 0); assuming AuxiliaryClassId is of type int

I actually did cc => cc.AuxiliaryClass.AuxiliaryClassId != null, which worked, getting me this query:

select
    concretecl0_.ABSTRACT_CLASS_ID as concretecl0_1_0_
from
    CONCRETE_CLASS concretecl0_
inner join
    ABSTRACT_CLASS concretecl0_1_
on
    concretecl0_.concretecl0_=concretecl0_1_.concretecl0_
  , AUXILIARY_CLASS auxiliaryc1_
where
    concretecl0_.ABSTRACT_CLASS_ID=auxiliaryc1_.FOO_ABSTRACT_CLASS_ID
and (auxiliaryc1_.AUXILIARY_CLASS_ID is not null)

However, when I tried the inverse case, cc => cc.AuxiliaryClass.AuxiliaryClassId == null, I got a non-working query:

select
    concretecl0_.ABSTRACT_CLASS_ID as concretecl0_1_0_
from
    CONCRETE_CLASS concretecl0_
inner join
    ABSTRACT_CLASS concretecl0_1_
on
    concretecl0_.concretecl0_=concretecl0_1_.concretecl0_
  , AUXILIARY_CLASS auxiliaryc1_
where
    concretecl0_.ABSTRACT_CLASS_ID=auxiliaryc1_.FOO_ABSTRACT_CLASS_ID
and (auxiliaryc1_.AUXILIARY_CLASS_ID is null)
1

There are 1 answers

0
Suhas On

Just listing down my comments (slightly tailored to an answer) from the original questions as those seems to have helped the author of the question

  1. Try changing your Linq query to nh.Query<ConcreteClass>(cc => cc.AuxiliaryClass.AuxiliaryClassId > 0); assuming AuxiliaryClassId is of type int
  2. The above would not work when you want to fetch records present in ConcreteClass but not present in AuxiliaryClass. For that you might want to use left outer join. If you do left outer join, then you can do it via QueryOver (or Linq in a not so direct way) and then do the check for nullity in your code. Depending on the size of the dataset you load, this may not be a good thing to do in production. I am not sure how would do subquery to achieve what you are trying to achieve
  3. if LINQ is the only option then you can use DefaultIfEmpty method to get left outer joins in LINQ. This SO question should be a good starting point.
  4. Lastly a plain SQL could be most effective in this situation. If you want to go down that route then you can use ISession.CreateSQLQuery and use your SQL query as is