Skip to content

Commit de05452

Browse files
authored
CSHARP-1913: Support using Dictionary fields as IEnumerable<KeyValuePair<TKey, TValue>> (#1800)
1 parent 55c13cd commit de05452

File tree

4 files changed

+250
-21
lines changed

4 files changed

+250
-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: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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 Nested__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+
.Select(to => to.DictionaryWithArrayOfArraysRepresentation.SelectMany(kvp => new KeyValuePair<string, string>[] { kvp }));
42+
43+
var stages = Translate(collection, queryable);
44+
AssertStages(
45+
stages,
46+
"{ $match : { Name : 'TestName' } }",
47+
"{ $project : { _v : { $reduce : { input : { $map : { input : '$DictionaryWithArrayOfArraysRepresentation', as : 'kvp', in : ['$$kvp'] } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }");
48+
49+
var result = queryable.Single();
50+
result.Select(kvp => kvp.Key).Should().Equal("A", "B", "C");
51+
}
52+
53+
[Fact]
54+
public void Nested__SelectMany_with_ArrayOfDocuments_representation_should_work()
55+
{
56+
var collection = Fixture.Collection;
57+
58+
var queryable = collection.AsQueryable()
59+
.OfType<C>()
60+
.Where(to => to.Name == "TestName")
61+
.Select(to => to.DictionaryWithArrayOfDocumentsRepresentation.SelectMany(kvp => new KeyValuePair<string, string>[] { kvp }));
62+
63+
var stages = Translate(collection, queryable);
64+
AssertStages(
65+
stages,
66+
"{ $match : { Name : 'TestName' } }",
67+
"{ $project : { _v : { $reduce : { input : { $map : { input : '$DictionaryWithArrayOfDocumentsRepresentation', as : 'kvp', in : ['$$kvp'] } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }");
68+
69+
var result = queryable.Single();
70+
result.Select(kvp => kvp.Key).Should().Equal("A", "B", "C");
71+
}
72+
73+
[Fact]
74+
public void Nested_SelectMany_with_Document_representation_should_work()
75+
{
76+
var collection = Fixture.Collection;
77+
78+
var queryable = collection.AsQueryable()
79+
.OfType<C>()
80+
.Where(to => to.Name == "TestName")
81+
.Select(to => to.DictionaryWithDocumentRepresentation.SelectMany(kvp => new KeyValuePair<string, string>[] { kvp }));
82+
83+
var stages = Translate(collection, queryable);
84+
AssertStages(
85+
stages,
86+
"{ $match : { Name : 'TestName' } }",
87+
"{ $project : { _v : { $reduce : { input : { $map : { input : { $objectToArray : '$DictionaryWithDocumentRepresentation' }, as : 'kvp', in : ['$$kvp'] } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }");
88+
89+
var result = queryable.Single();
90+
result.Select(kvp => kvp.Key).Should().Equal("A", "B", "C");
91+
}
92+
93+
[Fact]
94+
public void Top_level_SelectMany_with_ArrayOfArrays_representation_should_work()
95+
{
96+
var collection = Fixture.Collection;
97+
98+
var queryable = collection.AsQueryable()
99+
.OfType<C>()
100+
.Where(to => to.Name == "TestName")
101+
.SelectMany(to => to.DictionaryWithArrayOfArraysRepresentation);
102+
103+
var stages = Translate(collection, queryable);
104+
AssertStages(
105+
stages,
106+
"{ $match : { Name : 'TestName' } }",
107+
"{ $project : { _v : '$DictionaryWithArrayOfArraysRepresentation', _id : 0 } }",
108+
"{ $unwind : '$_v' }");
109+
110+
var results = queryable.ToList();
111+
results.Count.Should().Be(3);
112+
results[0].Key.Should().Be("A");
113+
results[0].Value.Should().Be("a");
114+
results[1].Key.Should().Be("B");
115+
results[1].Value.Should().Be("b");
116+
results[2].Key.Should().Be("C");
117+
results[2].Value.Should().Be("c");
118+
}
119+
120+
[Fact]
121+
public void Top_level_SelectMany_with_ArrayOfDocuments_representation_should_work()
122+
{
123+
var collection = Fixture.Collection;
124+
125+
var queryable = collection.AsQueryable()
126+
.OfType<C>()
127+
.Where(to => to.Name == "TestName")
128+
.SelectMany(to => to.DictionaryWithArrayOfDocumentsRepresentation);
129+
130+
var stages = Translate(collection, queryable);
131+
AssertStages(
132+
stages,
133+
"{ $match : { Name : 'TestName' } }",
134+
"{ $project : { _v : '$DictionaryWithArrayOfDocumentsRepresentation', _id : 0 } }",
135+
"{ $unwind : '$_v' }");
136+
137+
var results = queryable.ToList();
138+
results.Count.Should().Be(3);
139+
results[0].Key.Should().Be("A");
140+
results[0].Value.Should().Be("a");
141+
results[1].Key.Should().Be("B");
142+
results[1].Value.Should().Be("b");
143+
results[2].Key.Should().Be("C");
144+
results[2].Value.Should().Be("c");
145+
}
146+
147+
[Fact]
148+
public void Top_level_SelectMany_with_Document_representation_should_work()
149+
{
150+
var collection = Fixture.Collection;
151+
152+
var queryable = collection.AsQueryable()
153+
.OfType<C>()
154+
.Where(to => to.Name == "TestName")
155+
.SelectMany(to => to.DictionaryWithDocumentRepresentation);
156+
157+
var stages = Translate(collection, queryable);
158+
AssertStages(
159+
stages,
160+
"{ $match : { Name : 'TestName' } }",
161+
"{ $project : { _v : { $objectToArray : '$DictionaryWithDocumentRepresentation' }, _id : 0 } }",
162+
"{ $unwind : '$_v' }");
163+
164+
var results = queryable.ToList();
165+
results.Count.Should().Be(3);
166+
results[0].Key.Should().Be("A");
167+
results[0].Value.Should().Be("a");
168+
results[1].Key.Should().Be("B");
169+
results[1].Value.Should().Be("b");
170+
results[2].Key.Should().Be("C");
171+
results[2].Value.Should().Be("c");
172+
}
173+
174+
public class C
175+
{
176+
public int Id { get; set; }
177+
public string Name {get;set;}
178+
179+
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
180+
public Dictionary<string,string> DictionaryWithArrayOfArraysRepresentation { get; set; } = new Dictionary<string, string>();
181+
182+
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
183+
public Dictionary<string,string> DictionaryWithArrayOfDocumentsRepresentation { get; set; } = new Dictionary<string, string>();
184+
185+
[BsonDictionaryOptions(DictionaryRepresentation.Document)]
186+
public Dictionary<string,string> DictionaryWithDocumentRepresentation { get; set; } = new Dictionary<string, string>();
187+
}
188+
189+
public sealed class ClassFixture : MongoCollectionFixture<C>
190+
{
191+
protected override IEnumerable<C> InitialData =>
192+
[
193+
new C
194+
{
195+
Id = 1,
196+
Name = "TestName",
197+
DictionaryWithArrayOfArraysRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } },
198+
DictionaryWithArrayOfDocumentsRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } },
199+
DictionaryWithDocumentRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } },
200+
}
201+
];
202+
}
203+
}

0 commit comments

Comments
 (0)