Skip to content

Commit fc8f926

Browse files
CSHARP-2801: Nested .Any() behavior changed in 2.8.0.
1 parent 063440c commit fc8f926

File tree

3 files changed

+122
-10
lines changed

3 files changed

+122
-10
lines changed

src/MongoDB.Driver/Linq/Translators/PredicateTranslator.cs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,29 +1028,27 @@ private FilterDefinition<BsonDocument> TranslatePipelineContains(PipelineExpress
10281028
{
10291029
var value = ((ContainsResultOperator)node.ResultOperator).Value;
10301030
var constantExpression = node.Source as ConstantExpression;
1031-
IFieldExpression field;
10321031
if (constantExpression != null)
10331032
{
1034-
field = value as IFieldExpression;
1035-
if (field != null)
1033+
if (TryGetFieldNameAndSerializationExpression(value, out var fieldName, out var serializationExpression))
10361034
{
1037-
var ienumerableInterfaceType = constantExpression.Type.FindIEnumerable();
1038-
var itemType = ienumerableInterfaceType.GetTypeInfo().GetGenericArguments()[0];
1039-
var serializedValues = field.SerializeValues(itemType, (IEnumerable)constantExpression.Value);
1040-
if (string.IsNullOrEmpty(field.FieldName))
1035+
var iEnumerableInterfaceType = constantExpression.Type.FindIEnumerable();
1036+
var itemType = iEnumerableInterfaceType.GetTypeInfo().GetGenericArguments()[0];
1037+
var serializedValues = serializationExpression.SerializeValues(itemType, (IEnumerable)constantExpression.Value);
1038+
if (string.IsNullOrEmpty(fieldName))
10411039
{
10421040
return new BsonDocument("$in", serializedValues);
10431041
}
10441042
else
10451043
{
1046-
return __builder.In(field.FieldName, serializedValues);
1044+
return __builder.In(fieldName, serializedValues);
10471045
}
10481046
}
10491047
}
10501048
else
10511049
{
10521050
constantExpression = value as ConstantExpression;
1053-
field = node.Source as IFieldExpression;
1051+
var field = node.Source as IFieldExpression;
10541052
if (constantExpression != null && field != null)
10551053
{
10561054
var arraySerializer = field.Serializer as IBsonArraySerializer;
@@ -1064,6 +1062,29 @@ private FilterDefinition<BsonDocument> TranslatePipelineContains(PipelineExpress
10641062
}
10651063

10661064
return null;
1065+
1066+
bool TryGetFieldNameAndSerializationExpression(Expression containsResultOperatorValue, out string fieldName, out ISerializationExpression serializationExpression)
1067+
{
1068+
fieldName = null;
1069+
serializationExpression = containsResultOperatorValue as ISerializationExpression;
1070+
switch (serializationExpression)
1071+
{
1072+
case IFieldExpression fieldExpression:
1073+
fieldName = fieldExpression.FieldName;
1074+
break;
1075+
case DocumentExpression _:
1076+
fieldName = string.Empty;
1077+
break;
1078+
}
1079+
1080+
var success = serializationExpression != null && fieldName != null;
1081+
if (!success)
1082+
{
1083+
serializationExpression = null;
1084+
}
1085+
1086+
return success;
1087+
}
10671088
}
10681089

10691090
private FilterDefinition<BsonDocument> TranslateStringIndexOfQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
@@ -1704,6 +1725,7 @@ private void ValidatePipelineExpressionThrowIfNotValid(WhereExpression whereExpr
17041725
}
17051726

17061727
// nested types
1728+
// This converter replaces all DocumentExpression nodes on FieldExpression at the current nesting level
17071729
private class DocumentToFieldConverter : ExtensionExpressionVisitor
17081730
{
17091731
public static Expression Convert(Expression node)
@@ -1716,6 +1738,11 @@ protected internal override Expression VisitDocument(DocumentExpression node)
17161738
{
17171739
return new FieldExpression("", node.Serializer);
17181740
}
1741+
1742+
protected internal override Expression VisitPipeline(PipelineExpression node)
1743+
{
1744+
return node;
1745+
}
17191746
}
17201747
}
17211748
}

tests/MongoDB.Driver.Tests/Linq/IntegrationTestBase.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,19 @@ private void InsertSecond()
217217
F = 333,
218218
H = 444,
219219
I = new [] { "igloo" }
220+
},
221+
X = new E[]
222+
{
223+
new E()
224+
{
225+
S = "value 1",
226+
C = new C()
227+
{
228+
D = "value 2",
229+
Ids = new [] { new ObjectId("222222222222222222222222") }
230+
},
231+
I = new [] { "value 3" }
232+
}
220233
}
221234
},
222235
new C
@@ -343,6 +356,7 @@ public class E
343356
public int F { get; set; }
344357

