From 4c42ed7c477882ead756736ee24debf1db4d7841 Mon Sep 17 00:00:00 2001 From: Alexandre Weinberger Date: Tue, 14 Feb 2017 12:07:41 -0200 Subject: [PATCH 1/3] NH-3946: Linq where is base class doesn't get subclasses. Added failing test --- src/NHibernate.Test/Linq/WhereTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/NHibernate.Test/Linq/WhereTests.cs b/src/NHibernate.Test/Linq/WhereTests.cs index 2f8f66e5c0a..920286ef1d9 100644 --- a/src/NHibernate.Test/Linq/WhereTests.cs +++ b/src/NHibernate.Test/Linq/WhereTests.cs @@ -652,6 +652,16 @@ where o is Dog Assert.That(query.Count, Is.EqualTo(2)); } + [Test(Description = "NH-3946")] + public void PolymorphicSearchOnObjectTypeWithIsKeyword() + { + var query = (from o in session.Query() + where o is Mammal + select o).ToList(); + + Assert.That(query.Count, Is.EqualTo(3)); + } + [Test] public void BitwiseQuery() { From b12486e0ade761ff708ee0b352b40e1e47d9f02e Mon Sep 17 00:00:00 2001 From: Alexandre Weinberger Date: Tue, 14 Feb 2017 12:24:43 -0200 Subject: [PATCH 2/3] NH-3946: Linq where is base class doesn't get subclasses. Adapted logic from SingleTableEntityPersister.DiscriminatorFilterFragment to find subclasses. Seems to work with JoinedSubclassEntityPersister as well --- .../HqlGeneratorExpressionTreeVisitor.cs | 38 +++++++++++++++++++ .../Entity/AbstractEntityPersister.cs | 1 + .../Entity/JoinedSubclassEntityPersister.cs | 5 +++ .../Entity/SingleTableEntityPersister.cs | 4 +- .../Entity/UnionSubclassEntityPersister.cs | 2 +- 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs index 9b80a446a4b..d2ab800f8e6 100644 --- a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs +++ b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs @@ -147,6 +147,44 @@ protected HqlTreeNode VisitExpression(Expression expression) private HqlTreeNode VisitTypeBinaryExpression(TypeBinaryExpression expression) { + var meta = SessionFactory.GetClassMetadata(expression.TypeOperand) as Persister.Entity.AbstractEntityPersister; + if (meta != null && !meta.IsExplicitPolymorphism) + { + //Adapted the logic found in SingleTableEntityPersister.DiscriminatorFilterFragment + var Factory = SessionFactory as NHibernate.Engine.ISessionFactoryImplementor; + var nodes = new System.Collections.Generic.List(); + foreach (var typeName in meta.SubclassClosure) + { + var persister = (NHibernate.Persister.Entity.IQueryable)Factory.GetEntityPersister(typeName); + if (persister.IsAbstract) continue; + nodes.Add(_hqlTreeBuilder.Ident(persister.EntityName)); + } + + if (nodes.Count == 1) + { + return _hqlTreeBuilder.Equality( + _hqlTreeBuilder.Dot(Visit(expression.Expression).AsExpression(), _hqlTreeBuilder.Class()), + nodes[0]); + } + else if (nodes.Count > 1) + { + return _hqlTreeBuilder.In( + _hqlTreeBuilder.Dot( + Visit(expression.Expression).AsExpression(), + _hqlTreeBuilder.Class()), + _hqlTreeBuilder.ExpressionSubTreeHolder(nodes.ToArray())); + } + else + { + const string abstractClassWithNoSubclassExceptionMessageTemplate = +@"The class {0} can't be instatiated and does not have mapped subclasses; +possible solutions: +- don't map the abstract class +- map its subclasses."; + + throw new NotSupportedException(string.Format(abstractClassWithNoSubclassExceptionMessageTemplate, meta.EntityName)); + } + } return _hqlTreeBuilder.Equality( _hqlTreeBuilder.Dot(Visit(expression.Expression).AsExpression(), _hqlTreeBuilder.Class()), _hqlTreeBuilder.Ident(expression.TypeOperand.FullName)); diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index f817f5647d9..c0b98bb0eeb 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -1013,6 +1013,7 @@ public int[] NaturalIdentifierProperties public abstract string[] ConstraintOrderedTableNameClosure { get;} public abstract string DiscriminatorSQLValue { get;} public abstract object DiscriminatorValue { get;} + public abstract string[] SubclassClosure { get; } public abstract string[] PropertySpaces { get;} protected virtual void AddDiscriminatorToInsert(SqlInsertBuilder insert) { } diff --git a/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs b/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs index 63aae0dcc44..c1510d9409c 100644 --- a/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs @@ -345,6 +345,11 @@ public override object DiscriminatorValue get { return discriminatorValue; } } + public override string[] SubclassClosure + { + get { return subclassClosure; } + } + public override string[] PropertySpaces { get diff --git a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs index 0131d84aafa..7ce36aaa03f 100644 --- a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs @@ -424,7 +424,7 @@ public override object DiscriminatorValue get { return discriminatorValue; } } - public virtual string[] SubclassClosure + public override string[] SubclassClosure { get { return subclassClosure; } } @@ -618,7 +618,7 @@ private string DiscriminatorFilterFragment(string alias) @"The class {0} can't be instatiated and does not have mapped subclasses; possible solutions: - don't map the abstract class -- map the its subclasses."; +- map its subclasses."; if (NeedsDiscriminator) { diff --git a/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs b/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs index a1a3d5267bf..ad93180dc2d 100644 --- a/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/UnionSubclassEntityPersister.cs @@ -186,7 +186,7 @@ public override object DiscriminatorValue get { return discriminatorValue; } } - public string[] SubclassClosure + public override string[] SubclassClosure { get { return subclassClosure; } } From 06d17c76f867315f67dd3b191331bd451e2b02ae Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 15 Feb 2017 10:57:47 +1300 Subject: [PATCH 3/3] NH-3845 - Partially fix OfType operator --- src/NHibernate.Test/Linq/WhereTests.cs | 8 ++++ .../HqlGeneratorExpressionTreeVisitor.cs | 43 +++++++++++-------- .../ResultOperatorProcessors/ProcessOfType.cs | 19 +++----- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/NHibernate.Test/Linq/WhereTests.cs b/src/NHibernate.Test/Linq/WhereTests.cs index 920286ef1d9..d35d9bc079e 100644 --- a/src/NHibernate.Test/Linq/WhereTests.cs +++ b/src/NHibernate.Test/Linq/WhereTests.cs @@ -662,6 +662,14 @@ where o is Mammal Assert.That(query.Count, Is.EqualTo(3)); } + [Test(Description = "NH-3845")] + public void PolymorphicSearchOnObjectTypeWithOfType() + { + var query = session.Query().OfType().ToList(); + + Assert.That(query.Count, Is.EqualTo(3)); + } + [Test] public void BitwiseQuery() { diff --git a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs index d2ab800f8e6..4bdbffa05a7 100644 --- a/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs +++ b/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs @@ -147,34 +147,40 @@ protected HqlTreeNode VisitExpression(Expression expression) private HqlTreeNode VisitTypeBinaryExpression(TypeBinaryExpression expression) { - var meta = SessionFactory.GetClassMetadata(expression.TypeOperand) as Persister.Entity.AbstractEntityPersister; + return BuildOfType(expression.Expression, expression.TypeOperand); + } + + internal HqlBooleanExpression BuildOfType(Expression expression, System.Type type) + { + var sessionFactory = _parameters.SessionFactory; + var meta = sessionFactory.GetClassMetadata(type) as Persister.Entity.AbstractEntityPersister; if (meta != null && !meta.IsExplicitPolymorphism) { //Adapted the logic found in SingleTableEntityPersister.DiscriminatorFilterFragment - var Factory = SessionFactory as NHibernate.Engine.ISessionFactoryImplementor; - var nodes = new System.Collections.Generic.List(); - foreach (var typeName in meta.SubclassClosure) - { - var persister = (NHibernate.Persister.Entity.IQueryable)Factory.GetEntityPersister(typeName); - if (persister.IsAbstract) continue; - nodes.Add(_hqlTreeBuilder.Ident(persister.EntityName)); - } + var nodes = meta + .SubclassClosure + .Select(typeName => (NHibernate.Persister.Entity.IQueryable) sessionFactory.GetEntityPersister(typeName)) + .Where(persister => !persister.IsAbstract) + .Select(persister => _hqlTreeBuilder.Ident(persister.EntityName)) + .ToList(); if (nodes.Count == 1) { return _hqlTreeBuilder.Equality( - _hqlTreeBuilder.Dot(Visit(expression.Expression).AsExpression(), _hqlTreeBuilder.Class()), + _hqlTreeBuilder.Dot(Visit(expression).AsExpression(), _hqlTreeBuilder.Class()), nodes[0]); } - else if (nodes.Count > 1) + + if (nodes.Count > 1) { return _hqlTreeBuilder.In( - _hqlTreeBuilder.Dot( - Visit(expression.Expression).AsExpression(), - _hqlTreeBuilder.Class()), - _hqlTreeBuilder.ExpressionSubTreeHolder(nodes.ToArray())); + _hqlTreeBuilder.Dot( + Visit(expression).AsExpression(), + _hqlTreeBuilder.Class()), + _hqlTreeBuilder.ExpressionSubTreeHolder(nodes)); } - else + + if (nodes.Count == 0) { const string abstractClassWithNoSubclassExceptionMessageTemplate = @"The class {0} can't be instatiated and does not have mapped subclasses; @@ -185,9 +191,10 @@ private HqlTreeNode VisitTypeBinaryExpression(TypeBinaryExpression expression) throw new NotSupportedException(string.Format(abstractClassWithNoSubclassExceptionMessageTemplate, meta.EntityName)); } } + return _hqlTreeBuilder.Equality( - _hqlTreeBuilder.Dot(Visit(expression.Expression).AsExpression(), _hqlTreeBuilder.Class()), - _hqlTreeBuilder.Ident(expression.TypeOperand.FullName)); + _hqlTreeBuilder.Dot(Visit(expression).AsExpression(), _hqlTreeBuilder.Class()), + _hqlTreeBuilder.Ident(type.FullName)); } protected HqlTreeNode VisitNhStar(NhStarExpression expression) diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessOfType.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessOfType.cs index 7c9613df7a8..4da0271fee2 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessOfType.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessOfType.cs @@ -1,24 +1,17 @@ -using System.Linq.Expressions; -using NHibernate.Hql.Ast; -using Remotion.Linq.Clauses.ResultOperators; +using Remotion.Linq.Clauses.ResultOperators; namespace NHibernate.Linq.Visitors.ResultOperatorProcessors { public class ProcessOfType : IResultOperatorProcessor { - #region IResultOperatorProcessor Members - public void Process(OfTypeResultOperator resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree) { - Expression source = queryModelVisitor.Model.SelectClause.GetOutputDataInfo().ItemExpression; + var source = queryModelVisitor.Model.SelectClause.GetOutputDataInfo().ItemExpression; - tree.AddWhereClause(tree.TreeBuilder.Equality( - tree.TreeBuilder.Dot( - HqlGeneratorExpressionTreeVisitor.Visit(source, queryModelVisitor.VisitorParameters).AsExpression(), - tree.TreeBuilder.Class()), - tree.TreeBuilder.Ident(resultOperator.SearchedItemType.FullName))); - } + var expression = new HqlGeneratorExpressionTreeVisitor(queryModelVisitor.VisitorParameters) + .BuildOfType(source, resultOperator.SearchedItemType); - #endregion + tree.AddWhereClause(expression); + } } } \ No newline at end of file