Skip to content

Commit 067ddb6

Browse files
committed
CSHARP-5506: Support Dictionary get_Item for keys that aren't strings but are serialized as strings.
1 parent 693ea35 commit 067ddb6

File tree

4 files changed

+172
-23
lines changed

4 files changed

+172
-23
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/DictionaryMethod.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,15 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
2121
internal static class DictionaryMethod
2222
{
2323
// public static methods
24-
public static bool IsGetItemWithStringMethod(MethodInfo method)
24+
public static bool IsGetItemWithKeyMethod(MethodInfo method)
2525
{
2626
return
2727
!method.IsStatic &&
2828
method.Name == "get_Item" &&
29+
method.DeclaringType.ImplementsDictionaryInterface(out var keyType, out var valueType) &&
2930
method.GetParameters() is var parameters &&
3031
parameters.Length == 1 &&
31-
parameters[0].ParameterType == typeof(string) &&
32-
method.DeclaringType.ImplementsDictionaryInterface(out var keyType, out var valueType) &&
33-
keyType == typeof(string) &&
32+
parameters[0].ParameterType == keyType &&
3433
method.ReturnType == valueType;
3534
}
3635
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs

+51-9
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
using System.Reflection;
2020
using MongoDB.Bson;
2121
using MongoDB.Bson.Serialization;
22+
using MongoDB.Bson.Serialization.Options;
2223
using MongoDB.Bson.Serialization.Serializers;
2324
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
24-
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
2525
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2626
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
2727

@@ -55,9 +55,9 @@ public static TranslatedExpression Translate(TranslationContext context, Express
5555
return TranslateIListGetItemWithInt(context, expression, sourceExpression, arguments[0]);
5656
}
5757

58-
if (DictionaryMethod.IsGetItemWithStringMethod(method))
58+
if (DictionaryMethod.IsGetItemWithKeyMethod(method))
5959
{
60-
return TranslateIDictionaryGetItemWithString(context, expression, sourceExpression, arguments[0]);
60+
return TranslateIDictionaryGetItemWithKey(context, expression, sourceExpression, arguments[0]);
6161
}
6262

6363
throw new ExpressionNotSupportedException(expression);
@@ -112,13 +112,55 @@ private static TranslatedExpression TranslateIListGetItemWithInt(TranslationCont
112112
return new TranslatedExpression(expression, ast, serializer);
113113
}
114114

115-
private static TranslatedExpression TranslateIDictionaryGetItemWithString(TranslationContext context, Expression expression, Expression sourceExpression, Expression keyExpression)
115+
private static TranslatedExpression TranslateIDictionaryGetItemWithKey(TranslationContext context, Expression expression, Expression dictionaryExpression, Expression keyExpression)
116116
{
117-
var sourceTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, sourceExpression);
118-
var key = keyExpression.GetConstantValue<string>(containingExpression: expression);
119-
var ast = AstExpression.GetField(sourceTranslation.Ast, key); // TODO: verify that dictionary is using Document representation
120-
var valueSerializer = GetDictionaryValueSerializer(sourceTranslation.Serializer);
121-
return new TranslatedExpression(expression, ast, valueSerializer);
117+
var dictionaryTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, dictionaryExpression);
118+
if (!(dictionaryTranslation.Serializer is IBsonDictionarySerializer dictionarySerializer))
119+
{
120+
throw new ExpressionNotSupportedException(expression, because: $"dictionary serializer class {dictionaryTranslation.Serializer.GetType()} does not implement {nameof(IBsonDictionarySerializer)}");
121+
}
122+
if (dictionarySerializer.DictionaryRepresentation != DictionaryRepresentation.Document)
123+
{
124+
throw new ExpressionNotSupportedException(expression, because: "dictionary is not represented as a document");
125+
}
126+
127+
var keySerializer = dictionarySerializer.KeySerializer;
128+
AstExpression keyFieldNameAst;
129+
130+
if (keyExpression is ConstantExpression constantKeyExpression)
131+
{
132+
var key = constantKeyExpression.Value;
133+
var serializedKey = SerializationHelper.SerializeValue(keySerializer, key);
134+
135+
if (!(serializedKey is BsonString))
136+
{
137+
throw new ExpressionNotSupportedException(expression, because: "key did not serialize as a string");
138+
}
139+
140+
keyFieldNameAst = AstExpression.Constant(serializedKey);
141+
}
142+
else
143+
{
144+
if (!(keySerializer is IHasRepresentationSerializer hasRepresentationSerializer))
145+
{
146+
throw new ExpressionNotSupportedException(expression, because: $"key serializer class {keySerializer.GetType()} does not implement {nameof(IHasRepresentationSerializer)}");
147+
}
148+
if (hasRepresentationSerializer.Representation != BsonType.String)
149+
{
150+
throw new ExpressionNotSupportedException(expression, because: $"key serializer class {keySerializer.GetType()} does not serialize as a string");
151+
}
152+
153+
var keyTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, keyExpression);
154+
if (!keyTranslation.Serializer.Equals(keySerializer))
155+
{
156+
throw new ExpressionNotSupportedException(expression, because: "key expression serializer is not equal to the key serializer");
157+
}
158+
159+
keyFieldNameAst = keyTranslation.Ast;
160+
}
161+
162+
var ast = AstExpression.GetField(dictionaryTranslation.Ast, keyFieldNameAst);
163+
return new TranslatedExpression(expression, ast, dictionarySerializer.ValueSerializer);
122164
}
123165
}
124166
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/GetItemMethodToFilterFieldTranslator.cs

