Skip to content

Commit 0543c63

Browse files
CSHARP-2705: Linq Translation of Nested Any throws NotSupportedException.
1 parent 92ab159 commit 0543c63

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/MongoDB.Driver/Linq/Processors/OutOfCurrentScopePrefixCollector.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace MongoDB.Driver.Linq.Processors
2424
/// </summary>
2525
internal sealed class OutOfCurrentScopePrefixCollector : ExtensionExpressionVisitor
2626
{
27+
private bool _isWhereSourceConstantExpression = false;
2728
private readonly HashSet<string> _outOfCurrentScopePrefixCollection = new HashSet<string>();
2829

2930
/// <summary>
@@ -41,12 +42,25 @@ public static IEnumerable<string> Collect(Expression expression)
4142
public override Expression Visit(Expression node)
4243
{
4344
var hasOutOfCurrentScopePrefix = node as IHasOutOfCurrentScopePrefix;
44-
if (hasOutOfCurrentScopePrefix != null && !string.IsNullOrWhiteSpace(hasOutOfCurrentScopePrefix.OutOfCurrentScopePrefix))
45+
if (hasOutOfCurrentScopePrefix != null &&
46+
!string.IsNullOrWhiteSpace(hasOutOfCurrentScopePrefix.OutOfCurrentScopePrefix) &&
47+
!_isWhereSourceConstantExpression)
4548
{
4649
_outOfCurrentScopePrefixCollection.Add(hasOutOfCurrentScopePrefix.OutOfCurrentScopePrefix);
4750
}
4851

4952
return base.Visit(node);
5053
}
54+
55+
protected internal override Expression VisitWhere(WhereExpression node)
56+
{
57+
if (node.Source.NodeType == ExpressionType.Constant)
58+
{
59+
_isWhereSourceConstantExpression = true;
60+
}
61+
var result = base.VisitWhere(node);
62+
_isWhereSourceConstantExpression = false;
63+
return result;
64+
}
5165
}
5266
}

tests/MongoDB.Driver.Tests/Linq/Translators/PredicateTranslatorValidationTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,75 @@ public void Should_not_throw_the_exception_when_a_child_expression_is_called_fro
5959
Execute(CreateWhereQuery(expr));
6060
}
6161

62+
[Fact]
63+
public void Should_not_throw_the_exception_when_a_child_expression_is_called_from_nested_locals()
64+
{
65+
Setup();
66+
67+
var local = new List<int> { 1, 2 };
68+
var local2 = new List<bool> { true, false };
69+
70+
Expression<Func<TestObject, bool>> expr = (a) => a.Collection1.Any(b => local.Any(c => b.Collection2.Contains(c)));
71+
AssertWhere(expr, "{ $match : { 'Collection2' : { '$in' : [1, 2] } } }");
72+
73+
expr = (a) => a.Collection1.Any(b => b.Collection1.Any(c => local.Any(d => c.Collection2.Contains(d))));
74+
AssertWhere(expr, "{ $match : { 'Collection2' : { '$in' : [1, 2] } } }");
75+
76+
expr = (a) => a.Collection1.Any(b => b.Collection2 != null && b.Collection1.Any(c => local.Any(d => c.Collection2.Contains(d))));
77+
AssertWhere(expr, "{ $match : { 'Collection1' : { '$elemMatch' : { 'Collection2' : { '$ne' : null, '$in' : [1, 2] } } } } }");
78+
79+
expr = (a) => a.Collection1.Any(
80+
b =>
81+
b.Collection1 != null && b.Collection1.Any(
82+
c =>
83+
local.Any(d => c.Collection2.Contains(d)) &&
84+
local2.Any(e => c.Collection3.Contains(e))
85+
));
86+
AssertWhere(
87+
expr,
88+
@"{
89+
$match : {
90+
'Collection1' : {
91+
'$elemMatch' : {
92+
'Collection1' : {
93+
'$ne' : null,
94+
'$elemMatch' : {
95+
'Collection2' : { '$in' : [1, 2] },
96+
'Collection3' : { '$in' : [true, false] }
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}");
103+
104+
expr = (a) => a.Collection1.Any(
105+
b =>
106+
b.Collection1 != null && b.Collection1.Any(
107+
c =>
108+
c.Collection1.Any(d => d.Value1 == 2) &&
109+
local2.Any(e => c.Collection3.Contains(e))
110+
));
111+
AssertWhere(
112+
expr,
113+
@"
114+
{
115+
$match : {
116+
'Collection1' : {
117+
'$elemMatch' : {
118+
'Collection1' : {
119+
'$ne' : null,
120+
'$elemMatch' : {
121+
'Collection1' : { '$elemMatch' : { 'Value1' : 2 } },
122+
'Collection3' : { '$in' : [true, false] }
123+
}
124+
}
125+
}
126+
}
127+
}
128+
}");
129+
}
130+
62131
[Fact]
63132
public void Should_not_throw_the_exception_when_a_predicate_has_only_parameter_expressions()
64133
{
@@ -175,6 +244,21 @@ public void Should_throw_the_exception_when_there_is_the_parent_parameter_in_the
175244
exception.Message.Should().Be(string.Format(NotSupportErrorMessageTemplate, "{document}{Collection1}.Where((({document}{Value1} != 3) AndAlso Any({document}{Collection1}.Where(({document}{Value1} != 5)))))", "a"));
176245
}
177246

247+
// private methods
248+
private void AssertWhere(Expression<Func<TestObject, bool>> expression, string expectedStages)
249+
{
250+
var actualStages = Execute(CreateWhereQuery(expression));
251+
var actual = new BsonDocument();
252+
foreach (var actualStage in actualStages)
253+
{
254+
actual.AddRange(actualStage);
255+
}
256+
257+
var expected = BsonDocument.Parse(expectedStages);
258+
259+
actual.Should().Be(expected);
260+
}
261+
178262
private IEnumerable<BsonDocument> Execute<T>(IMongoQueryable<T> queryable)
179263
{
180264
var result = (AggregateQueryableExecutionModel<T>)queryable.GetExecutionModel();

0 commit comments

Comments
 (0)