Skip to content

Commit 8fe8e0a

Browse files
committed
CSHARP-1894: fallback to serializing as-is if value cannot be converted to field serializer.
1 parent e8b95d6 commit 8fe8e0a

File tree

8 files changed

+134
-91
lines changed

8 files changed

+134
-91
lines changed

src/MongoDB.Driver.Dotnet/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"netstandard1.5": {
3030
"dependencies": {
3131
"NETStandard.Library": "1.6.0",
32+
"System.ComponentModel.TypeConverter": "4.1.0",
3233
"System.Linq.Queryable": "4.0.1"
3334
}
3435
}

src/MongoDB.Driver/FieldDefinition.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,7 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
308308

309309
var underlyingSerializer = field.Serializer;
310310
var fieldSerializer = underlyingSerializer as IBsonSerializer<TField>;
311-
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, typeof(TField));
312-
if (valueSerializer == null)
313-
{
314-
valueSerializer = serializerRegistry.GetSerializer<TField>();
315-
}
311+
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField));
316312

317313
return new RenderedFieldDefinition<TField>(field.FieldName, fieldSerializer, valueSerializer, underlyingSerializer);
318314
}
@@ -376,12 +372,16 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
376372

377373
var fieldSerializer = underlyingSerializer as IBsonSerializer<TField>;
378374

379-
var valueSerializer = _fieldSerializer;
380-
if (valueSerializer == null && underlyingSerializer != null)
375+
IBsonSerializer<TField> valueSerializer;
376+
if (_fieldSerializer != null)
377+
{
378+
valueSerializer = _fieldSerializer;
379+
}
380+
else if (underlyingSerializer != null)
381381
{
382-
valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, typeof(TField));
382+
valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField));
383383
}
384-
if (valueSerializer == null)
384+
else
385385
{
386386
valueSerializer = serializerRegistry.GetSerializer<TField>();
387387
}

src/MongoDB.Driver/FieldValueSerializerHelper.cs

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18+
using System.ComponentModel;
1819
using System.Reflection;
1920
using MongoDB.Bson;
2021
using MongoDB.Bson.Serialization;
@@ -25,12 +26,12 @@ namespace MongoDB.Driver
2526
{
2627
internal static class FieldValueSerializerHelper
2728
{
28-
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, Type valueType)
29+
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType)
2930
{
30-
return GetSerializerForValueType(fieldSerializer, valueType, 0);
31+
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, 0);
3132
}
3233

33-
private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, Type valueType, int recursionLevel)
34+
private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, int recursionLevel)
3435
{
3536
if (recursionLevel > 1)
3637
{
@@ -115,26 +116,26 @@ private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSe
115116
if (recursionLevel == 0)
116117
{
117118
var itemSerializer = itemSerializationInfo.Serializer;
118-
return GetSerializerForValueType(itemSerializer, valueType, recursionLevel + 1);
119+
return GetSerializerForValueType(itemSerializer, serializerRegistry, valueType, recursionLevel + 1);
119120
}
120121
}
121122
}
122123

123-
// if we can't return a valid value serializer based on the field serializer return casting serializer
124-
var castingSerializerType = typeof(CastingSerializer<,>).MakeGenericType(valueType, fieldType);
125-
var castingSerializerConstructor = castingSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType });
126-
return (IBsonSerializer)castingSerializerConstructor.Invoke(new object[] { fieldSerializer });
124+
// if we can't return a value serializer based on the field serializer return a converting serializer
125+
var convertIfPossibleSerializerType = typeof(ConvertIfPossibleSerializer<,>).MakeGenericType(valueType, fieldType);
126+
var convertIfPossibleSerializerConstructor = convertIfPossibleSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType, typeof(IBsonSerializerRegistry) });
127+
return (IBsonSerializer)convertIfPossibleSerializerConstructor.Invoke(new object[] { fieldSerializer, serializerRegistry });
127128
}
128129

