Skip to content

Commit e47850f

Browse files
CSHARP-2046, CSHARP-2158: Fix SelectMany workflow within consecutive calls and incorrect $unwind query when using SelectMany in subobjects.
1 parent cc9ba1b commit e47850f

File tree

3 files changed

+245
-11
lines changed

3 files changed

+245
-11
lines changed

src/MongoDB.Driver/Linq/Processors/Pipeline/MethodCallBinders/SelectManyBinder.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ public Expression Bind(PipelineExpression pipeline, PipelineBindingContext bindi
6262
{
6363
var resultLambda = ExpressionHelper.GetLambda(arguments.Last());
6464
bindingContext.AddExpressionMapping(resultLambda.Parameters[0], pipeline.Projector);
65-
bindingContext.AddExpressionMapping(
66-
resultLambda.Parameters[1],
67-
new FieldExpression(collectionField.FieldName, itemSerializationInfo.Serializer));
65+
66+
var fieldExpression = PrepareFieldExpression(collectionField, itemSerializationInfo.Serializer, bindingContext);
67+
bindingContext.AddExpressionMapping(resultLambda.Parameters[1], fieldExpression);
6868

6969
resultItemName = resultLambda.Parameters[1].Name;
7070
resultSelector = bindingContext.Bind(resultLambda.Body);
7171
}
7272
else
7373
{
7474
resultItemName = "__p";
75-
resultSelector = new FieldExpression(collectionField.FieldName, itemSerializationInfo.Serializer);
75+
resultSelector = PrepareFieldExpression(collectionField, itemSerializationInfo.Serializer, bindingContext);
7676
}
7777

7878
var projector = bindingContext.BindProjector(ref resultSelector);
@@ -117,5 +117,16 @@ private static IFieldExpression GetCollectionField(Expression collectionSelector
117117

118118
return null;
119119
}
120+
121+
private FieldExpression PrepareFieldExpression(IFieldExpression collectionField, IBsonSerializer serializer, PipelineBindingContext bindingContext)
122+
{
123+
// Determine whether we generate a query based on a document itself (when a `Document` type is `DocumentExpression`)
124+
// or the result of previous steps (the `Document` type is `FieldExpression`).
125+
var parentFieldExpression = collectionField.Document as FieldExpression;
126+
127+
return parentFieldExpression == null
128+
? new FieldExpression(collectionField.FieldName, serializer)
129+
: new FieldExpression(new FieldExpression(parentFieldExpression.FieldName, parentFieldExpression.Serializer), collectionField.FieldName, serializer);
130+
}
120131
}
121-
}
132+
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
using MongoDB.Bson.Serialization.Serializers;
2424
using MongoDB.Driver.Core.Misc;
2525
using MongoDB.Driver.Linq.Expressions;
26-
using MongoDB.Driver.Linq.Expressions.ResultOperators;
2726
using MongoDB.Driver.Support;
2827

2928
namespace MongoDB.Driver.Linq.Translators
@@ -327,7 +326,17 @@ private void TranslateSelectMany(SelectManyExpression node)
327326
throw new NotSupportedException(message);
328327
}
329328

330-
BsonValue unwindValue = "$" + field.FieldName;
329+
BsonValue unwindValue;
330+
var parentFieldExpression = field.Document as IFieldExpression;
331+
if (parentFieldExpression != null)
332+
{
333+
unwindValue = $"${parentFieldExpression.FieldName}.{field.FieldName}";
334+
}
335+
else
336+
{
337+
unwindValue = $"${field.FieldName}";
338+
}
339+
331340
var groupJoin = node.Source as GroupJoinExpression;
332341
if (groupJoin != null && isLeftOuterJoin)
333342
{
@@ -392,4 +401,4 @@ private BsonDocument TranslateProjectValue(Expression selector)
392401
return projectValue;
393402
}
394403
}
395-
}
404+
}

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

