From c9721e9e127859d07a5c8d2626a4b62808745c13 Mon Sep 17 00:00:00 2001 From: maca88 Date: Fri, 6 Mar 2020 23:02:28 +0100 Subject: [PATCH 1/3] Update IIsEntityDecider to use ExpressionsHelper.TryGetMappedType --- .../Linq/ReWriters/AddJoinsReWriter.cs | 22 +++++++++++++++---- .../Visitors/MemberExpressionJoinDetector.cs | 12 +++++++--- .../Linq/Visitors/WhereJoinDetector.cs | 15 ++++++++----- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs index d022e1ffc88..5f8350d92a6 100644 --- a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs +++ b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Specialized; using System.Linq; +using System.Linq.Expressions; using NHibernate.Engine; using NHibernate.Linq.Clauses; using NHibernate.Linq.Visitors; +using NHibernate.Util; using Remotion.Linq; using Remotion.Linq.Clauses; @@ -11,8 +13,7 @@ namespace NHibernate.Linq.ReWriters { internal interface IIsEntityDecider { - bool IsEntity(System.Type type); - bool IsIdentifier(System.Type type, string propertyName); + bool IsEntity(MemberExpression expression, out bool isIdentifier); } public class AddJoinsReWriter : NhQueryModelVisitorBase, IIsEntityDecider @@ -26,8 +27,8 @@ private AddJoinsReWriter(ISessionFactoryImplementor sessionFactory, QueryModel q { _sessionFactory = sessionFactory; var joiner = new Joiner(queryModel, AddJoin); - _memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner); - _whereJoinDetector = new WhereJoinDetector(this, joiner); + _memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner, _sessionFactory); + _whereJoinDetector = new WhereJoinDetector(this, joiner, _sessionFactory); } public static void ReWrite(QueryModel queryModel, VisitorParameters parameters) @@ -77,11 +78,15 @@ public override void VisitJoinClause(JoinClause joinClause, QueryModel queryMode _currentJoin = null; } + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] public bool IsEntity(System.Type type) { return _sessionFactory.GetImplementors(type.FullName).Any(); } + // Since v5.3 + [Obsolete("This method has no usages and will be removed in a future version")] public bool IsIdentifier(System.Type type, string propertyName) { var metadata = _sessionFactory.GetClassMetadata(type); @@ -99,5 +104,14 @@ private void AddJoin(QueryModel queryModel, NhJoinClause joinClause) queryModel.BodyClauses.Add(joinClause); } + + bool IIsEntityDecider.IsEntity(MemberExpression expression, out bool isIdentifier) + { + isIdentifier = + ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out var mappedType, out var entityPersister, out _, out var memberPath) + && entityPersister?.IdentifierPropertyName == memberPath; + + return mappedType?.IsEntityType == true; + } } } diff --git a/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs b/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs index f333fc5e61d..afc761078b7 100644 --- a/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs +++ b/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; +using NHibernate.Engine; using NHibernate.Linq.Expressions; using NHibernate.Linq.ReWriters; using Remotion.Linq.Clauses; @@ -18,16 +19,21 @@ internal class MemberExpressionJoinDetector : RelinqExpressionVisitor { private readonly IIsEntityDecider _isEntityDecider; private readonly IJoiner _joiner; + private readonly ISessionFactoryImplementor _sessionFactory; private bool _requiresJoinForNonIdentifier; private bool _preventJoinsInConditionalTest; private bool _hasIdentifier; private int _memberExpressionDepth; - public MemberExpressionJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner) + public MemberExpressionJoinDetector( + IIsEntityDecider isEntityDecider, + IJoiner joiner, + ISessionFactoryImplementor sessionFactory) { _isEntityDecider = isEntityDecider; _joiner = joiner; + _sessionFactory = sessionFactory; } protected override Expression VisitMember(MemberExpression expression) @@ -39,7 +45,7 @@ protected override Expression VisitMember(MemberExpression expression) return base.VisitMember(expression); } - var isIdentifier = _isEntityDecider.IsIdentifier(expression.Expression.Type, expression.Member.Name); + var isEntity = _isEntityDecider.IsEntity(expression, out var isIdentifier); if (isIdentifier) _hasIdentifier = true; if (!isIdentifier) @@ -50,7 +56,7 @@ protected override Expression VisitMember(MemberExpression expression) if (!isIdentifier) _memberExpressionDepth--; - if (_isEntityDecider.IsEntity(expression.Type) && + if (isEntity && ((_requiresJoinForNonIdentifier && !_hasIdentifier) || _memberExpressionDepth > 0) && _joiner.CanAddJoin(expression)) { diff --git a/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs b/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs index 4be0b8d2af2..dc4f68ca4b6 100644 --- a/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs +++ b/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using NHibernate.Engine; using NHibernate.Linq.ReWriters; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; @@ -61,6 +62,7 @@ internal class WhereJoinDetector : RelinqExpressionVisitor // TODO: There are a number of types of expressions that we didn't handle here due to time constraints. For example, the ?: operator could be checked easily. private readonly IIsEntityDecider _isEntityDecider; private readonly IJoiner _joiner; + private readonly ISessionFactoryImplementor _sessionFactory; private readonly Stack _handled = new Stack(); @@ -70,10 +72,14 @@ internal class WhereJoinDetector : RelinqExpressionVisitor // The following is used for member expressions traversal. private int _memberExpressionDepth; - internal WhereJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner) + internal WhereJoinDetector( + IIsEntityDecider isEntityDecider, + IJoiner joiner, + ISessionFactoryImplementor sessionFactory) { _isEntityDecider = isEntityDecider; _joiner = joiner; + _sessionFactory = sessionFactory; } public Expression Transform(Expression expression) @@ -314,10 +320,7 @@ protected override Expression VisitMember(MemberExpression expression) return base.VisitMember(expression); } - var isIdentifier = _isEntityDecider.IsIdentifier( - expression.Expression.Type, - expression.Member.Name); - + var isEntity = _isEntityDecider.IsEntity(expression, out var isIdentifier); if (!isIdentifier) _memberExpressionDepth++; @@ -327,7 +330,7 @@ protected override Expression VisitMember(MemberExpression expression) _memberExpressionDepth--; ExpressionValues values = _values.Pop().Operation(pvs => pvs.MemberAccess(expression.Type)); - if (_isEntityDecider.IsEntity(expression.Type)) + if (isEntity) { // Don't add joins for things like a.B == a.C where B and C are entities. // We only need to join B when there's something like a.B.D. From d77e6f43ecf9d54a7145e272a064cde9f1189440 Mon Sep 17 00:00:00 2001 From: maca88 Date: Mon, 20 Apr 2020 22:27:44 +0200 Subject: [PATCH 2/3] Code review changes --- src/NHibernate.DomainModel/Northwind/Entities/Animal.cs | 6 ++++-- src/NHibernate.Test/Async/Linq/SelectionTests.cs | 8 ++++++++ src/NHibernate.Test/Linq/SelectionTests.cs | 8 ++++++++ src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs | 4 ++-- .../Linq/Visitors/MemberExpressionJoinDetector.cs | 7 +------ src/NHibernate/Linq/Visitors/SelectClauseNominator.cs | 9 +++++++-- src/NHibernate/Linq/Visitors/WhereJoinDetector.cs | 7 +------ 7 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs b/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs index 03c76d428af..21fac97d4ae 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Animal.cs @@ -12,7 +12,9 @@ public class Animal public virtual Animal Father { get; set; } public virtual IList Children { get; set; } public virtual string SerialNumber { get; set; } - } + + public virtual Animal FatherOrMother => Father ?? Mother; + } public abstract class Reptile : Animal { @@ -30,4 +32,4 @@ public abstract class Mammal : Animal public class Dog : Mammal { } public class Cat : Mammal { } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/Linq/SelectionTests.cs b/src/NHibernate.Test/Async/Linq/SelectionTests.cs index cf065e6bf5d..2404abae275 100644 --- a/src/NHibernate.Test/Async/Linq/SelectionTests.cs +++ b/src/NHibernate.Test/Async/Linq/SelectionTests.cs @@ -289,6 +289,14 @@ public async Task CanSelectFirstElementFromChildCollectionAsync() } } + [Test] + public async Task CanSelectNotMappedEntityPropertyAsync() + { + var list = await (db.Animals.Where(o => o.Mother != null).Select(o => o.FatherOrMother.SerialNumber).ToListAsync()); + + Assert.That(list, Has.Count.GreaterThan(0)); + } + [Test] public async Task CanProjectWithCastAsync() { diff --git a/src/NHibernate.Test/Linq/SelectionTests.cs b/src/NHibernate.Test/Linq/SelectionTests.cs index 7aac7edc2da..e68c87c5cac 100644 --- a/src/NHibernate.Test/Linq/SelectionTests.cs +++ b/src/NHibernate.Test/Linq/SelectionTests.cs @@ -328,6 +328,14 @@ public void CanSelectWrappedType() Assert.IsTrue(query.ToArray().Length > 0); } + [Test] + public void CanSelectNotMappedEntityProperty() + { + var list = db.Animals.Where(o => o.Mother != null).Select(o => o.FatherOrMother.SerialNumber).ToList(); + + Assert.That(list, Has.Count.GreaterThan(0)); + } + [Test] public void CanProjectWithCast() { diff --git a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs index 5f8350d92a6..dc8f1952516 100644 --- a/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs +++ b/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs @@ -27,8 +27,8 @@ private AddJoinsReWriter(ISessionFactoryImplementor sessionFactory, QueryModel q { _sessionFactory = sessionFactory; var joiner = new Joiner(queryModel, AddJoin); - _memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner, _sessionFactory); - _whereJoinDetector = new WhereJoinDetector(this, joiner, _sessionFactory); + _memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner); + _whereJoinDetector = new WhereJoinDetector(this, joiner); } public static void ReWrite(QueryModel queryModel, VisitorParameters parameters) diff --git a/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs b/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs index afc761078b7..580ba3cf00c 100644 --- a/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs +++ b/src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs @@ -19,21 +19,16 @@ internal class MemberExpressionJoinDetector : RelinqExpressionVisitor { private readonly IIsEntityDecider _isEntityDecider; private readonly IJoiner _joiner; - private readonly ISessionFactoryImplementor _sessionFactory; private bool _requiresJoinForNonIdentifier; private bool _preventJoinsInConditionalTest; private bool _hasIdentifier; private int _memberExpressionDepth; - public MemberExpressionJoinDetector( - IIsEntityDecider isEntityDecider, - IJoiner joiner, - ISessionFactoryImplementor sessionFactory) + public MemberExpressionJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner) { _isEntityDecider = isEntityDecider; _joiner = joiner; - _sessionFactory = sessionFactory; } protected override Expression VisitMember(MemberExpression expression) diff --git a/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs b/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs index 9205b4f1b05..c923d6f3f60 100644 --- a/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs +++ b/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using NHibernate.Engine; using NHibernate.Linq.Functions; using NHibernate.Linq.Expressions; using NHibernate.Util; @@ -15,6 +16,7 @@ namespace NHibernate.Linq.Visitors class SelectClauseHqlNominator : RelinqExpressionVisitor { private readonly ILinqToHqlGeneratorsRegistry _functionRegistry; + private readonly ISessionFactoryImplementor _sessionFactory; /// /// The expression parts that can be converted to pure HQL. @@ -35,6 +37,7 @@ class SelectClauseHqlNominator : RelinqExpressionVisitor public SelectClauseHqlNominator(VisitorParameters parameters) { _functionRegistry = parameters.SessionFactory.Settings.LinqToHqlGeneratorsRegistry; + _sessionFactory = parameters.SessionFactory; } internal Expression Nominate(Expression expression) @@ -168,8 +171,10 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj return projectConstantsInHql; } - // Assume all is good - return true; + return !(expression is MemberExpression memberExpression) || // Assume all is good + // Evaluate only expressions that represent a mapped property + ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out _, out _, out _, out _) || + _functionRegistry.TryGetGenerator(memberExpression.Member, out _); } private static bool CanBeEvaluatedInHqlStatementShortcut(Expression expression) diff --git a/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs b/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs index dc4f68ca4b6..886d4e0e2b1 100644 --- a/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs +++ b/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs @@ -62,7 +62,6 @@ internal class WhereJoinDetector : RelinqExpressionVisitor // TODO: There are a number of types of expressions that we didn't handle here due to time constraints. For example, the ?: operator could be checked easily. private readonly IIsEntityDecider _isEntityDecider; private readonly IJoiner _joiner; - private readonly ISessionFactoryImplementor _sessionFactory; private readonly Stack _handled = new Stack(); @@ -72,14 +71,10 @@ internal class WhereJoinDetector : RelinqExpressionVisitor // The following is used for member expressions traversal. private int _memberExpressionDepth; - internal WhereJoinDetector( - IIsEntityDecider isEntityDecider, - IJoiner joiner, - ISessionFactoryImplementor sessionFactory) + internal WhereJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner) { _isEntityDecider = isEntityDecider; _joiner = joiner; - _sessionFactory = sessionFactory; } public Expression Transform(Expression expression) From a4f64a6bd37ef294080f7711bfa9c724edac6c95 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 21 Apr 2020 21:54:46 +0200 Subject: [PATCH 3/3] Update src/NHibernate/Linq/Visitors/SelectClauseNominator.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Frédéric Delaporte <12201973+fredericDelaporte@users.noreply.github.com> --- src/NHibernate/Linq/Visitors/SelectClauseNominator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs b/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs index c923d6f3f60..10d7bd07d3c 100644 --- a/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs +++ b/src/NHibernate/Linq/Visitors/SelectClauseNominator.cs @@ -172,7 +172,7 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj } return !(expression is MemberExpression memberExpression) || // Assume all is good - // Evaluate only expressions that represent a mapped property + // Nominate only expressions that represent a mapped property or a translatable method call ExpressionsHelper.TryGetMappedType(_sessionFactory, expression, out _, out _, out _, out _) || _functionRegistry.TryGetGenerator(memberExpression.Member, out _); }