129-
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, Type valueType, object value)
130+
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, object value)
130131
{
131132
if (!valueType.GetTypeInfo().IsValueType && value == null)
132133
{
133134
return fieldSerializer;
134135
}
135136
else
136137
{
137-
return GetSerializerForValueType(fieldSerializer, valueType);
138+
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType);
138139
}
139140
}
140141

@@ -166,6 +167,74 @@ private static IBsonSerializer WithStringRepresentation(IBsonSerializer serializ
166167
}
167168

168169
// nested types
170+
private class ConvertIfPossibleSerializer<TFrom, TTo> : SerializerBase<TFrom>
171+
{
172+
private readonly IBsonSerializer<TTo> _serializer;
173+
private readonly IBsonSerializerRegistry _serializerRegistry;
174+
175+
public ConvertIfPossibleSerializer(IBsonSerializer<TTo> serializer, IBsonSerializerRegistry serializerRegistry)
176+
{
177+
_serializer = serializer;
178+
_serializerRegistry = serializerRegistry;
179+
}
180+
181+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TFrom value)
182+
{
183+
TTo convertedValue;
184+
if (TryConvertValue(value, out convertedValue))
185+
{
186+
_serializer.Serialize(context, args, convertedValue);
187+
}
188+
else
189+
{
190+
var serializer = _serializerRegistry.GetSerializer<TFrom>();
191+
serializer.Serialize(context, args, value);
192+
}
193+
}
194+
195+
private bool TryConvertValue(TFrom value, out TTo convertedValue)
196+
{
197+
if (object.ReferenceEquals(value, null))
198+
{
199+
convertedValue = (TTo)(object)null;
200+
return true;
201+
}
202+
203+
Type fromType = value.GetType();
204+
Type toType = typeof(TTo);
205+
206+
if (toType.GetTypeInfo().IsAssignableFrom(fromType))
207+
{
208+
convertedValue = (TTo)(object)value;
209+
return true;
210+
}
211+
212+
var toConverter = TypeDescriptor.GetConverter(toType);
213+
if (toConverter.CanConvertFrom(fromType))
214+
{
215+
convertedValue = (TTo)toConverter.ConvertFrom(value);
216+
return true;
217+
}
218+
219+
var fromConverter = TypeDescriptor.GetConverter(fromType);
220+
if (fromConverter.CanConvertTo(toType))
221+
{
222+
convertedValue = (TTo)fromConverter.ConvertTo(value, toType);
223+
return true;
224+
}
225+
226+
try
227+
{
228+
convertedValue = (TTo)Convert.ChangeType(value, toType);
229+
return true;
230+
}
231+
catch { }
232+
233+
convertedValue = default(TTo);
234+
return false;
235+
}
236+
}
237+
169238
private class EnumConvertingSerializer<TFrom, TTo> : SerializerBase<TFrom>
170239
{
171240
private readonly IBsonSerializer<TTo> _serializer;
@@ -239,43 +308,5 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
239308
}
240309
}
241310
}
242-
243-
//unbound type casting
244-
private class CastingSerializer<TFrom, TTo> : SerializerBase<TFrom>
245-
{
246-
private readonly IBsonSerializer<TTo> _serializer;
247-
248-
public CastingSerializer(IBsonSerializer<TTo> serializer)
249-
{
250-
_serializer = serializer;
251-
}
252-
253-
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TFrom value)
254-
{
255-
_serializer.Serialize(context, args, CastValue(value));
256-
}
257-
258-
private TTo CastValue(TFrom value)
259-
{
260-
if (ReferenceEquals(value, null) || typeof(TTo).GetTypeInfo().IsAssignableFrom(value.GetType()))
261-
{
262-
//direct cast with boxing
263-
return (TTo)(object)value;
264-
}
265-
//cast using TypeDescriptor
266-
var converter = TypeDescriptor.GetConverter(value.GetType());
267-
if (converter.CanConvertTo(typeof(TTo)))
268-
{
269-
return (TTo)converter.ConvertTo(value, typeof(TTo));
270-
}
271-
converter = TypeDescriptor.GetConverter(typeof(TTo));
272-
if (converter.CanConvertFrom(value.GetType()))
273-
{
274-
return (TTo)converter.ConvertFrom(value);
275-
}
276-
//cast with Convert.ChangeType() - last chance
277-
return (TTo)Convert.ChangeType(value, typeof(TTo));
278-
}
279-
}
280311
}
281312
}