Lines changed: 217 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,10 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq;
19-
using System.Linq.Expressions;
2019
using System.Threading.Tasks;
2120
using FluentAssertions;
2221
using MongoDB.Bson;
23-
using MongoDB.Bson.TestHelpers.XunitExtensions;
2422
using MongoDB.Driver;
25-
using MongoDB.Driver.Core;
2623
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
2724
using MongoDB.Driver.Linq;
2825
using MongoDB.Driver.Tests.Linq;
@@ -1148,6 +1145,61 @@ public void SelectMany_with_only_resultSelector()
11481145
"{ $project: { G: '$G', _id: 0 } }");
11491146
}
11501147

1148+
[Fact]
1149+
public void SelectMany_with_result_selector_which_has_subobjects()
1150+
{
1151+
var query = CreateQuery()
1152+
.SelectMany(x => x.C.X);
1153+
1154+
Assert(query,
1155+
2,
1156+
"{ $unwind : '$C.X' }",
1157+
"{ $project : { X : '$C.X', _id : 0 } }");
1158+
}
1159+
1160+
[Fact]
1161+
public void SelectMany_with_result_selector_which_is_called_from_SelectMany()
1162+
{
1163+
var cQuery = CreateQuery()
1164+
.SelectMany(g => g.G)
1165+
.SelectMany(s => s.S);
1166+
1167+
Assert(cQuery,
1168+
1,
1169+
"{ $unwind : '$G' }",
1170+
"{ $project : { G : '$G', _id : 0 } }",
1171+
"{ $unwind : '$G.S' }",
1172+
"{ $project : { S : '$G.S', _id : 0 } }");
1173+
1174+
var xQuery = CreateQuery()
1175+
.SelectMany(g => g.G)
1176+
.SelectMany(s => s.S)
1177+
.SelectMany(x => x.X);
1178+
1179+
Assert(xQuery,
1180+
0,
1181+
"{ $unwind : '$G' }",
1182+
"{ $project : { G : '$G', _id : 0 } }",
1183+
"{ $unwind : '$G.S' }",
1184+
"{ $project : { S : '$G.S', _id : 0 } }",
1185+
"{ $unwind : '$S.X' }",
1186+
"{ $project : { X : '$S.X', _id : 0 } }");
1187+
}
1188+
1189+
[Fact]
1190+
public void SelectMany_with_result_selector_which_called_from_where()
1191+
{
1192+
var query = CreateQuery()
1193+
.Where(c => c.K)
1194+
.SelectMany(x => x.G);
1195+
1196+
Assert(query,
1197+
2,
1198+
"{ $match : { 'K' : true } }",
1199+
"{ $unwind : '$G' }",
1200+
"{ $project : { G : '$G', _id : 0 } }");
1201+
}
1202+
11511203
[Fact]
11521204
public void SelectMany_with_collection_selector_method_simple_scalar()
11531205
{
@@ -1160,6 +1212,57 @@ public void SelectMany_with_collection_selector_method_simple_scalar()
11601212
"{ $project: { G: '$G', _id: 0 } }");
11611213
}
11621214

