@@ -28,22 +28,28 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggreg
28
28
{
29
29
internal static class MemberInitExpressionToAggregationExpressionTranslator
30
30
{
31
- public static AggregationExpression Translate ( TranslationContext context , MemberInitExpression expression )
31
+ public static AggregationExpression Translate ( TranslationContext context , MemberInitExpression expression , IBsonSerializer targetSerializer )
32
32
{
33
33
if ( expression . Type == typeof ( BsonDocument ) )
34
34
{
35
35
return NewBsonDocumentExpressionToAggregationExpressionTranslator . Translate ( context , expression ) ;
36
36
}
37
37
38
- return Translate ( context , expression , expression . NewExpression , expression . Bindings ) ;
38
+ return Translate ( context , expression , expression . NewExpression , expression . Bindings , targetSerializer ) ;
39
39
}
40
40
41
41
public static AggregationExpression Translate (
42
42
TranslationContext context ,
43
43
Expression expression ,
44
44
NewExpression newExpression ,
45
- IReadOnlyList < MemberBinding > bindings )
45
+ IReadOnlyList < MemberBinding > bindings ,
46
+ IBsonSerializer targetSerializer )
46
47
{
48
+ if ( targetSerializer != null )
49
+ {
50
+ return TranslateWithTargetSerializer ( context , expression , newExpression , bindings , targetSerializer ) ;
51
+ }
52
+
47
53
var constructorInfo = newExpression . Constructor ; // note: can be null when using the default constructor with a struct
48
54
var constructorArguments = newExpression . Arguments ;
49
55
var computedFields = new List < AstComputedField > ( ) ;
@@ -100,6 +106,72 @@ public static AggregationExpression Translate(
100
106
return new AggregationExpression ( expression , ast , serializer ) ;
101
107
}
102
108
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
+
103
175
private static BsonClassMap CreateClassMap ( Type classType , ConstructorInfo constructorInfo , out BsonCreatorMap creatorMap )
104
176
{
105
177
BsonClassMap baseClassMap = null ;
@@ -190,6 +262,36 @@ private static void EnsureDefaultValue(BsonMemberMap memberMap)
190
262
memberMap . SetDefaultValue ( defaultValue ) ;
191
263
}
192
264
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
+
193
295
private static BsonMemberMap FindMemberMap ( Expression expression , BsonClassMap classMap , string memberName )
194
296
{
195
297
foreach ( var memberMap in classMap . DeclaredMemberMaps )
0 commit comments