Search code examples
c#nhibernatejoinqueryover

NHibernate QueryOver - Select one field of sub childrens of join tables


It's hard to find how to resolve my issue so I post it here.

public class A
{
    public int IdA { get; set; }
    public List<AB> ABs { get; set; }
}

public class AB
{
    public A ObjectA { get; set; }
    public B ObjectB { get; set; }
}

public class B
{
    public int IdB { get; set; }
    public string Name { get; set; }
}

I would like find a way with QueryOver, to retrieve a list of this DTO

public class DTO
{
   public int IdA { get; set; }
   public List<string> Names { get; set; }
}

As you can see, I wanted to have all "Name" properties of the object B foreach object B found in my ABs property.

Can you please help me if you know how to do that ?


Solution

  • There are a few ways to do this, but none that I know of in just QueryOver. You can think of a QueryOver query as translating almost directly to SQL. If you think of it that way, it's impossible to populate a List property in one step (you couldn't write a SQL query that resulted in an ID and a "list" of items, at least not without using some kind of aggregate).

    With that in mind I think it's best here to define a new type of intermediate DTO.

    public class FlattenedDTO 
    {
        public int IdA { get; set; }
        public string Name { get; set; }
    }
    

    Then you'd write your query, projecting out to FlattenedDTO. After we select out a list of FlattenedDTOs, we can group them into a new list of DTOs:

    B bAlias = null;
    FlattenedDTO result = null;
    
    session.QueryOver<A>()
        .JoinQueryOver<AB>(a => a.ABs)
        .JoinQueryOver(ab => ab.B, () => bAlias)
        .SelectList(list => list
            .Select(a => a.Id).WithAlias(() => result.IdA)
            .Select(() => bAlias.Name).WithAlias(() => result.Name))
        .TransformUsing(Transformers.AliasToBean<FlattenedDTO>())
        .List<FlattenedDTO>()
        // At this point the query has been run and we just need to group the results
        .GroupBy(dto => dto.IdA, dto => dto.Name)
        .Select(grp => new DTO { IdA = grp.Key, Names = grp.ToList() });
    

    This will end up issuing one query:

    SELECT this_.IdA        as y0_,
           balias2_.Name    as y1_
    FROM   [A] this_
           inner join [AB] ab1_
             on this_.Id = ab1_.IdA
           inner join [B] balias2_
             on ab1_.IdB = balias2_.Id
    

    And grouping the results in memory. There are a few other ways to do this, so let me know if this does not suit your needs.