Skip to content

Commit 0a210b5

Browse files
committed
CSHARP-2723: Verify that this scenario works in LINQ3.
1 parent 9dbfbaa commit 0a210b5

File tree

3 files changed

+302
-5
lines changed

3 files changed

+302
-5
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,22 @@ internal override RenderedFieldDefinition TranslateExpressionToField<TDocument>(
7373
var context = TranslationContext.Create(expression, documentSerializer);
7474
var symbol = context.CreateSymbol(parameter, documentSerializer, isCurrent: true);
7575
context = context.WithSymbol(symbol);
76-
var field = ExpressionToFilterFieldTranslator.Translate(context, expression.Body);
76+
var body = RemovePossibleConvertToObject(expression.Body);
77+
var field = ExpressionToFilterFieldTranslator.Translate(context, body);
7778

7879
return new RenderedFieldDefinition(field.Path, field.Serializer);
80+
81+
static Expression RemovePossibleConvertToObject(Expression expression)
82+
{
83+
if (expression is UnaryExpression unaryExpression &&
84+
unaryExpression.NodeType == ExpressionType.Convert &&
85+
unaryExpression.Type == typeof(object))
86+
{
87+
return unaryExpression.Operand;
88+
}
89+
90+
return expression;
91+
}
7992
}
8093