+22-10
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
using System.Collections.ObjectModel;
1717
using System.Linq.Expressions;
1818
using System.Reflection;
19+
using MongoDB.Bson;
1920
using MongoDB.Bson.Serialization;
2021
using MongoDB.Bson.Serialization.Options;
2122
using MongoDB.Bson.Serialization.Serializers;
22-
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
2323
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
24+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2425
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
2526

2627
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators
@@ -53,9 +54,9 @@ public static TranslatedFilterField Translate(TranslationContext context, Expres
5354
return TranslateIListGetItemWithInt(context, expression, fieldExpression, arguments[0]);
5455
}
5556

56-
if (DictionaryMethod.IsGetItemWithStringMethod(method))
57+
if (DictionaryMethod.IsGetItemWithKeyMethod(method))
5758
{
58-
return TranslateDictionaryGetItemWithString(context, expression, fieldExpression, arguments[0]);
59+
return TranslateDictionaryGetItemWithKey(context, expression, fieldExpression, arguments[0]);
5960
}
6061

6162
throw new ExpressionNotSupportedException(expression);
@@ -79,19 +80,30 @@ private static TranslatedFilterField TranslateIListGetItemWithInt(TranslationCon
7980
return ArrayIndexExpressionToFilterFieldTranslator.Translate(context, expression, fieldExpression, indexExpression);
8081
}
8182

