I'm getting some unexpected behaviour with a JoinAlias QueryOver in NHibernate.
My entity essentially looks like this:
public class Field
{
public virtual long Id { get; protected set; }
public virtual Field Parent { get; protected set; }
public virtual FieldType Type { get; protected set; }
public virtual string Value { get; protected set; }
...(Ctors etc
}
My mapping is such:
public class FieldMap : ClassMap<Field>
{
public FieldMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
References(x => x.Type)
.Column("FieldTypeId")
.LazyLoad()
.Cascade.All()
;
Map(x => x.Value);
References(x => x.Parent)
.Column("ParentFieldId")
.Nullable()
.LazyLoad()
.Cascade.All()
;
}
My query:
Field fieldAlias = null;
string typeAlias = null;
Field parentFieldAlias = null;
var query = getSession().QueryOver<Field>(() => fieldAlias)
.JoinAlias(() => fieldAlias.Type, () => typeAlias)
.Where(() => typeAlias.Name == type)
.JoinAlias(() => fieldAlias.Parent, () => parentFieldAlias)
.Where(() => (parentFieldAlias.Value == parentValue) || (parentFieldAlias == null))
;
As far as I'm concerned, this should give me some SQL like this:
... WHERE (a.ParentFieldId == NULL) OR (a.ParentFieldId = c.FieldId AND c.Value = parentValue)
But I'm getting a null reference exception. (I assume when the alias is resolved and the Parent is null).
The exception details are:
System.NullReferenceException occurred
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=NHibernate
StackTrace:
at NHibernate.Criterion.ConstantProjection..ctor(Object value)
at NHibernate.Criterion.Projections.Constant(Object obj)
at NHibernate.Impl.ExpressionProcessor.FindMemberProjection(Expression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessSimpleExpression(Expression left, Expression right, ExpressionType nodeType)
at NHibernate.Impl.ExpressionProcessor.ProcessSimpleExpression(BinaryExpression be)
at NHibernate.Impl.ExpressionProcessor.ProcessBinaryExpression(BinaryExpression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessExpression(Expression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessOrExpression(BinaryExpression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessBinaryExpression(BinaryExpression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessExpression(Expression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessLambdaExpression(LambdaExpression expression)
at NHibernate.Impl.ExpressionProcessor.ProcessExpression(Expression`1 expression)
at NHibernate.Criterion.QueryOver`2.Add(Expression`1 expression)
at NHibernate.Criterion.QueryOver`2.Where(Expression`1 expression)
at NHibernate.Criterion.QueryOver`2.NHibernate.IQueryOver<TRoot,TSubType>.Where(Expression`1 expression)
at Ismoos.Director.FieldOptionsQuery.Execute(Service service, String type, String parentValue) in D:\Work\Ismoos\Ismoos\Director\Ismoos.Director\FieldOptionsQuery.cs:line 31
InnerException:
I've tried a few different ways, including:
.JoinAlias(() => fieldAlias.Parent, () => parentFieldAlias)
.Where(Restrictions.Or(Restrictions.On(() => fieldAlias.Parent).IsNotNull,
Restrictions.On(() => parentFieldAlias.Value).IsLike(parentValue))))
but none of these work.
I have a workaround, by leaving out the restriction on the parent field value in the query, and performing a LINQ query after the QueryOver returns, like so:
Field fieldAlias = null;
string typeAlias = null;
Field parentFieldAlias = null;
var query = getSession().QueryOver<Field>(() => fieldAlias)
.JoinAlias(() => fieldAlias.Type, () => typeAlias)
.Where(() => typeAlias.Name == type)
;
var list = query
.List<Field>()
;
return list
.Where(x => (x.Parent == null) || (x.Parent.Value == parentValue))
.ToList();
but this isn't as optimal as doing it in the QueryOver.
Any suggestions?
The second solution with restrictions would do the job. There are two issues. We need a LEFT JOIN to Parent and I see a typo: IsNotNull
should be IsNull
to correctly evaluate the OR
statement:
Broken solution IsNotNull
(and most likely inner join)
.JoinAlias(() => fieldAlias.Parent, () => parentFieldAlias)
.Where
(
Restrictions.Or(
Restrictions.On(() => fieldAlias.Parent).IsNotNull, // here
Restrictions.On(() => parentFieldAlias.Value).IsLike(parentValue)))
)
Working OR
with IsNull
and LEFT JOIN:
.JoinAlias(() => fieldAlias.Parent, () => parentFieldAlias
, NHibernate.SqlCommand.JoinType.LeftOuterJoin)) // left join for NULL
.Where
(
Restrictions.Or(
Restrictions.On(() => fieldAlias.Parent).IsNull, // this is what we need
Restrictions.On(() => parentFieldAlias.Value).IsLike(parentValue))
);
The issue with the first solution is, that we cannot evaluate for null the floating/virtual object: parentFieldAlias
.Where(() => (parentFieldAlias.Value == parentValue) || (parentFieldAlias == null)) ;
We need to check the property of the owner: fieldAlias.Parent