3

It is possible to order by class type in HQL and ICriteria queries using the special keyword class.

criteria.AddOrder("s.class", true);

Can this keyword be used in an IQueryable OrderBy? This seems to only except propery expressions and .GetType() is not supported.

Skippy
  • 145
  • 2
  • 9
  • It appears HQL `class` special property of base entities is deprecated on Hibernate side, in favor of HQL function `type(entityAlias)`. See this [doc info notice](http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#hql-entity-type-exp). But I have not found any information on what is NHibernate stance on that subject. – Frédéric Feb 19 '16 at 22:26

2 Answers2

4

Quick&Dirty suggestion

A dirty hack may work. In my experience, linq2NH does translate queryables to HQL without much checks (see this question for an example case in its 'trick' sample code). So you may add a dummy and not mapped public virtual string @class { get; set; } property on your base entity then order by it.

session.Query<YourBaseEntityType>().OrderBy(e => e.@class);

Test it, it is likely it would be translated 'bluntly' to a working HQL query. Of course, it is ugly, may break at any NH version upgrade, ...

(The @ is required because class is a reserved word in C#. Prefixing with @ tells the compiler it is just a name, not the class keyword.)

Cleaner suggestion

A cleaner way would be to extend linq2NH for supporting a Class() method on your entities. (Blog post found on this answer to another question. Edit: here is another interesting implementation example on SO, and here is a list of links which provides much more information on extensibility.)

Adapting linked above 'extend' blog post to your case, it should be something like following steps.

First, you need an extension method usable on your entities. Something like:

public static class YourLinqExtensions
{
    public static string Class(this YourEntityBaseClass source)
    { 
        // .Net runtime implementation just in case, useless in linq2nhib case
        return source == null ? null : source.GetType().Name;
    }
}

Then, implements its HQL translation:

public class ClassGenerator : NHibernate.Linq.Functions.BaseHqlGeneratorForMethod
{
    public ClassGenerator()
    {
        SupportedMethods = new[]
        {
            NHibernate.Linq.ReflectionHelper.GetMethod(
                () => YourLinqExtensions.Class(null))
        };
    }

    public override NHibernate.Hql.Ast.HqlTreeNode BuildHql(MethodInfo method, 
        Expression targetObject,
        ReadOnlyCollection<Expression> arguments,
        NHibernate.Hql.Ast.HqlTreeBuilder treeBuilder,
        NHibernate.Linq.Visitors.IHqlExpressionVisitor visitor)
    {
        return treeBuilder.Dot(
            // using NHibernate.Hql.Ast; required for .AsExpression() to be found
            // at compile time.
            visitor.Visit(arguments[0]).AsExpression(),
            // Below 'Class' method is not 'YourLinqExtensions.Class' extension
            // method. It is natively available on HqlTreeBuilder.
            treeBuilder.Class());
    }
}

Extend the default linq2NH registry with your generator:

public class YourLinqToHqlGeneratorsRegistry:
    NHibernate.Linq.Functions.DefaultLinqToHqlGeneratorsRegistry
{
    public YourLinqToHqlGeneratorsRegistry()
    {
        // using NHibernate.Linq.Functions; required for .Merge() to be found
        // at compile time.
        this.Merge(new ClassGenerator());
    }
}

And configure NH to use your new registry. With hibernate.cfg.xml, it should be something like adding following property node under session-factory node:

<property name="linqtohql.generatorsregistry">YourNameSpace.YourLinqToHqlGeneratorsRegistry, YourAssemblyName</property>

Usage:

session.Query<YourBaseEntityType>().OrderBy(e => e.Class());

(With the way I have elaborated all of this, it should at least compile, but it is fully untested. Edit: now it is tested.)

Frédéric
  • 9,364
  • 3
  • 62
  • 112
  • Would be nice to be sure that it is tested and working.. but as a concept you did a great job here... – Radim Köhler Feb 19 '16 at 09:55
  • The dirty suggestion is working. I have not been able to get the cleaner suggestion working. It is generating a NHibernate.Hql.Ast.ANTLR.QuerySyntaxException "A recognition error occurred." I am using NHibernate 4.0.0.4000 – Skippy Feb 19 '16 at 15:51
  • @RadimKöhler, yes I agree, answers should be tested. But I have no dev env at home (not even MS Windows indeed), and doing that from work is not what I am paid for. So on unanswered questions, giving some untested suggestions may still help. – Frédéric Feb 19 '16 at 20:16
  • @Skippy, my `BuildHql` implementation was wrong. I was wondering how it was inferring on what to apply `class`. It was not able to do that. I have now added the `Dot` construct in `BuildHql` which allows to explicit this. (And this time, tested (but I am way out of office normal hours, though still there).) It was quite a great deal of fiddling, I was unable to find relevant doc about all that. – Frédéric Feb 19 '16 at 22:16
  • Thanks for your help @Frédéric. I have confirmed the cleaner suggestion has solved my question. – Skippy Feb 22 '16 at 14:26
1

Depending on your needs, a sorting expression that is based upon a comparison to a specific type should work. For example (forgive the VB.NET):

Dim o = Session.Query(Of Animal).OrderBy(Function(x) If(TypeOf x Is Cat, 1, 2)).ToList()
DanP
  • 6,310
  • 4
  • 40
  • 68
  • While I was at this subject, I have tested with skepticism the c# translation of your suggestion. But it works indeed. `session.Query().OrderBy(e => e is SomeChildClass ? 1 : 2);` – Frédéric Feb 19 '16 at 22:43