345358
public int H { get; set; }
359+
public string S { get; set; }
346360

347361
public IEnumerable<string> I { get; set; }
348362
public C C { get; set; }

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

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public void Any_with_a_predicate_on_documents_itself_and_ClassEquals()
167167
Assert(
168168
x => x.G.Any(g => g == c1),
169169
1,
170-
"{ \"G\" : { \"$elemMatch\" : { \"Ids\" : null, \"D\" : \"Dolphin\", \"E\" : { \"F\" : 55, \"H\" : 66, \"I\" : [\"insecure\"], \"C\" : null }, \"S\" : null, \"X\" : null, \"Y\" : null, \"Z\" : null } } }");
170+
"{ \"G\" : { \"$elemMatch\" : { \"Ids\" : null, \"D\" : \"Dolphin\", \"E\" : { \"F\" : 55, \"H\" : 66, \"S\": null, \"I\" : [\"insecure\"], \"C\" : null }, \"S\" : null, \"X\" : null, \"Y\" : null, \"Z\" : null } } }");
171171
}
172172

173173
[Fact]
@@ -312,6 +312,77 @@ public void Any_with_advanced_nested_Anys()
312312
} } }");
313313
}
314314

315+
[Fact]
316+
public void Any_with_advanced_nested_Anys_and_contains()
317+
{
318+
Assert(
319+
r => r.G != null && r.G.Any(
320+
g => g.X != null && g.X.Any(
321+
x => x.I.Contains("value 3"))),
322+
1,
323+
"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null, '$elemMatch' : { 'I' : 'value 3' } } } } }");
324+
325+
Assert(
326+
r => r.G != null && r.G.Any(
327+
g => g.X != null && g.X.Any(
328+
x => x.C.Ids.Contains(new ObjectId("222222222222222222222222")))),
329+
1,
330+
"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null, '$elemMatch' : { 'C.Ids' : ObjectId('222222222222222222222222') } } } } }");
331+
}
332+
333+
[Fact]
334+
public void Any_with_advanced_nested_Anys_and_endwith()
335+
{
336+
Assert(
337+
r => r.G != null && r.G.Any(
338+
g => g.X != null && g.X.Any(
339+
x => x.S.EndsWith("lue 1"))),
340+
1,
341+
@"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null }, 'X.S' : /lue\ 1$/s } } }");
342+
343+
Assert(
344+
r => r.G != null && r.G.Any(
345+
g => g.X != null && g.X.Any(
346+
x => x.C.D.EndsWith("lue 2"))),
347+
1,
348+
@"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null }, 'X.C.D' : /lue\ 2$/s } } }");
349+
}
350+
351+
[Fact]
352+
public void Any_with_advanced_nested_Anys_and_regex()
353+
{
354+
var regex = new Regex("^value");
355+
Assert(
356+
r => r.G != null && r.G.Any(
357+
g => g.X != null && g.X.Any(x => regex.IsMatch(x.S))),
358+
1,
359+
"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null, '$elemMatch' : { 'S' : /^value/ } } } } }");
360+
361+
Assert(
362+
r => r.G != null && r.G.Any(
363+
g => g.X != null && g.X.Any(x => regex.IsMatch(x.C.D))),
364+
1,
365+
"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null, '$elemMatch' : { 'C.D' : /^value/ } } } } }");
366+
}
367+
368+
[Fact]
369+
public void Any_with_advanced_nested_Anys_and_startwith()
370+
{
371+
Assert(
372+
r => r.G != null && r.G.Any(
373+
g => g.X != null && g.X.Any(
374+
x => x.S.StartsWith("value"))),
375+
1,
376+
"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null }, 'X.S' : /^value/s } } }");
377+
378+
Assert(
379+
r => r.G != null && r.G.Any(
380+
g => g.X != null && g.X.Any(
381+
x => x.C.D.StartsWith("value"))),
382+
1,
383+
"{ G : { '$ne' : null, '$elemMatch' : { 'X' : { '$ne' : null }, 'X.C.D' : /^value/s } } }");
384+
}
385+
315386
[Fact]
316387
public void Any_with_a_nested_Any()
317388
{

0 commit comments

Comments
 (0)