Skip to content

Commit 6c7b2d5

Browse files
kreigrstam
authored andcommitted
CSHARP-1894 (InvalidCastException in FieldValueSerializerHelper on implicit type casting) fix updated to match current upstream master branch.
1 parent 93cdb81 commit 6c7b2d5

File tree

2 files changed

+139
-2
lines changed

2 files changed

+139
-2
lines changed

src/MongoDB.Driver/FieldValueSerializerHelper.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSe
120120
}
121121
}
122122

123-
// if we can't return a valid value serializer based on the field serializer return null
124-
return null;
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 });
125127
}
126128

127129
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, Type valueType, object value)
@@ -237,5 +239,43 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
237239
}
238240
}
239241
}
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+
}
240280
}
241281
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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;
10+
using System.ComponentModel;
11+
using System.Linq.Expressions;
12+
using MongoDB.Bson;
13+
using System.Globalization;
14+
using System.Reflection;
15+
16+
namespace MongoDB.Driver.Tests.Linq.Translators
17+
{
18+
public class PredicateImplicitTypeCastTests : IntegrationTestBase
19+
{
20+
[Fact]
21+
public void Implicit_Type_Casting_Primitive_Types()
22+
{
23+
Assert(
24+
x => x.B == (object)10,
25+
0,
26+
"{B: '10'}");
27+
}
28+
29+
[Fact]
30+
public void Implicit_Type_Casting_With_Custom_TypeConverter()
31+
{
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";
36+
Assert(
37+
x => x.C == objectToCast,
38+
0,
39+
"{C: { '_t' : 'C', D: 'Dexter', E: null, S: null, X: null}}");
40+
}
41+
42+
public void Assert(Expression<Func<Root, bool>> filter, int expectedCount, string expectedFilter)
43+
{
44+
var serializer = BsonSerializer.SerializerRegistry.GetSerializer<Root>();
45+
var filterDocument = PredicateTranslator.Translate(filter, serializer, BsonSerializer.SerializerRegistry);
46+
47+
var list = __collection.FindSync(filterDocument).ToList();
48+
49+
filterDocument.Should().Be(BsonDocument.Parse(expectedFilter));
50+
list.Count.Should().Be(expectedCount);
51+
}
52+
53+
/// <summary>
54+
/// Custom type converter for <see cref="C"/> test class.
55+
/// </summary>
56+
public class CExampleTypeCoverter : TypeConverter
57+
{
58+
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
59+
{
60+
if (sourceType == typeof(string)) return true;
61+
return base.CanConvertFrom(context, sourceType);
62+
}
63+
64+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
65+
{
66+
if (value is string) return CreateFromString(value);
67+
return base.ConvertFrom(context, culture, value);
68+
}
69+
70+
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
71+
{
72+
return (typeof(C).GetTypeInfo().IsAssignableFrom(destinationType));
73+
}
74+
75+
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
76+
{
77+
if (typeof(C).GetTypeInfo().IsAssignableFrom(destinationType))
78+
{
79+
if (value is string)
80+
{
81+
return CreateFromString(value);
82+
}
83+
}
84+
return base.ConvertTo(context, culture, value, destinationType);
85+
}
86+
87+
/// <summary>
88+
/// Creates new <see cref="C"/> instance from string.
89+
/// </summary>
90+
/// <param name="value">String value used for conversion.</param>
91+
private C CreateFromString(object value)
92+
{
93+
return new C { D = (string)value };
94+
}
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)