src/MongoDB.Driver/FilterDefinitionBuilder.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,7 +1585,7 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
15851585
{
15861586
var renderedField = _field.Render(documentSerializer, serializerRegistry);
15871587

1588-
IBsonSerializer itemSerializer = null;
1588+
IBsonSerializer itemSerializer;
15891589
if (renderedField.FieldSerializer != null)
15901590
{
15911591
var arraySerializer = renderedField.FieldSerializer as IBsonArraySerializer;
@@ -1595,9 +1595,9 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
15951595
var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
15961596
throw new InvalidOperationException(message);
15971597
}
1598-
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, typeof(TItem));
1598+
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem));
15991599
}
1600-
if (itemSerializer == null)
1600+
else
16011601
{
16021602
itemSerializer = serializerRegistry.GetSerializer<TItem>();
16031603
}
@@ -2179,7 +2179,7 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
21792179
{
21802180
var renderedField = _field.Render(documentSerializer, serializerRegistry);
21812181

2182-
IBsonSerializer itemSerializer = null;
2182+
IBsonSerializer itemSerializer;
21832183
if (renderedField.FieldSerializer != null)
21842184
{
21852185
var arraySerializer = renderedField.FieldSerializer as IBsonArraySerializer;
@@ -2189,9 +2189,9 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
21892189
var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
21902190
throw new InvalidOperationException(message);
21912191
}
2192-
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, typeof(TItem));
2192+
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem));
21932193
}
2194-
if (itemSerializer == null)
2194+
else
21952195
{
21962196
itemSerializer = serializerRegistry.GetSerializer<TItem>();
21972197
}
@@ -2228,7 +2228,7 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
22282228
{
22292229
var renderedField = _field.Render(documentSerializer, serializerRegistry);
22302230

2231-
IBsonSerializer<TItem> itemSerializer = null;
2231+
IBsonSerializer<TItem> itemSerializer;
22322232
if (renderedField.FieldSerializer != null)
22332233
{
22342234
var arraySerializer = renderedField.FieldSerializer as IBsonArraySerializer;
@@ -2238,9 +2238,9 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
22382238
var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
22392239
throw new InvalidOperationException(message);
22402240
}
2241-
itemSerializer = (IBsonSerializer<TItem>)FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, typeof(TItem));
2241+
itemSerializer = (IBsonSerializer<TItem>)FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem));
22422242
}
2243-
if (itemSerializer == null)
2243+
else
22442244
{
22452245
itemSerializer = serializerRegistry.GetSerializer<TItem>();
22462246
}

src/MongoDB.Driver/Linq/Expressions/ISerializationExpression.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static BsonValue SerializeValue(this ISerializationExpression field, Type
4949
{
5050
Ensure.IsNotNull(field, nameof(field));
5151

52-
var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, valueType, value);
52+
var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, BsonSerializer.SerializerRegistry, valueType, value);
5353

5454
var tempDocument = new BsonDocument();
5555
using (var bsonWriter = new BsonDocumentWriter(tempDocument))
@@ -69,7 +69,7 @@ public static BsonArray SerializeValues(this ISerializationExpression field, Typ
6969
Ensure.IsNotNull(itemType, nameof(itemType));
7070
Ensure.IsNotNull(values, nameof(values));
7171

72-
var itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, itemType);
72+
var itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, BsonSerializer.SerializerRegistry, itemType);
7373

7474
var tempDocument = new BsonDocument();
7575
using (var bsonWriter = new BsonDocumentWriter(tempDocument))

