Skip to content

Commit eafd5ab

Browse files
committed
CSHARP-1913: Support top-level SelectMany with Dictionary.
1 parent eaa2eae commit eafd5ab

File tree

4 files changed

+191
-21
lines changed

4 files changed

+191
-21
lines changed

src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -499,21 +499,20 @@ obj is DictionarySerializerBase<TDictionary, TKey, TValue> other &&
499499
/// <inheritdoc/>
500500
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo)
501501
{
502-
if (_dictionaryRepresentation != DictionaryRepresentation.ArrayOfDocuments)
502+
if (_dictionaryRepresentation is DictionaryRepresentation.ArrayOfArrays or DictionaryRepresentation.ArrayOfDocuments)
503503
{
504-
serializationInfo = null;
505-
return false;
504+
var representation = _dictionaryRepresentation == DictionaryRepresentation.ArrayOfArrays
505+
? BsonType.Array
506+
: BsonType.Document;
507+
var keySerializer = _lazyKeySerializer.Value;
508+
var valueSerializer = _lazyValueSerializer.Value;
509+
var keyValuePairSerializer = new KeyValuePairSerializer<TKey, TValue>(representation, keySerializer, valueSerializer);
510+
serializationInfo = new BsonSerializationInfo(null, keyValuePairSerializer, keyValuePairSerializer.ValueType);
511+
return true;
506512
}
507513

508-
var serializer = new KeyValuePairSerializer<TKey, TValue>(
509-
BsonType.Document,
510-
_lazyKeySerializer.Value,
511-
_lazyValueSerializer.Value);
512-
serializationInfo = new BsonSerializationInfo(
513-
null,
514-
serializer,
515-
serializer.ValueType);
516-
return true;
514+
serializationInfo = null;
515+
return false;
517516
}
518517

