nhibernatedistinctqueryover

NHIbernate 3.0 - QueryOver, using same projection with distinct and order throws sql error


I have an NHibernate QueryOver which is generating the sql error : ORDER BY items must appear in the select list if SELECT DISTINCT is specified

The problem is caused by a sql projection I am using to select, where and order by. Because the projection itself uses a sql function, it has parameters (a constant: space).

When using the projection assigned to a variable, NH translates each use of this variable uniquely to sql, meaning each gets its own new sql parameter. Sql therefore thinks the statements are different. I have tried to no avail using aliases for the projections, but there seems no way to do this with QueryOver.

Bit lost for ideas other than devolving back to the Criteria API.

This is the QueryIOver code simplified:

  var projection = ContactOrCompanyName();
  return Session.QueryOver<Contact>()
    .Select(
      Projections.Distinct(
        Projections.ProjectionList()
          .Add(Projections.Property<Contact>(x => x.Id).As("ContactId"))
          .Add(projection)
        )
    )
    .TransformUsing(Transformers.AliasToBean<ContactDto>())
    .OrderBy(projection).Asc;

private IProjection ContactOrCompanyName
    {
      get
      {
        return Projections.SqlFunction(
          "coalesce",
          NHibernateUtil.String,
          Projections.Property<Contact>(c => c.CompanyName),
          Projections.SqlFunction(
            "concat",
            NHibernateUtil.String,
            Projections.Property<Contact>(c => c.FirstName),
            Projections.Constant(" "),
            Projections.Property<Contact>(c => c.LastName)
          )
        );
      }
    }

results in the following sql:

SELECT distinct   
this_.CONTACT_ID as y0_, 
coalesce(this_.COMPANY_NM, (this_.FIRST_NM+@p0+this_.LAST_NM)) as y1_ 
FROM dbo.ADD_CONTACT this_ 
ORDER BY coalesce(this_.COMPANY_NM, (this_.FIRST_NM+@p1+this_.LAST_NM)) asc

Criteria API does seem to support alias reuse going by this example:

IList results = session.CreateCriteria(typeof(DomesticCat), "cat")
    .CreateAlias("kittens", "kit")
    .SetProjection( Projections.ProjectionList()
        .Add( Projections.Property("cat.Name"), "catName" )
        .Add( Projections.Property("kit.Name"), "kitName" )
    )
    .AddOrder( Order.Asc("catName") )
    .AddOrder( Order.Asc("kitName") )
    .List();

So where is it in QueryOver?


Solution

  • This is a bit hacky, but it works. By creating two projections one for comparison and one for selection, we can stop SQL complaining about the order by projection not being included in the select list:

          CompanyDirectorDto dtoAlias = null;
          var contactsQuery = Session.QueryOver<Contact>()
            .Select(
              Projections.Distinct(
                Projections.ProjectionList()
                  .Add(Projections.Property<Contact>(x => x.Id).WithAlias(() => dtoAlias.ContactId))
                  .Add(ContactOrCompanyNameComparer)
                  .Add(ContactOrCompanyNameSelector.WithAlias(() => dtoAlias.ContactDisplayName))
                )
            )
            .TransformUsing(Transformers.AliasToBean<CompanyDirectorDto>())
            .OrderBy(ContactOrCompanyNameComparer).Asc;
    
    private IProjection ContactOrCompanyNameSelector
    {
      get
      {
        return Projections.SqlFunction(
          "coalesce",
          NHibernateUtil.String,
          Projections.Property<Contact>(c => c.CompanyName),
          Projections.SqlFunction(
            "concat",
            NHibernateUtil.String,
            Projections.Property<Contact>(c => c.FirstName),
            Projections.Constant(" "),
            Projections.Property<Contact>(c => c.LastName)
          )
        );
      }
    }
    
    private IProjection ContactOrCompanyNameComparer
    {
      get
      {
        return Projections.SqlFunction(
          "coalesce",
          NHibernateUtil.String,
          Projections.Property<Contact>(c => c.CompanyName),
          Projections.SqlFunction(
            "concat",
            NHibernateUtil.String,
            Projections.Property<Contact>(c => c.FirstName),
            Projections.Property<Contact>(c => c.LastName)
          )
        );
      }
    }