82-
private static TranslatedFilterField TranslateDictionaryGetItemWithString(TranslationContext context, Expression expression, Expression fieldExpression, Expression keyExpression)
83+
private static TranslatedFilterField TranslateDictionaryGetItemWithKey(TranslationContext context, Expression expression, Expression fieldExpression, Expression keyExpression)
8384
{
8485
var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
85-
var key = keyExpression.GetConstantValue<string>(containingExpression: expression);
86+
var key = keyExpression.GetConstantValue<object>(containingExpression: expression);
8687

87-
if (fieldTranslation.Serializer is IBsonDictionarySerializer dictionarySerializer &&
88-
dictionarySerializer.DictionaryRepresentation == DictionaryRepresentation.Document)
88+
if (!(fieldTranslation.Serializer is IBsonDictionarySerializer dictionarySerializer))
89+
{
90+
throw new ExpressionNotSupportedException(expression, because: $"dictionary serializer class {fieldTranslation.Serializer.GetType()} does not implement {nameof(IBsonDictionarySerializer)}");
91+
}
92+
if (dictionarySerializer.DictionaryRepresentation != DictionaryRepresentation.Document)
8993
{
90-
var valueSerializer = dictionarySerializer.ValueSerializer;
91-
return fieldTranslation.SubField(key, valueSerializer);
94+
throw new ExpressionNotSupportedException(expression, because: "dictionary is not represented as a document");
9295
}
9396

94-
throw new ExpressionNotSupportedException(expression);
97+
var keySerializer = dictionarySerializer.KeySerializer;
98+
var valueSerializer = dictionarySerializer.ValueSerializer;
99+
100+
var serializedKey = SerializationHelper.SerializeValue(keySerializer, key);
101+
if (serializedKey is not BsonString)
102+
{
103+
throw new ExpressionNotSupportedException(expression, because: "key did not serialize as a string");
104+
}
105+
106+
return fieldTranslation.SubField(serializedKey.AsString, valueSerializer);
95107
}
96108
}
97109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using FluentAssertions;
20+
using MongoDB.Bson;
21+
using MongoDB.Bson.Serialization;
22+
using MongoDB.Bson.Serialization.Options;
23+
using MongoDB.Bson.Serialization.Serializers;
24+
using MongoDB.Driver.TestHelpers;
25+
using Xunit;
26+
27+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira;
28+
29+
public class CSharp5506Tests : LinqIntegrationTest<CSharp5506Tests.ClassFixture>
30+
{
31+
static CSharp5506Tests()
32+
{
33+
var keySerializer = new GuidSerializer(BsonType.String);
34+
var valueSerializer = Int32Serializer.Instance;
35+
var dictionarySerializer = new DictionaryInterfaceImplementerSerializer<Dictionary<Guid, int>>(
36+
DictionaryRepresentation.Document,
37+
keySerializer,
38+
valueSerializer);
39+
40+
BsonClassMap.RegisterClassMap<C>(cm =>
41+
{
42+
cm.AutoMap();
43+
cm.MapMember(x => x.Dictionary).SetSerializer(dictionarySerializer);
44+
});
45+
}
46+
47+
public CSharp5506Tests(ClassFixture fixture)
48+
: base(fixture)
49+
{
50+
}
51+
52+
[Fact]
53+
public void Where_Dictionary_item_equals_value_should_work()
54+
{
55+
var collection = Fixture.Collection;
56+
var guid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10");
57+
58+
var queryable = collection.AsQueryable().Where(x => x.Dictionary[guid] == 2);
59+
60+
var stages = Translate(collection, queryable);
61+
AssertStages(stages, "{ $match : { 'Dictionary.01020304-0506-0708-090a-0b0c0d0e0f10' : 2 } }");
62+
63+
var result = Queryable.Single(queryable);
64+
result.Id.Should().Be(1);
65+
}
66+
67+
[Fact]
68+
public void Select_Dictionary_item_equals_value_should_work()
69+
{
70+
var collection = Fixture.Collection;
71+
var guid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10");
72+
73+
var queryable = collection.AsQueryable()
74+
.Select(x => x.Dictionary[guid]);
75+
76+
var stages = Translate(collection, queryable);
77+
AssertStages(stages, "{ $project : { _v : '$Dictionary.01020304-0506-0708-090a-0b0c0d0e0f10', _id : 0 } }");
78+
79+
var result = Queryable.Single(queryable);
80+
result.Should().Be(2);
81+
}
82+
83+
public class C
84+
{
85+
public int Id { get; set; }
86+
public Dictionary<Guid, int> Dictionary { get; set; }
87+
}
88+
89+
public sealed class ClassFixture : MongoCollectionFixture<C>
90+
{
91+
protected override IEnumerable<C> InitialData =>
92+
[
93+
new C { Id = 1, Dictionary = new Dictionary<Guid, int> { [Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10")] = 2 } },
94+
];
95+
}
96+
}

0 commit comments

Comments
 (0)