8194
internal override RenderedFieldDefinition<TField> TranslateExpressionToField<TDocument, TField>(
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections.Generic;
17+
using System.Linq;
18+
using FluentAssertions;
19+
using MongoDB.Bson;
20+
using MongoDB.Driver.Linq;
21+
using Xunit;
22+
23+
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira
24+
{
25+
public class CSharp2723Tests
26+
{
27+
[Fact]
28+
public void Nested_Select_should_work()
29+
{
30+
var client = DriverTestConfiguration.Linq3Client;
31+
var database = client.GetDatabase("foo");
32+
var parents = database.GetCollection<Parent>("parents");
33+
var children = database.GetCollection<Child>("children");
34+
var grandChildren = database.GetCollection<GrandChild>("grandChildren");
35+
36+
database.DropCollection("parents");
37+
database.DropCollection("children");
38+
database.DropCollection("grandChildren");
39+
40+
parents.InsertMany(new[]
41+
{
42+
new Parent { Id = 1, Name = "parent1" },
43+
new Parent { Id = 2, Name = "parent2" }
44+
});
45+
46+
children.InsertMany(new[]
47+
{
48+
new Child { Id = 1, Name = "child1", ParentId = 2 },
49+
new Child { Id = 2, Name = "child2", ParentId = 1 }
50+
});
51+
52+
grandChildren.InsertMany(new[]
53+
{
54+
new GrandChild { Id = 1, Name = "grandchild1", ChildId = 2 },
55+
new GrandChild { Id = 2, Name = "grandchild2", ChildId = 1 }
56+
});
57+
58+
var aggregate = parents
59+
.Aggregate()
60+
.Lookup<Parent, Child, ParentProjection>(
61+
children,
62+
parent => parent.Id,
63+
child => child.ParentId,
64+
parentProjection => parentProjection.Children)
65+
.Lookup<ParentProjection, GrandChild, GrandChild, IEnumerable<GrandChild>, ParentProjection>(
66+
grandChildren,
67+
let: new BsonDocument { { "children", "$Children" } },
68+
lookupPipeline: new BsonDocumentStagePipelineDefinition<GrandChild, GrandChild>(
69+
new[] { BsonDocument.Parse(@"{ $match : { $expr : { $and : [ { $in : [ ""$ChildId"", ""$$children._id"" ] } ] } } }") }),
70+
childProjection => childProjection.GrandChildren)
71+
.Project(parent => new ParentTree
72+
{
73+
Id = parent.Id,
74+
ParentName = parent.Name,
75+
Children = parent.Children
76+
.Select(child => new ChildTree
77+
{
78+
Id = child.Id,
79+
Name = child.Name,
80+
GrandChildren = parent
81+
.GrandChildren
82+
.Where(gc =>
83+
gc.ChildId ==
84+
child.Id)
85+
.Select(gc =>
86+
new GrandChildProjection()
87+
{
88+
Id = gc.Id,
89+
Name = gc.Name
90+
})
91+
})
92+
});
93+
94+
var stages = Linq3TestHelpers.Translate(parents, aggregate);
95+
var expectedStages = new[]
96+
{
97+
@"{
98+
'$lookup':{
99+
'from':'children',
100+
'localField':'_id',
101+
'foreignField':'ParentId',
102+
'as':'Children'
103+
}
104+
}",
105+
@"{
106+
'$lookup':{
107+
'from':'grandChildren',
108+
'let':{'children':'$Children'},
109+
'pipeline':[
110+
{
111+
'$match':{'$expr':{'$and':[{'$in':['$ChildId','$$children._id']}]}}}
112+
],
113+
'as':'GrandChildren'
114+
}
115+
}",
116+
@"{
117+
'$project':{
118+
'_id':'$_id',
119+
'ParentName':'$Name',
120+
'Children':{
121+
'$map':{
122+
'input':'$Children',
123+
'as':'child',
124+
'in':{
125+
'_id':'$$child._id',
126+
'Name':'$$child.Name',
127+
'GrandChildren':{
128+
'$map':{
129+
'input':{
130+
'$filter':{
131+
'input':'$GrandChildren',
132+
'as':'gc',
133+
'cond':{
134+
'$eq':[
135+
'$$gc.ChildId',
136+
'$$child._id'
137+
]
138+
}
139+
}
140+
},
141+
'as':'gc',
142+
'in':{
143+
'_id':'$$gc._id',
144+
'Name':'$$gc.Name'
145+
}
146+
}
147+
}
148+
}
149+
}
150+
}
151+
}
152+
}"
153+
};
154+
Linq3TestHelpers.AssertStages(stages, expectedStages);
155+
156+
var pipelineDefinition = new BsonDocumentStagePipelineDefinition<Parent, BsonDocument>(expectedStages.Select(s => BsonDocument.Parse(s)));
157+
var resultBsonDocuments = parents.Aggregate(pipelineDefinition).ToList();
158+
var expectedBsonDocuments = new[]
159+
{
160+
"{ '_id' : 1, 'ParentName' : 'parent1', 'Children' : [{ '_id' : 2, 'Name' : 'child2', 'GrandChildren' : [{ '_id' : 1, 'Name' : 'grandchild1' }] }] }",
161+
"{ '_id' : 2, 'ParentName' : 'parent2', 'Children' : [{ '_id' : 1, 'Name' : 'child1', 'GrandChildren' : [{ '_id' : 2, 'Name' : 'grandchild2' }] }] }"
162+
};
163+
resultBsonDocuments.Should().Equal(expectedBsonDocuments.Select(d => BsonDocument.Parse(d)));
164+
165+
var result = aggregate.ToList();
166+
var expectedResults = new[]
167+
{
168+
new ParentTree { Id = 1, ParentName = "parent1", Children = new[] { new ChildTree { Id = 2, Name = "child2", GrandChildren = new[] { new GrandChildProjection { Id = 1, Name = "grandchild1" } } } } },
169+
new ParentTree { Id = 2, ParentName = "parent2", Children = new[] { new ChildTree { Id = 1, Name = "child1", GrandChildren = new[] { new GrandChildProjection { Id = 2, Name = "grandchild2" } } } } }
170+
};
171+
var parentTreeComparer = new ParentTreeComparer();
172+
result.Should().Equal(expectedResults, (x, y) => parentTreeComparer.Equals(x, y));
173+
}
174+
175+
public class Parent
176+
{
177+
public int Id { get; set; }
178+
public string Name { get; set; }
179+
}
180+
181+
public class Child
182+
{
183+
public int Id { get; set; }
184+
public string Name { get; set; }
185+
public int ParentId { get; set; }
186+
}
187+
188+
public class GrandChild
189+
{
190+
public int Id { get; set; }
191+
public string Name { get; set; }
192+
public int ChildId { get; set; }
193+
}
194+
195+
public class ParentProjection
196+
{
197+
public int Id { get; set; }
198+
public string Name { get; set; }
199+
public IEnumerable<Child> Children { get; set; }
200+
public IEnumerable<GrandChild> GrandChildren { get; set; }
201+
}
202+
203+
public class GrandChildProjection
204+
{
205+
public int Id { get; set; }
206+
public string Name { get; set; }
207+
}
208+
209+
public class ParentTree
210+
{
211+
public int Id { get; set; }
212+
public string ParentName { get; set; }
213+
public IEnumerable<ChildTree> Children { get; set; }
214+
}
215+
216+
public class ChildTree
217+
{
218+
public int Id { get; set; }
219+
public string Name { get; set; }
220+
public IEnumerable<GrandChildProjection> GrandChildren { get; set; }
221+
}
222+
223+
private class ParentTreeComparer : IEqualityComparer<ParentTree>
224+
{
225+
private readonly IEqualityComparer<ChildTree> _childTreeComparer = new ChildTreeComparer();
226+
227+
public bool Equals(ParentTree x, ParentTree y)
228+
{
229+
return
230+
x.Id == y.Id &&
231+
x.ParentName == y.ParentName &&
232+
x.Children.SequenceEqual(y.Children, _childTreeComparer);
233+
}
234+
235+
public int GetHashCode(ParentTree obj) => throw new System.NotImplementedException();
236+
}
237+
238+
private class ChildTreeComparer : IEqualityComparer<ChildTree>
239+
{
240+
private readonly IEqualityComparer<GrandChildProjection> _grandChildProjectionComparer = new GrandChildProjectionComparer();
241+
242+
public bool Equals(ChildTree x, ChildTree y)
243+
{
244+
return
245+
x.Id == y.Id &&
246+
x.Name == y.Name &&
247+
x.GrandChildren.SequenceEqual(y.GrandChildren, _grandChildProjectionComparer);
248+
}
249+
250+
public int GetHashCode(ChildTree obj) => throw new System.NotImplementedException();
251+
}
252+
253+
private class GrandChildProjectionComparer : IEqualityComparer<GrandChildProjection>
254+
{
255+
public bool Equals(GrandChildProjection x, GrandChildProjection y)
256+
{
257+
return
258+
x.Id == y.Id &&
259+
x.Name == y.Name;
260+
}
261+
262+
public int GetHashCode(GrandChildProjection obj) => throw new System.NotImplementedException();
263+
}
264+
}
265+
}

tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Linq3TestHelpers.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,52 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System.Collections.Generic;
1617
using System.Linq;
1718
using FluentAssertions;
1819
using MongoDB.Bson;
20+
using MongoDB.Bson.Serialization;
21+
using MongoDB.Driver.Linq;
1922
using MongoDB.Driver.Linq.Linq3Implementation;
2023
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators;
2124

2225
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests
2326
{
2427
public static class Linq3TestHelpers
2528
{
26-
public static void AssertStages(BsonDocument[] stages, string[] expectedStages)
29+
public static void AssertStages(IEnumerable<BsonDocument> stages, IEnumerable<string> expectedStages)
2730
{
2831
stages.Should().Equal(expectedStages.Select(json => BsonDocument.Parse(json)));
2932
}
3033

34+
public static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IAggregateFluent<TResult> aggregate)
35+
{
36+
var renderedStages = new List<BsonDocument>();
37+
38+
IBsonSerializer inputSerializer = collection.DocumentSerializer;
39+
var serializerRegistry = BsonSerializer.SerializerRegistry;
40+
foreach (var stage in aggregate.Stages)
41+
{
42+
var renderedStage = stage.Render(inputSerializer, serializerRegistry, LinqProvider.V3);
43+
renderedStages.Add(renderedStage.Document);
44+
inputSerializer = renderedStage.OutputSerializer;
45+
}
46+
47+
return renderedStages;
48+
}
49+
3150
// in this overload the collection argument is used only to infer the TDocument type
32-
public static BsonDocument[] Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IQueryable<TResult> queryable)
51+
public static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IQueryable<TResult> queryable)
3352
{
3453
return Translate<TDocument, TResult>(queryable);
3554
}
3655

37-
public static BsonDocument[] Translate<TDocument, TResult>(IQueryable<TResult> queryable)
56+
public static List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable)
3857
{
3958
var provider = (MongoQueryProvider<TDocument>)queryable.Provider;
4059
var executableQuery = ExpressionToExecutableQueryTranslator.Translate<TDocument, TResult>(provider, queryable.Expression);
4160
var stages = executableQuery.Pipeline.Stages;
42-
return stages.Select(s => s.Render().AsBsonDocument).ToArray();
61+
return stages.Select(s => s.Render().AsBsonDocument).ToList();
4362
}
4463
}
4564
}

0 commit comments

Comments
 (0)