nhibernatequeryovernhibernate-projections

Selecting referenced entity instead of root entity in NHibernate, ordered by usage count


I have the following two object models:

public class Product
{
    public int IdProduct;
    public Category IdCategory;
    public string Name;
    public bool Available;
}

public class Category
{
    public int IdCategory;
    public string Name;
}

I want a list of all categories, ordered with the most used categories on top of the list. I came up with the following NHibernate query:

Product productAlias = null;
Category categoryAlias = null;
Category categoryAliasOutput = null;

session.QueryOver<Product>(() => productAlias)
    .JoinAlias(p => p.Category, () => categoryAlias, JoinType.RightOuterJoin)
    .Select(Projections.ProjectionList()
        .Add(Projections.Group(() => categoryAlias.IdCategory).WithAlias(() => categoryAliasOutput.IdCategory))
        .Add(Projections.Group(() => categoryAlias.Name).WithAlias(() => categoryAliasOutput.Name))
        .Add(Projections.Count(() => productAlias.IdCategory.IdCategory)))
    .OrderBy(Projections.Count(() => productAlias.IdCategory.IdCategory)).Desc
    .ThenBy(Projections.Property(() => categoryAlias.Name)).Asc
    .TransformUsing(Transformers.AliasToBean<Category>())
    .List<Category>();

This works, but I am looking for a way to simplify the code as it looks kind of ugly. Also this is a simplified example. In my case I am dealing with objects with a lot more properties which all have to be added to the ProjectionList.

I cannot use 'Transformers.RootEntity' since the root entity is of type 'Product' and the result must be a list of type 'Category'.


Solution

  • Since NHibernate 5.1+ you can use Entity projection to select referenced entities. But grouping is not supported for entity projections (in most cases grouping can be replaced by subquery):

    Product productAlias = null;
    Category categoryAlias = null;
    
    session.QueryOver<Product>(() => productAlias)
        .JoinAlias(p => p.IdCategory, () => categoryAlias, JoinType.RightOuterJoin)
        .Select(p => categoryAlias.AsEntity())
        .OrderBy(
            Projections.SubQuery(
                QueryOver.Of<Product>()
                .Where(p => p.IdCategory == categoryAlias)
                .Select(Projections.RowCount()))).Desc
        .ThenBy(Projections.Property(() => categoryAlias.Name)).Asc
        .List<Category>();
    

    Also it seems referenced entity is not required in your case and query can be simiplified to something like:

    Category categoryAlias = null;
    var catergories = session.QueryOver(() => categoryAlias)
        .OrderBy(
            Projections.SubQuery(
                QueryOver.Of<Product>()
                .Where(p => p.IdCategory == categoryAlias)
                .Select(Projections.RowCount())))
        .Desc
        .List();