Skip to content

Update IIsEntityDecider to use ExpressionsHelper.TryGetMappedType #2324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/NHibernate.DomainModel/Northwind/Entities/Animal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ public class Animal
public virtual Animal Father { get; set; }
public virtual IList<Animal> Children { get; set; }
public virtual string SerialNumber { get; set; }
}

public virtual Animal FatherOrMother => Father ?? Mother;
}

public abstract class Reptile : Animal
{
Expand All @@ -30,4 +32,4 @@ public abstract class Mammal : Animal
public class Dog : Mammal { }

public class Cat : Mammal { }
}
}
8 changes: 8 additions & 0 deletions src/NHibernate.Test/Async/Linq/SelectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
8 changes: 8 additions & 0 deletions src/NHibernate.Test/Linq/SelectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
18 changes: 16 additions & 2 deletions src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
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;

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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
}
5 changes: 3 additions & 2 deletions src/NHibernate/Linq/Visitors/MemberExpressionJoinDetector.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,7 +40,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)
Expand All @@ -50,7 +51,7 @@ protected override Expression VisitMember(MemberExpression expression)
if (!isIdentifier)
_memberExpressionDepth--;

if (_isEntityDecider.IsEntity(expression.Type) &&
if (isEntity &&
((_requiresJoinForNonIdentifier && !_hasIdentifier) || _memberExpressionDepth > 0) &&
_joiner.CanAddJoin(expression))
{
Expand Down
9 changes: 7 additions & 2 deletions src/NHibernate/Linq/Visitors/SelectClauseNominator.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +16,7 @@ namespace NHibernate.Linq.Visitors
class SelectClauseHqlNominator : RelinqExpressionVisitor
{
private readonly ILinqToHqlGeneratorsRegistry _functionRegistry;
private readonly ISessionFactoryImplementor _sessionFactory;

/// <summary>
/// The expression parts that can be converted to pure HQL.
Expand All @@ -35,6 +37,7 @@ class SelectClauseHqlNominator : RelinqExpressionVisitor
public SelectClauseHqlNominator(VisitorParameters parameters)
{
_functionRegistry = parameters.SessionFactory.Settings.LinqToHqlGeneratorsRegistry;
_sessionFactory = parameters.SessionFactory;
}

internal Expression Nominate(Expression expression)
Expand Down Expand Up @@ -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
// 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 _);
}

private static bool CanBeEvaluatedInHqlStatementShortcut(Expression expression)
Expand Down
8 changes: 3 additions & 5 deletions src/NHibernate/Linq/Visitors/WhereJoinDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -314,10 +315,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++;

Expand All @@ -327,7 +325,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.
Expand Down