1215+
[Fact]
1216+
public void SelectMany_with_collection_selector_method_simple_scalar_which_is_called_from_SelectMany()
1217+
{
1218+
var cQuery = CreateQuery()
1219+
.SelectMany(g => g.G, (x, c) => c)
1220+
.SelectMany(s => s.S);
1221+
1222+
Assert(cQuery,
1223+
1,
1224+
"{ $unwind : '$G' }",
1225+
"{ $project : { G : '$G', _id : 0 } }",
1226+
"{ $unwind : '$G.S' }",
1227+
"{ $project : { S : '$G.S', _id : 0 } }");
1228+
1229+
cQuery = CreateQuery()
1230+
.SelectMany(g => g.G)
1231+
.SelectMany(s => s.S, (x, c) => c);
1232+
1233+
Assert(cQuery,
1234+
1,
1235+
"{ $unwind : '$G' }",
1236+
"{ $project : { G : '$G', _id : 0 } }",
1237+
"{ $unwind : '$G.S' }",
1238+
"{ $project : { S : '$G.S', _id : 0 } }");
1239+
1240+
cQuery = CreateQuery()
1241+
.SelectMany(g => g.G, (x, c) => c)
1242+
.SelectMany(s => s.S, (x ,c) => c);
1243+
1244+
Assert(cQuery,
1245+
1,
1246+
"{ $unwind : '$G' }",
1247+
"{ $project : { G : '$G', _id : 0 } }",
1248+
"{ $unwind : '$G.S' }",
1249+
"{ $project : { S : '$G.S', _id : 0 } }");
1250+
1251+
var xQuery = CreateQuery()
1252+
.SelectMany(g => g.G, (x, c) => c)
1253+
.SelectMany(s => s.S, (x, c) => c)
1254+
.SelectMany(x => x.X, (x, c) => c);
1255+
1256+
Assert(xQuery,
1257+
0,
1258+
"{ $unwind : '$G' }",
1259+
"{ $project : { G : '$G', _id : 0 } }",
1260+
"{ $unwind : '$G.S' }",
1261+
"{ $project : { S : '$G.S', _id : 0 } }",
1262+
"{ $unwind : '$S.X' }",
1263+
"{ $project : { X : '$S.X', _id : 0 } }");
1264+
}
1265+
11631266
[Fact]
11641267
public void SelectMany_with_collection_selector_syntax_simple_scalar()
11651268
{
@@ -1173,6 +1276,24 @@ from y in x.G
11731276
"{ $project: { G: '$G', _id: 0 } }");
11741277
}
11751278

1279+
[Fact]
1280+
public void SelectMany_with_collection_selector_syntax_simple_scalar_which_is_called_from_SelectMany()
1281+
{
1282+
var selectMany1 = from x in CreateQuery()
1283+
from g in x.G
1284+
select g;
1285+
var selectMany2 = from g in selectMany1
1286+
from s in g.S
1287+
select s;
1288+
1289+
Assert(selectMany2,
1290+
1,
1291+
"{ $unwind : '$G' }",
1292+
"{ $project : { G : '$G', _id : 0 } }",
1293+
"{ $unwind : '$G.S' }",
1294+
"{ $project : { S : '$G.S', _id : 0 } }");
1295+
}
1296+
11761297
[Fact]
11771298
public void SelectMany_with_collection_selector_method_computed_scalar()
11781299
{
@@ -1185,6 +1306,21 @@ public void SelectMany_with_collection_selector_method_computed_scalar()
11851306
"{ $project: { __fld0: { $add: ['$C.E.F', '$G.E.F', '$G.E.H'] }, _id: 0 } }");
11861307
}
11871308

1309+
[Fact]
1310+
public void SelectMany_with_collection_selector_method_computed_scalar_which_is_called_from_SelectMany()
1311+
{
1312+
var query = CreateQuery()
1313+
.SelectMany(g => g.G)
1314+
.SelectMany(s => s.S, (x, c) => (int?)(x.E.F + c.E.F + c.E.H));
1315+
1316+
Assert(query,
1317+
1,
1318+
"{ $unwind : '$G' }",
1319+
"{ $project : { G : '$G', _id : 0 } }",
1320+
"{ $unwind : '$G.S' }",
1321+
"{ $project : { __fld0 : { $add : ['$G.E.F', '$G.S.E.F', '$G.S.E.H'] }, _id : 0 } }");
1322+
}
1323+
11881324
[Fact]
11891325
public void SelectMany_with_collection_selector_syntax_computed_scalar()
11901326
{
@@ -1198,6 +1334,24 @@ from y in x.G
11981334
"{ $project: { __fld0: { $add: ['$C.E.F', '$G.E.F', '$G.E.H'] }, _id: 0 } }");
11991335
}
12001336