src/MongoDB.Driver/Linq/Translators/PredicateTranslator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ private FilterDefinition<BsonDocument> TranslateComparison(Expression variableEx
443443

444444
var fieldExpression = GetFieldExpression(variableExpression);
445445

446-
var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(fieldExpression.Serializer, constantExpression.Type, value);
446+
var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(fieldExpression.Serializer, BsonSerializer.SerializerRegistry, constantExpression.Type, value);
447447
var serializedValue = valueSerializer.ToBsonValue(value);
448448

449449
switch (operatorType)

tests/MongoDB.Driver.Tests/Linq/Translators/PredicateImplicitTypeCastTests.cs renamed to tests/MongoDB.Driver.Tests/Linq/Translators/PredicateValueConversionTests.cs

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,52 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
using FluentAssertions;
7-
using Xunit;
8-
using MongoDB.Bson.Serialization;
9-
using MongoDB.Driver.Linq.Translators;
1+
/* Copyright 2017 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;
1017
using System.ComponentModel;
11-
using System.Linq.Expressions;
12-
using MongoDB.Bson;
1318
using System.Globalization;
19+
using System.Linq.Expressions;
1420
using System.Reflection;
21+
using FluentAssertions;
22+
using MongoDB.Bson;
23+
using MongoDB.Bson.Serialization;
24+
using MongoDB.Driver.Linq.Translators;
25+
using Xunit;
1526

1627
namespace MongoDB.Driver.Tests.Linq.Translators
1728
{
18-
public class PredicateImplicitTypeCastTests : IntegrationTestBase
29+
public class PredicateValueConversionTests : IntegrationTestBase
1930
{
2031
[Fact]
21-
public void Implicit_Type_Casting_Primitive_Types()
32+
public void Value_conversion_with_primitive_types()
2233
{
2334
Assert(
2435
x => x.B == (object)10,
2536
0,
26-
"{B: '10'}");
37+
"{ B : '10' }");
2738
}
2839

2940
[Fact]
30-
public void Implicit_Type_Casting_With_Custom_TypeConverter()
41+
public void Value_conversion_with_custom_type_converter()
3142
{
32-
//register custom type converter for C type
33-
TypeDescriptor.AddAttributes(typeof(C), new TypeConverterAttribute(typeof(CExampleTypeCoverter)));
34-
//cast string to object so it can be used in predicate
35-
var objectToCast = (object)"Dexter";
43+
TypeDescriptor.AddAttributes(typeof(C), new TypeConverterAttribute(typeof(CExampleTypeConverter)));
44+
45+
var objectToConvert = (object)"Dexter";
3646
Assert(
37-
x => x.C == objectToCast,
47+
x => x.C == objectToConvert,
3848
0,
39-
"{C: { '_t' : 'C', D: 'Dexter', E: null, S: null, X: null}}");
49+
"{ C : { '_t' : 'C', D : 'Dexter', E : null, S : null, X : null } }");
4050
}
4151

4252
public void Assert(Expression<Func<Root, bool>> filter, int expectedCount, string expectedFilter)
@@ -53,7 +63,7 @@ public void Assert(Expression<Func<Root, bool>> filter, int expectedCount, strin
5363
/// <summary>
5464
/// Custom type converter for <see cref="C"/> test class.
5565
/// </summary>
56-
public class CExampleTypeCoverter : TypeConverter
66+
private class CExampleTypeConverter : TypeConverter
5767
{
5868
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
5969
{

tests/MongoDB.Driver.Tests/MongoDB.Driver.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
<Compile Include="Linq\MongoQueryableTests.cs" />
134134
<Compile Include="Linq\Processors\PartialEvaluatorTests.cs" />
135135
<Compile Include="Linq\Translators\LegacyPredicateTranslatorTests.cs" />
136+
<Compile Include="Linq\Translators\PredicateValueConversionTests.cs" />
136137
<Compile Include="Linq\Translators\PredicateTranslatorTests.cs" />
137138
<Compile Include="Linq\MongoQueryableNullableEnumComparedToNullableEnumTests.cs" />
138139
<Compile Include="OfTypeMongoCollectionTests.cs" />

0 commit comments

Comments
 (0)