Skip to content

Commit bd0a2aa

Browse files
Oleksandr Poliakovrstam
Oleksandr Poliakov
authored andcommitted
CSHARP-5461: Add targetSerializer parameter to Translate methods.
1 parent 660f33d commit bd0a2aa

File tree

8 files changed

+408
-53
lines changed

8 files changed

+408
-53
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,22 @@
2323

2424
namespace MongoDB.Bson.Serialization
2525
{
26+
/// <summary>
27+
/// An interface implemented by BsonClassMapSerializer.
28+
/// </summary>
29+
public interface IBsonClassMapSerializer
30+
{
31+
/// <summary>
32+
/// Gets the class map for a BsonClassMapSerializer.
33+
/// </summary>
34+
public BsonClassMap ClassMap { get; }
35+
}
36+
2637
/// <summary>
2738
/// Represents a serializer for a class map.
2839
/// </summary>
2940
/// <typeparam name="TClass">The type of the class.</typeparam>
30-
public sealed class BsonClassMapSerializer<TClass> : SerializerBase<TClass>, IBsonIdProvider, IBsonDocumentSerializer, IBsonPolymorphicSerializer, IHasDiscriminatorConvention
41+
public sealed class BsonClassMapSerializer<TClass> : SerializerBase<TClass>, IBsonClassMapSerializer, IBsonIdProvider, IBsonDocumentSerializer, IBsonPolymorphicSerializer, IHasDiscriminatorConvention
3142
{
3243
// private fields
3344
private readonly BsonClassMap _classMap;
@@ -57,6 +68,9 @@ public BsonClassMapSerializer(BsonClassMap classMap)
5768
}
5869

5970
// public properties
71+
/// <inheritdoc/>
72+
public BsonClassMap ClassMap => _classMap;
73+
6074
/// <inheritdoc/>
6175
public IDiscriminatorConvention DiscriminatorConvention => _classMap.GetDiscriminatorConvention();
6276

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,27 @@
1717
using MongoDB.Bson.IO;
1818
using MongoDB.Bson.Serialization;
1919
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2021
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
2122

2223
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
2324
{
2425
internal static class ConstantExpressionToAggregationExpressionTranslator
2526
{
26-
public static AggregationExpression Translate(ConstantExpression constantExpression)
27+
public static AggregationExpression Translate(ConstantExpression constantExpression, IBsonSerializer targetSerializer)
2728
{
28-
var constantType = constantExpression.Type;
29-
var constantSerializer = StandardSerializers.TryGetSerializer(constantType, out var serializer) ? serializer : BsonSerializer.LookupSerializer(constantType);
30-
return Translate(constantExpression, constantSerializer);
31-
}
29+
var resultSerializer = targetSerializer;
30+
if (resultSerializer == null)
31+
{
32+
var constantType = constantExpression.Type;
33+
resultSerializer = StandardSerializers.TryGetSerializer(constantType, out var serializer) ? serializer : BsonSerializer.LookupSerializer(constantType);
34+
}
3235

33-
public static AggregationExpression Translate(ConstantExpression constantExpression, IBsonSerializer constantSerializer)
34-
{
35-
var constantValue = constantExpression.Value;
36-
var serializedValue = constantSerializer.ToBsonValue(constantValue);
36+
var value = constantExpression.Value;
37+
var serializedValue = SerializationHelper.SerializeValue(resultSerializer, value);
3738
var ast = AstExpression.Constant(serializedValue);
38-
return new AggregationExpression(constantExpression, ast, constantSerializer);
39+
40+
return new AggregationExpression(constantExpression, ast, resultSerializer);
3941
}
4042
}
4143
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggreg
2727
internal static class ExpressionToAggregationExpressionTranslator
2828
{
2929
// public static methods
30-
public static AggregationExpression Translate(TranslationContext context, Expression expression)
30+
public static AggregationExpression Translate(TranslationContext context, Expression expression, IBsonSerializer targetSerializer = null)
3131
{
3232
switch (expression.NodeType)
3333
{
@@ -67,21 +67,21 @@ public static AggregationExpression Translate(TranslationContext context, Expres
6767
case ExpressionType.Conditional:
6868
return ConditionalExpressionToAggregationExpressionTranslator.Translate(context, (ConditionalExpression)expression);
6969
case ExpressionType.Constant:
70-
return ConstantExpressionToAggregationExpressionTranslator.Translate((ConstantExpression)expression);
70+
return ConstantExpressionToAggregationExpressionTranslator.Translate((ConstantExpression)expression, targetSerializer);
7171
case ExpressionType.Index:
7272
return IndexExpressionToAggregationExpressionTranslator.Translate(context, (IndexExpression)expression);
7373
case ExpressionType.ListInit:
7474
return ListInitExpressionToAggregationExpressionTranslator.Translate(context, (ListInitExpression)expression);
7575
case ExpressionType.MemberAccess:
7676
return MemberExpressionToAggregationExpressionTranslator.Translate(context, (MemberExpression)expression);
7777
case ExpressionType.MemberInit:
78-
return MemberInitExpressionToAggregationExpressionTranslator.Translate(context, (MemberInitExpression)expression);
78+
return MemberInitExpressionToAggregationExpressionTranslator.Translate(context, (MemberInitExpression)expression, targetSerializer);
7979
case ExpressionType.Negate:
8080
return NegateExpressionToAggregationExpressionTranslator.Translate(context, (UnaryExpression)expression);
8181
case ExpressionType.New:
82-
return NewExpressionToAggregationExpressionTranslator.Translate(context, (NewExpression)expression);
82+
return NewExpressionToAggregationExpressionTranslator.Translate(context, (NewExpression)expression, targetSerializer);
8383
case ExpressionType.NewArrayInit:
84-
return NewArrayInitExpressionToAggregationExpressionTranslator.Translate(context, (NewArrayExpression)expression);
84+
return NewArrayInitExpressionToAggregationExpressionTranslator.Translate(context, (NewArrayExpression)expression, targetSerializer);
8585
case ExpressionType.Parameter:
8686
return ParameterExpressionToAggregationExpressionTranslator.Translate(context, (ParameterExpression)expression);
8787
case ExpressionType.TypeIs:

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

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,28 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggreg
2828
{
2929
internal static class MemberInitExpressionToAggregationExpressionTranslator
3030
{
31-
public static AggregationExpression Translate(TranslationContext context, MemberInitExpression expression)
31+
public static AggregationExpression Translate(TranslationContext context, MemberInitExpression expression, IBsonSerializer targetSerializer)
3232
{
3333
if (expression.Type == typeof(BsonDocument))
3434
{
3535
return NewBsonDocumentExpressionToAggregationExpressionTranslator.Translate(context, expression);
3636
}
3737

38-
return Translate(context, expression, expression.NewExpression, expression.Bindings);
38+
return Translate(context, expression, expression.NewExpression, expression.Bindings, targetSerializer);
3939
}
4040

4141
public static AggregationExpression Translate(
4242
TranslationContext context,
4343
Expression expression,
4444
NewExpression newExpression,
45-
IReadOnlyList<MemberBinding> bindings)
45+
IReadOnlyList<MemberBinding> bindings,
46+
IBsonSerializer targetSerializer)
4647
{
48+
if (targetSerializer != null)
49+
{
50+
return TranslateWithTargetSerializer(context, expression, newExpression, bindings, targetSerializer);
51+
}
52+
4753
var constructorInfo = newExpression.Constructor; // note: can be null when using the default constructor with a struct
4854
var constructorArguments = newExpression.Arguments;
4955
var computedFields = new List<AstComputedField>();
@@ -100,6 +106,72 @@ public static AggregationExpression Translate(
100106
return new AggregationExpression(expression, ast, serializer);
101107
}
102108

109+
private static AggregationExpression TranslateWithTargetSerializer(
110+
TranslationContext context,
111+
Expression expression,
112+
NewExpression newExpression,
113+
IReadOnlyList<MemberBinding> bindings,
114+
IBsonSerializer targetSerializer)
115+
{
116+
var resultSerializer = targetSerializer as IBsonDocumentSerializer;
117+
if (resultSerializer == null)
118+
{
119+
throw new ExpressionNotSupportedException(expression, because: $"serializer class {targetSerializer.GetType()} does not implement IBsonDocumentSerializer.");
120+
}
121+
122+
var constructorInfo = newExpression.Constructor; // note: can be null when using the default constructor with a struct
123+
var constructorArguments = newExpression.Arguments;
124+
var computedFields = new List<AstComputedField>();
125+
126+
if (constructorInfo != null && constructorArguments.Count > 0)
127+
{
128+
var constructorParameters = constructorInfo.GetParameters();
129+
130+
// if the documentSerializer is a BsonClassMappedSerializer we can use the classMap
131+
var classMap = (resultSerializer as IBsonClassMapSerializer)?.ClassMap;
132+
var creatorMap = classMap == null ? null : FindMatchingCreatorMap(classMap, constructorInfo);
133+
if (creatorMap == null && classMap != null)
134+
{
135+
throw new ExpressionNotSupportedException(expression, because: "couldn't find matching creator map");
136+
}
137+
var creatorMapArguments = creatorMap?.Arguments?.ToArray();
138+
139+
for (var i = 0; i < constructorParameters.Length; i++)
140+
{
141+
var parameterName = constructorParameters[i].Name;
142+
var argumentExpression = constructorArguments[i];
143+
var memberName = creatorMapArguments?[i].Name; // null if there is no classMap
144+
145+
var (elementName, memberSerializer) = FindMemberElementNameAndSerializer(argumentExpression, classMap, memberName, resultSerializer, parameterName);
146+
if (elementName == null)
147+
{
148+
throw new ExpressionNotSupportedException(expression, because: $"couldn't find matching class member for constructor parameter {parameterName}");
149+
}
150+
151+
var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression, memberSerializer);
152+
computedFields.Add(AstExpression.ComputedField(elementName, argumentTranslation.Ast));
153+
}
154+
}
155+
156+
foreach (var binding in bindings)
157+
{
158+
var memberAssignment = (MemberAssignment)binding;
159+
var member = memberAssignment.Member;
160+
var valueExpression = memberAssignment.Expression;
161+
if (!resultSerializer.TryGetMemberSerializationInfo(member.Name, out var memberSerializationInfo))
162+
{
163+
throw new ExpressionNotSupportedException(valueExpression, expression, because: $"couldn't find member {member.Name}");
164+
}
165+
var memberSerializer = memberSerializationInfo.Serializer;
166+
167+
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression, memberSerializer);
168+
computedFields.Add(AstExpression.ComputedField(memberSerializationInfo.ElementName, valueTranslation.Ast));
169+
}
170+
171+
var ast = AstExpression.ComputedDocument(computedFields);
172+
return new AggregationExpression(expression, ast, resultSerializer);
173+
}
174+
103175
private static BsonClassMap CreateClassMap(Type classType, ConstructorInfo constructorInfo, out BsonCreatorMap creatorMap)
104176
{
105177
BsonClassMap baseClassMap = null;
@@ -190,6 +262,36 @@ private static void EnsureDefaultValue(BsonMemberMap memberMap)
190262
memberMap.SetDefaultValue(defaultValue);
191263
}
192264

265+
private static BsonCreatorMap FindMatchingCreatorMap(BsonClassMap classMap, ConstructorInfo constructorInfo)
266+
=> classMap?.CreatorMaps.FirstOrDefault(m => m.MemberInfo.Equals(constructorInfo));
267+
268+
private static (string, IBsonSerializer) FindMemberElementNameAndSerializer(
269+
Expression expression,
270+
BsonClassMap classMap,
271+
string memberName,
272+
IBsonDocumentSerializer documentSerializer,
273+
string constructorParameterName)
274+
{
275+
// if we have a classMap use it
276+
if (classMap != null)
277+
{
278+
var memberMap = FindMemberMap(expression, classMap, memberName);
279+
return (memberMap.ElementName, memberMap.GetSerializer());
280+
}
281+
282+
// otherwise fall back to calling TryGetMemberSerializationInfo on potential matches
283+
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase;
284+
foreach (var memberInfo in documentSerializer.ValueType.GetMember(constructorParameterName, bindingFlags))
285+
{
286+
if (documentSerializer.TryGetMemberSerializationInfo(memberInfo.Name, out var serializationInfo))
287+
{
288+
return (serializationInfo.ElementName, serializationInfo.Serializer);
289+
}
290+
}
291+
292+
return (null, null);
293+
}
294+
193295
private static BsonMemberMap FindMemberMap(Expression expression, BsonClassMap classMap, string memberName)
194296
{
195297
foreach (var memberMap in classMap.DeclaredMemberMaps)

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

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,34 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggreg
2424
{
2525
internal static class NewArrayInitExpressionToAggregationExpressionTranslator
2626
{
27-
public static AggregationExpression Translate(TranslationContext context, NewArrayExpression expression)
27+
public static AggregationExpression Translate(TranslationContext context, NewArrayExpression expression, IBsonSerializer targetSerializer)
2828
{
29-
var items = new List<AstExpression>();
29+
IBsonArraySerializer resultSerializer = null;
3030
IBsonSerializer itemSerializer = null;
31+
32+
if (targetSerializer != null)
33+
{
34+
if ((resultSerializer = targetSerializer as IBsonArraySerializer) == null)
35+
{
36+
throw new ExpressionNotSupportedException(expression, because: $"serializer class {targetSerializer} does not implement IBsonArraySerializer");
37+
}
38+
if (!resultSerializer.TryGetItemSerializationInfo(out var itemSerializationInfo))
39+
{
40+
throw new ExpressionNotSupportedException(expression, because: $"serializer class {targetSerializer} returned false for TryGetItemSerializationInfo");
41+
}
42+
43+
itemSerializer = itemSerializationInfo.Serializer;
44+
}
45+
46+
var items = new List<AstExpression>();
3147
foreach (var itemExpression in expression.Expressions)
3248
{
33-
var itemTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, itemExpression);
49+
var itemTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, itemExpression, itemSerializer);
3450
items.Add(itemTranslation.Ast);
35-
itemSerializer ??= itemTranslation.Serializer;
51+
if (itemSerializer == null)
52+
{
53+
itemSerializer = itemTranslation.Serializer;
54+
}
3655

3756
// make sure all items are serialized using the same serializer
3857
if (!itemTranslation.Serializer.Equals(itemSerializer))
@@ -42,14 +61,16 @@ public static AggregationExpression Translate(TranslationContext context, NewArr
4261
}
4362

4463
var ast = AstExpression.ComputedArray(items);
64+
if (resultSerializer == null)
65+
{
66+
var arrayType = expression.Type;
67+
var itemType = arrayType.GetElementType();
68+
itemSerializer ??= BsonSerializer.LookupSerializer(itemType); // if the array is empty itemSerializer might be null
69+
var arraySerializerType = typeof(ArraySerializer<>).MakeGenericType(itemType);
70+
resultSerializer = (IBsonArraySerializer)Activator.CreateInstance(arraySerializerType, itemSerializer);
71+
}
4572

46-
var arrayType = expression.Type;
47-
var itemType = arrayType.GetElementType();
48-
itemSerializer ??= BsonSerializer.LookupSerializer(itemType); // if the array is empty itemSerializer will be null
49-
var arraySerializerType = typeof(ArraySerializer<>).MakeGenericType(itemType);
50-
var arraySerializer = (IBsonSerializer)Activator.CreateInstance(arraySerializerType, itemSerializer);
51-
52-
return new AggregationExpression(expression, ast, arraySerializer);
73+
return new AggregationExpression(expression, ast, resultSerializer);
5374
}
5475
}
5576
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
using System.Collections.Generic;
1818
using System.Linq.Expressions;
1919
using MongoDB.Bson;
20+
using MongoDB.Bson.Serialization;
2021

2122
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
2223
{
2324
internal static class NewExpressionToAggregationExpressionTranslator
2425
{
25-
public static AggregationExpression Translate(TranslationContext context, NewExpression expression)
26+
public static AggregationExpression Translate(TranslationContext context, NewExpression expression, IBsonSerializer targetSerializer)
2627
{
2728
var expressionType = expression.Type;
2829

@@ -46,7 +47,8 @@ public static AggregationExpression Translate(TranslationContext context, NewExp
4647
{
4748
return NewTupleExpressionToAggregationExpressionTranslator.Translate(context, expression);
4849
}
49-
return MemberInitExpressionToAggregationExpressionTranslator.Translate(context, expression, expression, Array.Empty<MemberBinding>());
50+
51+
return MemberInitExpressionToAggregationExpressionTranslator.Translate(context, expression, expression, Array.Empty<MemberBinding>(), targetSerializer);
5052
}
5153
}
5254
}

0 commit comments

Comments
 (0)