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?
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)
)
);
}
}