1337+
[Fact]
1338+
public void SelectMany_with_collection_selector_syntax_computed_scalar_which_is_called_from_SelectMany()
1339+
{
1340+
var selectMany1 = from x in CreateQuery()
1341+
from g in x.G
1342+
select g;
1343+
var selectMany2 = from g in selectMany1
1344+
from s in g.S
1345+
select (int?)(g.E.F + s.E.F + s.E.H);
1346+
1347+
Assert(selectMany2,
1348+
1,
1349+
"{ $unwind : '$G' }",
1350+
"{ $project : { G : '$G', _id : 0 } }",
1351+
"{ $unwind : '$G.S' }",
1352+
"{ $project: { __fld0 : { $add : ['$G.E.F', '$G.S.E.F', '$G.S.E.H'] }, _id : 0 } }");
1353+
}
1354+
12011355
[Fact]
12021356
public void SelectMany_with_collection_selector_method_anonymous_type()
12031357
{
@@ -1210,6 +1364,21 @@ public void SelectMany_with_collection_selector_method_anonymous_type()
12101364
"{ $project: { F: '$C.E.F', Other: '$G.D', _id: 0 } }");
12111365
}
12121366

1367+
[Fact]
1368+
public void SelectMany_with_collection_selector_method_anonymous_type_which_is_called_from_SelectMany()
1369+
{
1370+
var query = CreateQuery()
1371+
.SelectMany(g => g.G)
1372+
.SelectMany(s => s.S, (x, c) => new { x.E.F, Other = c.D });
1373+
1374+
Assert(query,
1375+
1,
1376+
"{ $unwind : '$G' }",
1377+
"{ $project : { G : '$G', _id : 0 } }",
1378+
"{ $unwind : '$G.S' }",
1379+
"{ $project : { F : '$G.E.F', Other : '$G.S.D', _id : 0 } }");
1380+
}
1381+
12131382
[Fact]
12141383
public void SelectMany_with_collection_selector_syntax_anonymous_type()
12151384
{
@@ -1223,6 +1392,24 @@ from y in x.G
12231392
"{ $project: { F: '$C.E.F', Other: '$G.D', _id: 0 } }");
12241393
}
12251394

1395+
[Fact]
1396+
public void SelectMany_with_collection_selector_syntax_anonymous_type_which_is_called_from_SelectMany()
1397+
{
1398+
var selectMany1 = from x in CreateQuery()
1399+
from g in x.G
1400+
select g;
1401+
var selectMany2 = from g in selectMany1
1402+
from s in g.S
1403+
select new { g.E.F, Other = s.D };
1404+
1405+
Assert(selectMany2,
1406+
1,
1407+
"{ $unwind : '$G' }",
1408+
"{ $project : { G : '$G', _id : 0 } }",
1409+
"{ $unwind : '$G.S' }",
1410+
"{ $project : { F : '$G.E.F', Other : '$G.S.D', _id : 0 } }");
1411+
}
1412+
12261413
[Fact]
12271414
public void SelectMany_followed_by_a_group()
12281415
{
@@ -1246,6 +1433,33 @@ group f by f.D into g
12461433
"{ $project: { Key: '$_id', SumF: '$__agg0', _id: 0 } }");
12471434
}
12481435

1436+
[Fact]
1437+
public void SelectMany_followed_by_a_group_which_is_called_from_SelectMany()
1438+
{
1439+
var selectMany1 = from x in CreateQuery()
1440+
from g in x.G
1441+
select g;
1442+
var selectMany2 = from g in selectMany1
1443+
from s in g.S
1444+
select s;
1445+
var query = from s in selectMany2
1446+
group s by s.D into g
1447+
select new
1448+
{
1449+
g.Key,
1450+
SumF = g.Sum(x => x.E.F)
1451+
};
1452+
1453+
Assert(query,
1454+
1,
1455+
"{ $unwind : '$G' }",
1456+
"{ $project : { G: '$G', _id : 0 } }",
1457+
"{ $unwind : '$G.S' }",
1458+
"{ $project : { 'S' : '$G.S', '_id' : 0 } }",
1459+
"{ $group : { _id : '$S.D', __agg0 : { $sum : '$S.E.F' } } }",
1460+
"{ $project : { Key : '$_id', SumF : '$__agg0', _id : 0 } }");
1461+
}
1462+
12491463
[Fact]
12501464
public void Single()
12511465
{

0 commit comments

Comments
 (0)