diff --git a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs index f016139f79b..eebd9d8768c 100644 --- a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs @@ -342,8 +342,12 @@ public async Task NullableEntityProjectionAsync() var fullList = await (session.Query().Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToListAsync()); var withValidManyToOneList = await (session.Query().Where(x => x.ManyToOne != null).Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToListAsync()); + var withValidManyToOneList2 = await (session.CreateQuery("from NullableOwner ex where not ex.ManyToOne is null").ListAsync()); + var withNullManyToOneList = await (session.Query().Where(x => x.ManyToOne == null).ToListAsync()); Assert.That(fullList.Count, Is.EqualTo(2)); Assert.That(withValidManyToOneList.Count, Is.EqualTo(0)); + Assert.That(withValidManyToOneList2.Count, Is.EqualTo(0)); + Assert.That(withNullManyToOneList.Count, Is.EqualTo(2)); } } diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs index 2eec6634d65..85164ebb861 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs @@ -330,8 +330,12 @@ public void NullableEntityProjection() var fullList = session.Query().Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToList(); var withValidManyToOneList = session.Query().Where(x => x.ManyToOne != null).Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToList(); + var withValidManyToOneList2 = session.CreateQuery("from NullableOwner ex where not ex.ManyToOne is null").List(); + var withNullManyToOneList = session.Query().Where(x => x.ManyToOne == null).ToList(); Assert.That(fullList.Count, Is.EqualTo(2)); Assert.That(withValidManyToOneList.Count, Is.EqualTo(0)); + Assert.That(withValidManyToOneList2.Count, Is.EqualTo(0)); + Assert.That(withNullManyToOneList.Count, Is.EqualTo(2)); } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 578d9331153..3dc2cd30640 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -42,6 +42,7 @@ public partial class HqlSqlWalker private SelectClause _selectClause; private readonly AliasGenerator _aliasGenerator = new AliasGenerator(); private readonly ASTPrinter _printer = new ASTPrinter(); + private bool _isNullComparison; // //Maps each top-level result variable to its SelectExpression; @@ -1203,6 +1204,8 @@ public IASTFactory ASTFactory } } + internal bool IsNullComparison => _isNullComparison; + public void AddQuerySpaces(string[] spaces) { for (int i = 0; i < spaces.Length; i++) diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g index fba1010337c..d99340b9726 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g @@ -380,7 +380,7 @@ comparisonExpr | ^(NOT_BETWEEN exprOrSubquery exprOrSubquery exprOrSubquery) | ^(IN exprOrSubquery inRhs ) | ^(NOT_IN exprOrSubquery inRhs ) - | ^(IS_NULL exprOrSubquery) + | ^(IS_NULL { _isNullComparison = true; } exprOrSubquery { _isNullComparison = false; }) | ^(IS_NOT_NULL exprOrSubquery) // | ^(IS_TRUE expr) // | ^(IS_FALSE expr) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index 9db7baacb2f..b594f4aad44 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -389,8 +389,6 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string bool joinIsNeeded; //For nullable entity comparisons we always need to add join (like not constrained one-to-one or not-found ignore associations) - //NOTE: This fix is not fully correct. It doesn't work for comparisons with null (where e.OneToOneProp is null) - // as by default implicit join is generated and to work propelry left join is required (see GH-2611) bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause; if ( IsDotNode( parent ) ) @@ -417,8 +415,14 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string || comparisonWithNullableEntity; } - if ( joinIsNeeded ) + if ( joinIsNeeded ) { + if (comparisonWithNullableEntity && Walker.IsNullComparison) + { + implicitJoin = false; + _joinType = JoinType.LeftOuterJoin; + } + DereferenceEntityJoin( classAlias, entityType, implicitJoin, parent ); if (comparisonWithNullableEntity) {