Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
// 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)
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