519518
/// <inheritdoc/>

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ExpressionToAggregationExpressionTranslator.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
using System;
1717
using System.Linq;
1818
using System.Linq.Expressions;
19+
using MongoDB.Bson;
1920
using MongoDB.Bson.Serialization;
21+
using MongoDB.Bson.Serialization.Options;
2022
using MongoDB.Bson.Serialization.Serializers;
2123
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
2224
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
@@ -95,15 +97,28 @@ public static TranslatedExpression TranslateEnumerable(TranslationContext contex
9597
{
9698
var aggregateExpression = Translate(context, expression);
9799

98-
var serializer = aggregateExpression.Serializer;
99-
if (serializer is IWrappedEnumerableSerializer wrappedEnumerableSerializer)
100+
if (aggregateExpression.Serializer is IWrappedEnumerableSerializer wrappedEnumerableSerializer)
100101
{
101102
var enumerableFieldName = wrappedEnumerableSerializer.EnumerableFieldName;
102103
var enumerableElementSerializer = wrappedEnumerableSerializer.EnumerableElementSerializer;
103-
var enumerableSerializer = IEnumerableSerializer.Create(enumerableElementSerializer);
104+
104105
var ast = AstExpression.GetField(aggregateExpression.Ast, enumerableFieldName);
106+
var ienumerableSerializer = IEnumerableSerializer.Create(enumerableElementSerializer);
107+
108+
aggregateExpression = new TranslatedExpression(expression, ast, ienumerableSerializer);
109+
}
110+
111+
if (aggregateExpression.Serializer is IBsonDictionarySerializer dictionarySerializer &&
112+
dictionarySerializer.DictionaryRepresentation == DictionaryRepresentation.Document)
113+
{
114+
var keySerializer = dictionarySerializer.KeySerializer;
115+
var valueSerializer = dictionarySerializer.ValueSerializer;
116+
var keyValuePairSerializer = KeyValuePairSerializer.Create(BsonType.Document, keySerializer, valueSerializer);
117+
118+
var ast = AstExpression.ObjectToArray(aggregateExpression.Ast);
119+
var ienumerableSerializer = ArraySerializerHelper.CreateSerializer(keyValuePairSerializer);
105120

106-
return new TranslatedExpression(aggregateExpression.Expression, ast, enumerableSerializer);
121+
aggregateExpression = new TranslatedExpression(expression, ast, ienumerableSerializer);
107122
}
108123

109124
return aggregateExpression;

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/SelectManyMethodToPipelineTranslator.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414
*/
1515

1616
using System.Collections.ObjectModel;
17+
using System.Linq;
1718
using System.Linq.Expressions;
1819
using System.Reflection;
20+
using MongoDB.Bson;
21+
using MongoDB.Bson.Serialization;
22+
using MongoDB.Bson.Serialization.Options;
23+
using MongoDB.Bson.Serialization.Serializers;
1924
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
2025
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
2126
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages;
@@ -74,16 +79,23 @@ private static TranslatedPipeline TranslateSelectMany(
7479
{
7580
var sourceSerializer = pipeline.OutputSerializer;
7681
var selectorLambda = ExpressionHelper.UnquoteLambda(arguments[1]);
77-
var selectorTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, selectorLambda, sourceSerializer, asRoot: true);
78-
var resultValueSerializer = ArraySerializerHelper.GetItemSerializer(selectorTranslation.Serializer);
79-
var resultWrappedValueSerializer = WrappedValueSerializer.Create("_v", resultValueSerializer);
82+
var selectorParameter = selectorLambda.Parameters.Single();
83+
var selectorParameterSymbol = context.CreateSymbol(selectorParameter, sourceSerializer, isCurrent: true);
84+
var selectorContext = context.WithSymbol(selectorParameterSymbol);
85+
var selectorTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(selectorContext, selectorLambda.Body);
86+
87+
var valuesAst = selectorTranslation.Ast;
88+
var valuesSerializer = selectorTranslation.Serializer;
89+
90+
var valuesItemSerializer = ArraySerializerHelper.GetItemSerializer(valuesSerializer);
91+
var wrappedValueSerializer = WrappedValueSerializer.Create("_v", valuesItemSerializer);
8092

8193
pipeline = pipeline.AddStages(
8294
AstStage.Project(
83-
AstProject.Set("_v", selectorTranslation.Ast),
95+
AstProject.Set("_v", valuesAst),
8496
AstProject.ExcludeId()),
8597
AstStage.Unwind("_v"),
86-
resultWrappedValueSerializer);
98+
wrappedValueSerializer);
8799

88100
return pipeline;
89101
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 MongoDB.Driver.TestHelpers;
19+
using FluentAssertions;
20+
using MongoDB.Bson.Serialization.Attributes;
21+
using MongoDB.Bson.Serialization.Options;
22+
using Xunit;
23+
24+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira;
25+
26+
public class CSharp1913Tests : LinqIntegrationTest<CSharp1913Tests.ClassFixture>
27+
{
28+
public CSharp1913Tests(ClassFixture fixture)
29+
: base(fixture)
30+
{
31+
}
32+
33+
[Fact]
34+
public void SelectMany_with_ArrayOfArrays_representation_should_work()
35+
{
36+
var collection = Fixture.Collection;
37+
38+
var queryable = collection.AsQueryable()
39+
.OfType<C>()
40+
.Where( to => to.Name == "TestName" )
41+
.SelectMany( to => to.DictionaryWithArrayOfArraysRepresentation );
42+
43+
var stages = Translate(collection, queryable);
44+
AssertStages(
45+
stages,
46+
"{ $match : { Name : 'TestName' } }",
47+
"{ $project : { _v : '$DictionaryWithArrayOfArraysRepresentation', _id : 0 } }",
48+
"{ $unwind : '$_v' }");
49+
50+
var results = queryable.ToList();
51+
results.Count.Should().Be(3);
52+
results[0].Key.Should().Be("A");
53+
results[0].Value.Should().Be("a");
54+
results[1].Key.Should().Be("B");
55+
results[1].Value.Should().Be("b");
56+
results[2].Key.Should().Be("C");
57+
results[2].Value.Should().Be("c");
58+
}
59+
60+
[Fact]
61+
public void SelectMany_with_ArrayOfDocuments_representation_should_work()
62+
{
63+
var collection = Fixture.Collection;
64+
65+
var queryable = collection.AsQueryable()
66+
.OfType<C>()
67+
.Where( to => to.Name == "TestName" )
68+
.SelectMany( to => to.DictionaryWithArrayOfDocumentsRepresentation );
69+
70+
var stages = Translate(collection, queryable);
71+
AssertStages(
72+
stages,
73+
"{ $match : { Name : 'TestName' } }",
74+
"{ $project : { _v : '$DictionaryWithArrayOfDocumentsRepresentation', _id : 0 } }",
75+
"{ $unwind : '$_v' }");
76+
77+
var results = queryable.ToList();
78+
results.Count.Should().Be(3);
79+
results[0].Key.Should().Be("A");
80+
results[0].Value.Should().Be("a");
81+
results[1].Key.Should().Be("B");
82+
results[1].Value.Should().Be("b");
83+
results[2].Key.Should().Be("C");
84+
results[2].Value.Should().Be("c");
85+
}
86+
87+
[Fact]
88+
public void SelectMany_with_Document_representation_should_work()
89+
{
90+
var collection = Fixture.Collection;
91+
92+
var queryable = collection.AsQueryable()
93+
.OfType<C>()
94+
.Where( to => to.Name == "TestName" )
95+
.SelectMany( to => to.DictionaryWithDocumentRepresentation);
96+
97+
var stages = Translate(collection, queryable);
98+
AssertStages(
99+
stages,
100+
"{ $match : { Name : 'TestName' } }",
101+
"{ $project : { _v : { $objectToArray : '$DictionaryWithDocumentRepresentation' }, _id : 0 } }",
102+
"{ $unwind : '$_v' }");
103+
104+
var results = queryable.ToList();
105+
results.Count.Should().Be(3);
106+
results[0].Key.Should().Be("A");
107+
results[0].Value.Should().Be("a");
108+
results[1].Key.Should().Be("B");
109+
results[1].Value.Should().Be("b");
110+
results[2].Key.Should().Be("C");
111+
results[2].Value.Should().Be("c");
112+
}
113+
114+
public class C
115+
{
116+
public int Id { get; set; }
117+
public string Name {get;set;}
118+
119+
[BsonDictionaryOptions( DictionaryRepresentation.ArrayOfArrays )]
120+
public Dictionary<string,string> DictionaryWithArrayOfArraysRepresentation { get; set; } = new Dictionary<string, string>();
121+
122+
123+
[BsonDictionaryOptions( DictionaryRepresentation.ArrayOfDocuments )]
124+
public Dictionary<string,string> DictionaryWithArrayOfDocumentsRepresentation { get; set; } = new Dictionary<string, string>();
125+
126+
[BsonDictionaryOptions( DictionaryRepresentation.Document )]
127+
public Dictionary<string,string> DictionaryWithDocumentRepresentation { get; set; } = new Dictionary<string, string>();
128+
}
129+
130+
public sealed class ClassFixture : MongoCollectionFixture<C>
131+
{
132+
protected override IEnumerable<C> InitialData =>
133+
[
134+
new C
135+
{
136+
Id = 1,
137+
Name = "TestName",
138+
DictionaryWithArrayOfArraysRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } },
139+
DictionaryWithArrayOfDocumentsRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } },
140+
DictionaryWithDocumentRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } },
141+
}
142+
];
143+
}
144+
}

0 commit comments

Comments
 (0)