Skip to content

Commit f5bc175

Browse files
committed
Rewritten NhPartialEvaluatingExpressionVisitor
1 parent f0644cf commit f5bc175

File tree

2 files changed

+108
-7
lines changed

2 files changed

+108
-7
lines changed

src/NHibernate.Test/Linq/WhereTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,16 @@ public void SelectOnCollectionReturnsResult()
812812
Assert.That(result.Children, Is.Not.Empty);
813813
}
814814

815+
[Test(Description = "GH-1556")]
816+
public void ContainsOnCollection()
817+
{
818+
var animal = session.Get<Animal>(1);
819+
820+
Assert.DoesNotThrow(() => session.Query<Animal>()
821+
.Where(e => animal.Children.Contains(e.Father))
822+
.FirstOrDefault());
823+
}
824+
815825

816826
private static List<object[]> CanUseCompareInQueryDataSource()
817827
{

src/NHibernate/Linq/Visitors/NhPartialEvaluatingExpressionVisitor.cs

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,110 @@
11
using System;
22
using System.Linq;
33
using System.Linq.Expressions;
4+
using NHibernate.Collection;
45
using Remotion.Linq.Clauses.Expressions;
56
using Remotion.Linq.Parsing;
6-
using Remotion.Linq.Parsing.ExpressionVisitors;
77
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;
88

99
namespace NHibernate.Linq.Visitors
1010
{
11+
//Modified version of PartialEvaluatingExpressionVisitor from Relinq (https://github.com/re-motion/Relinq)
12+
//Copyright (c) rubicon IT GmbH, www.rubicon.eu
13+
//
14+
//Used under the Apache Software License 2.0
1115
internal class NhPartialEvaluatingExpressionVisitor : RelinqExpressionVisitor, IPartialEvaluationExceptionExpressionVisitor
1216
{
17+
public static Expression EvaluateIndependentSubtrees(Expression expression)
18+
{
19+
return EvaluateIndependentSubtrees(expression, new NhEvaluatableExpressionFilter());
20+
}
21+
/// <summary>
22+
/// Takes an expression tree and finds and evaluates all its evaluatable subtrees.
23+
/// </summary>
24+
public static Expression EvaluateIndependentSubtrees(Expression expressionTree, IEvaluatableExpressionFilter evaluatableExpressionFilter)
25+
{
26+
if (expressionTree == null) throw new ArgumentNullException(nameof(expressionTree));
27+
if (evaluatableExpressionFilter == null) throw new ArgumentNullException(nameof(evaluatableExpressionFilter));
28+
29+
var partialEvaluationInfo = EvaluatableTreeFindingExpressionVisitor.Analyze(expressionTree, evaluatableExpressionFilter);
30+
31+
var visitor = new NhPartialEvaluatingExpressionVisitor(partialEvaluationInfo, evaluatableExpressionFilter);
32+
return visitor.Visit(expressionTree);
33+
}
34+
35+
// _partialEvaluationInfo contains a list of the expressions that are safe to be evaluated.
36+
private readonly PartialEvaluationInfo _partialEvaluationInfo;
37+
private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter;
38+
39+
private NhPartialEvaluatingExpressionVisitor(
40+
PartialEvaluationInfo partialEvaluationInfo,
41+
IEvaluatableExpressionFilter evaluatableExpressionFilter)
42+
{
43+
_partialEvaluationInfo = partialEvaluationInfo ?? throw new ArgumentNullException(nameof(partialEvaluationInfo));
44+
_evaluatableExpressionFilter = evaluatableExpressionFilter ?? throw new ArgumentNullException(nameof(evaluatableExpressionFilter));
45+
}
46+
47+
public override Expression Visit(Expression expression)
48+
{
49+
// Only evaluate expressions which do not use any of the surrounding parameter expressions. Don't evaluate
50+
// lambda expressions (even if you could), we want to analyze those later on.
51+
if (expression == null)
52+
return null;
53+
54+
if (expression.NodeType == ExpressionType.Lambda || !_partialEvaluationInfo.IsEvaluatableExpression(expression))
55+
return base.Visit(expression);
56+
57+
Expression evaluatedExpression;
58+
try
59+
{
60+
evaluatedExpression = EvaluateSubtree(expression);
61+
}
62+
catch (Exception ex)
63+
{
64+
// Evaluation caused an exception. Skip evaluation of this expression and proceed as if it weren't evaluable.
65+
var baseVisitedExpression = base.Visit(expression);
66+
// Then wrap the result to capture the exception for the back-end.
67+
return new PartialEvaluationExceptionExpression(ex, baseVisitedExpression);
68+
}
69+
70+
if (evaluatedExpression != expression)
71+
return EvaluateIndependentSubtrees(evaluatedExpression, _evaluatableExpressionFilter);
72+
73+
return evaluatedExpression;
74+
}
75+
76+
/// <summary>
77+
/// Evaluates an evaluatable <see cref="Expression"/> subtree, i.e. an independent expression tree that is compilable and executable
78+
/// without any data being passed in. The result of the evaluation is returned as a <see cref="ConstantExpression"/>; if the subtree
79+
/// is already a <see cref="ConstantExpression"/>, no evaluation is performed.
80+
/// </summary>
81+
/// <param name="subtree">The subtree to be evaluated.</param>
82+
/// <returns>A <see cref="ConstantExpression"/> holding the result of the evaluation.</returns>
83+
private Expression EvaluateSubtree(Expression subtree)
84+
{
85+
if (subtree == null) throw new ArgumentNullException(nameof(subtree));
86+
87+
if (subtree.NodeType == ExpressionType.Constant)
88+
{
89+
var constantExpression = (ConstantExpression) subtree;
90+
var valueAsIQueryable = constantExpression.Value as IQueryable;
91+
if (valueAsIQueryable != null && !IsEvaluatedQueryable(constantExpression.Value) && valueAsIQueryable.Expression != constantExpression)
92+
return valueAsIQueryable.Expression;
93+
94+
return constantExpression;
95+
}
96+
Expression<Func<object>> lambdaWithoutParameters = Expression.Lambda<Func<object>>(Expression.Convert(subtree, typeof(object)));
97+
var compiledLambda = lambdaWithoutParameters.Compile();
98+
99+
object value = compiledLambda();
100+
return Expression.Constant(value, subtree.Type);
101+
}
102+
103+
protected virtual bool IsEvaluatedQueryable(object queryable)
104+
{
105+
return queryable is IPersistentCollection;
106+
}
107+
13108
protected override Expression VisitConstant(ConstantExpression expression)
14109
{
15110
var value = expression.Value as Expression;
@@ -21,12 +116,6 @@ protected override Expression VisitConstant(ConstantExpression expression)
21116
return EvaluateIndependentSubtrees(value);
22117
}
23118

24-
public static Expression EvaluateIndependentSubtrees(Expression expression)
25-
{
26-
var evaluatedExpression = PartialEvaluatingExpressionVisitor.EvaluateIndependentSubtrees(expression, new NhEvaluatableExpressionFilter());
27-
return new NhPartialEvaluatingExpressionVisitor().Visit(evaluatedExpression);
28-
}
29-
30119
public Expression VisitPartialEvaluationException(PartialEvaluationExceptionExpression partialEvaluationExceptionExpression)
31120
{
32121
throw new HibernateException(
@@ -35,6 +124,8 @@ public Expression VisitPartialEvaluationException(PartialEvaluationExceptionExpr
35124
}
36125
}
37126

127+
128+
38129
internal class NhEvaluatableExpressionFilter : EvaluatableExpressionFilterBase
39130
{
40131
public override bool IsEvaluatableMethodCall(MethodCallExpression node)

0 commit comments

Comments
 (0)