0

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)
Becca Dee
  • 1,530
  • 1
  • 24
  • 51
  • 1
    Try changing your Linq query to `nh.Query(cc => cc.AuxiliaryClass.AuxiliaryClassId > 0);` assuming `AuxiliaryClassId` is of type `int` – Suhas Aug 21 '15 at 18:48
  • Thanks, @Suhas. I've updated my question with the result of trying that. `AuxiliaryClassId` is `Int32?`. – Becca Dee Aug 21 '15 at 19:30
  • I do not thing what you are trying to achieve with the new query is possible. But before we get that point, tell me, how would you write the SQL to achieve what you are trying to achieve without thinking of NH or LINQ for a moment? – Suhas Aug 21 '15 at 21:31
  • I'd probably do a left outer join and check for nullity. Or if that were unpalatable for some reason (thinking of NH, which might have a reason to avoid that), a sub-query. – Becca Dee Aug 24 '15 at 14:55
  • 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. – Suhas Aug 24 '15 at 15:15
  • You could do something like `SELECT CONCRETE_CLASS.PROP1 FROM CONCRETE_CLASS INNER JOIN ABSTRACT_CLASS ON CONCRETE_CLASS.ABSTRACT_CLASS_ID = ABSTRACT_CLASS.ABSTRACT_CLASS_ID WHERE (SELECT TOP(1) ABSTRACT_CLASS_ID FROM AUXILIARY_CLASS) IS /* or IS NOT */ NULL` – Becca Dee Aug 24 '15 at 15:22
  • And regarding QueryOver, we are using LINQ exclusively because we've abstracted NHibernate behind a generic data access object that either takes LINQ queries or returns `IQueryable`s. – Becca Dee Aug 24 '15 at 15:28
  • `SELECT TOP(1) ABSTRACT_CLASS_ID FROM AUXILIARY_CLASS` may have two issues. 1. It is only checking if the first `ABSTRACT_CLASS_ID` is null or not null 2. It is not joining with `CONCRETE_CLASS` to actually filter the rows from `CONCRETE_CLASS` that have a missing `AUXILIARY_CLASS` record. – Suhas Aug 24 '15 at 16:16
  • You can use LINQ syntax coupled with theta-style join and a call to `DefaultifEmpty()` to get the effect of a left-outer join – Suhas Aug 24 '15 at 16:17
  • I accidentally left out the `WHERE` clause in my example. These little boxes are hard to type code in. `SELECT CONCRETE_CLASS.PROP1 FROM CONCRETE_CLASS INNER JOIN ABSTRACT_CLASS ON CONCRETE_CLASS.ABSTRACT_CLASS_ID = ABSTRACT_CLASS.ABSTRACT_CLASS_ID WHERE (SELECT TOP(1) AUXILIARY_CLASS_ID FROM AUXILIARY_CLASS WHERE AUXILIARY_CLASS .ABSTRACT_CLASS_ID = ABSTRACT_CLASS_ID) IS /* or IS NOT */ NULL` – Becca Dee Aug 24 '15 at 18:59
  • Can you give an example of using the LINQ syntax with the theta-style join? I see that NH is generating a theta-style join and an ANSI-style join in the same query, but I'm not sure how to use LINQ and DefaultIfEmpty to take advantage of that. Thanks! – Becca Dee Aug 24 '15 at 19:00
  • Quickly googling i found this [SO question](http://stackoverflow.com/questions/3404975/left-outer-join-in-linq) that should be a good starting point. Regards to the sub-query, if you are happy with that query, you can use `ISession.CreateSQLQuery` and use your query as is – Suhas Aug 24 '15 at 20:53
  • Thanks, @Suhas. I'll give that a try. If you want to write that up as an answer I'll accept when I've tested it out. I'll probably also post something somewhere NH-specific and see what the story is, since that seems like something that should be relatively easy. I may also try a formula for the column instead, which I think will make LINQ queries more intuitive for the rest of my team. – Becca Dee Aug 24 '15 at 21:18
  • Transferred my comments in a tailored format as answer for you to accept ;) – Suhas Aug 24 '15 at 21:28

1 Answers1

0

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
Community
  • 1
  • 1
Suhas
  • 7,919
  • 5
  • 34
  • 54