Skip to content

Commit e7f872a

Browse files
committed
CSHARP-1867: Non-nullable members cannot be compared to nullable values.
1 parent 3e85505 commit e7f872a

File tree

28 files changed

+3073
-69
lines changed

28 files changed

+3073
-69
lines changed

src/MongoDB.Bson/Serialization/IBsonSerializerExtensions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ public static void Serialize<TValue>(this IBsonSerializer<TValue> serializer, Bs
7474
serializer.Serialize(context, args, value);
7575
}
7676

77+
/// <summary>
78+
/// Converts a value to a BsonValue by serializing it.
79+
/// </summary>
80+
/// <param name="serializer">The serializer.</param>
81+
/// <param name="value">The value.</param>
82+
/// <returns>The serialized value.</returns>
83+
public static BsonValue ToBsonValue(this IBsonSerializer serializer, object value)
84+
{
85+
var document = new BsonDocument();
86+
using (var writer = new BsonDocumentWriter(document))
87+
{
88+
var context = BsonSerializationContext.CreateRoot(writer);
89+
writer.WriteStartDocument();
90+
writer.WriteName("x");
91+
serializer.Serialize(context, value);
92+
writer.WriteEndDocument();
93+
}
94+
return document[0];
95+
}
96+
7797
/// <summary>
7898
/// Converts a value to a BsonValue by serializing it.
7999
/// </summary>

src/MongoDB.Driver/FieldDefinition.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2010-2015 MongoDB Inc.
1+
/* Copyright 2010-2016 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -273,7 +273,9 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
273273
throw new InvalidOperationException(message);
274274
}
275275

276-
return new RenderedFieldDefinition<TField>(field.FieldName, (IBsonSerializer<TField>)field.Serializer);
276+
var fieldSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, typeof(TField));
277+
278+
return new RenderedFieldDefinition<TField>(field.FieldName, fieldSerializer);
277279
}
278280
}
279281

@@ -333,9 +335,17 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
333335
IBsonSerializer resolvedSerializer;
334336
StringFieldDefinitionHelper.Resolve<TDocument>(_fieldName, documentSerializer, out resolvedName, out resolvedSerializer);
335337

336-
return new RenderedFieldDefinition<TField>(
337-
resolvedName,
338-
_fieldSerializer ?? (resolvedSerializer as IBsonSerializer<TField>) ?? serializerRegistry.GetSerializer<TField>());
338+
var fieldSerializer = _fieldSerializer;
339+
if (fieldSerializer == null && resolvedSerializer != null)
340+
{
341+
fieldSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(resolvedSerializer, typeof(TField));
342+
}
343+
if (fieldSerializer == null)
344+
{
345+
fieldSerializer = serializerRegistry.GetSerializer<TField>();
346+
}
347+
348+
return new RenderedFieldDefinition<TField>(resolvedName, fieldSerializer);
339349
}
340350
}
341351

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/* Copyright 2016 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.Reflection;
19+
using MongoDB.Bson;
20+
using MongoDB.Bson.Serialization;
21+
using MongoDB.Bson.Serialization.Serializers;
22+
using MongoDB.Driver.Support;
23+
24+
namespace MongoDB.Driver
25+
{
26+
internal static class FieldValueSerializerHelper
27+
{
28+
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, Type valueType)
29+
{
30+
var fieldType = fieldSerializer.ValueType;
31+
32+
// these will normally be equal unless we've removed some Convert(s) that the compiler put in
33+
if (fieldType == valueType)
34+
{
35+
return fieldSerializer;
36+
}
37+
38+
// serialize numeric values without converting them
39+
if (fieldType.IsNumeric() && valueType.IsNumeric())
40+
{
41+
var valueSerializer = BsonSerializer.SerializerRegistry.GetSerializer(valueType);
42+
if (HasStringRepresentation(fieldSerializer))
43+
{
44+
valueSerializer = WithStringRepresentation(valueSerializer);
45+
}
46+
return valueSerializer;
47+
}
48+
49+
var fieldTypeInfo = fieldType.GetTypeInfo();
50+
var fieldSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(fieldType);
51+
var valueTypeInfo = valueType.GetTypeInfo();
52+
53+
// synthesize a NullableSerializer using the field serializer
54+
if (valueType.IsNullable() && valueType.GetNullableUnderlyingType() == fieldType)
55+
{
56+
var nullableSerializerType = typeof(NullableSerializer<>).MakeGenericType(fieldType);
57+
var nullableSerializerConstructor = nullableSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType });
58+
return (IBsonSerializer)nullableSerializerConstructor.Invoke(new object[] { fieldSerializer });
59+
}
60+
61+
// synthesize an EnumConvertingSerializer using the field serializer
62+
if (fieldTypeInfo.IsEnum)
63+
{
64+
var enumConvertingSerializerType = typeof(EnumConvertingSerializer<,>).MakeGenericType(valueType, fieldType);
65+
var enumConvertingSerializerConstructor = enumConvertingSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType });
66+
return (IBsonSerializer)enumConvertingSerializerConstructor.Invoke(new object[] { fieldSerializer });
67+
}
68+
69+
// synthesize a NullableEnumConvertingSerializer using the field serializer
70+
if (fieldType.IsNullableEnum() && valueType.IsNullable())
71+
{
72+
var nonNullableFieldType = fieldType.GetNullableUnderlyingType();
73+
var nonNullableValueType = valueType.GetNullableUnderlyingType();
74+
var nonNullableFieldSerializer = ((IChildSerializerConfigurable)fieldSerializer).ChildSerializer;
75+
var nonNullableFieldSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(nonNullableFieldType);
76+
var nullableEnumConvertingSerializerType = typeof(NullableEnumConvertingSerializer<,>).MakeGenericType(nonNullableValueType, nonNullableFieldType);
77+
var nullableEnumConvertingSerializerConstructor = nullableEnumConvertingSerializerType.GetTypeInfo().GetConstructor(new[] { nonNullableFieldSerializerInterfaceType });
78+
return (IBsonSerializer)nullableEnumConvertingSerializerConstructor.Invoke(new object[] { nonNullableFieldSerializer });
79+
}
80+
81+
// synthesize an IEnumerableSerializer serializer using the item serializer from the field serializer
82+
Type fieldIEnumerableInterfaceType;
83+
Type valueIEnumerableInterfaceType;
84+
Type itemType;
85+
if (
86+
(fieldIEnumerableInterfaceType = fieldType.FindIEnumerable()) != null &&
87+
(valueIEnumerableInterfaceType = valueType.FindIEnumerable()) != null &&
88+
(itemType = fieldIEnumerableInterfaceType.GetSequenceElementType()) == valueIEnumerableInterfaceType.GetSequenceElementType() &&
89+
fieldSerializer is IChildSerializerConfigurable)
90+
{
91+
var itemSerializer = ((IChildSerializerConfigurable)fieldSerializer).ChildSerializer;
92+
var itemSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(itemType);
93+
var ienumerableSerializerType = typeof(IEnumerableSerializer<>).MakeGenericType(itemType);
94+
var ienumerableSerializerConstructor = ienumerableSerializerType.GetTypeInfo().GetConstructor(new[] { itemSerializerInterfaceType });
95+
return (IBsonSerializer)ienumerableSerializerConstructor.Invoke(new object[] { itemSerializer });
96+
}
97+
98+
// otherwise assume that the value can be cast to the right type for the field serializer
99+
var castingSerializerType = typeof(CastingSerializer<,>).MakeGenericType(valueType, fieldType);
100+
var castingSerializerConstructor = castingSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType });
101+
return (IBsonSerializer)castingSerializerConstructor.Invoke(new object[] { fieldSerializer });
102+
}
103+
104+
public static IBsonSerializer<TValue> GetSerializerForValueType<TField, TValue>(IBsonSerializer<TField> fieldSerializer)
105+
{
106+
return (IBsonSerializer<TValue>)GetSerializerForValueType(fieldSerializer, typeof(TValue));
107+
}
108+
109+
// private static methods
110+
private static bool HasStringRepresentation(IBsonSerializer serializer)
111+
{
112+
var configurableSerializer = serializer as IRepresentationConfigurable;
113+
if (configurableSerializer != null)
114+
{
115+
return configurableSerializer.Representation == BsonType.String;
116+
}
117+
else
118+
{
119+
return false;
120+
}
121+
}
122+
123+
private static IBsonSerializer WithStringRepresentation(IBsonSerializer serializer)
124+
{
125+
var configurableSerializer = serializer as IRepresentationConfigurable;
126+
if (configurableSerializer != null)
127+
{
128+
return configurableSerializer.WithRepresentation(BsonType.String);
129+
}
130+
else
131+
{
132+
return serializer;
133+
}
134+
}
135+
136+
// nested types
137+
private class CastingSerializer<TFrom, TTo> : SerializerBase<TFrom>
138+
{
139+
private readonly IBsonSerializer<TTo> _serializer;
140+
141+
public CastingSerializer(IBsonSerializer<TTo> serializer)
142+
{
143+
_serializer = serializer;
144+
}
145+
146+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TFrom value)
147+
{
148+
_serializer.Serialize(context, args, (TTo)(object)value);
149+
}
150+
}
151+
152+
private class EnumConvertingSerializer<TFrom, TTo> : SerializerBase<TFrom>
153+
{
154+
private readonly IBsonSerializer<TTo> _serializer;
155+
156+
public EnumConvertingSerializer(IBsonSerializer<TTo> serializer)
157+
{
158+
_serializer = serializer;
159+
}
160+
161+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TFrom value)
162+
{
163+
_serializer.Serialize(context, args, (TTo)Enum.ToObject(typeof(TTo), (object)value));
164+
}
165+
}
166+
167+
private class IEnumerableSerializer<TItem> : SerializerBase<IEnumerable<TItem>>
168+
{
169+
private readonly IBsonSerializer<TItem> _itemSerializer;
170+
171+
public IEnumerableSerializer(IBsonSerializer<TItem> itemSerializer)
172+
{
173+
_itemSerializer = itemSerializer;
174+
}
175+
176+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IEnumerable<TItem> value)
177+
{
178+
var bsonWriter = context.Writer;
179+
if (value == null)
180+
{
181+
bsonWriter.WriteNull();
182+
}
183+
else
184+
{
185+
bsonWriter.WriteStartArray();
186+
foreach (var item in value)
187+
{
188+
_itemSerializer.Serialize(context, item);
189+
}
190+
bsonWriter.WriteEndArray();
191+
}
192+
}
193+
}
194+
195+
private class NullableEnumConvertingSerializer<TFrom, TTo> : SerializerBase<Nullable<TFrom>> where TFrom : struct where TTo : struct
196+
{
197+
private readonly IBsonSerializer<TTo> _nonNullableEnumSerializer;
198+
199+
public NullableEnumConvertingSerializer(IBsonSerializer<TTo> nonNullableEnumSerializer)
200+
{
201+
_nonNullableEnumSerializer = nonNullableEnumSerializer;
202+
}
203+
204+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Nullable<TFrom> value)
205+
{
206+
if (value == null)
207+
{
208+
context.Writer.WriteNull();
209+
}
210+
else
211+
{
212+
_nonNullableEnumSerializer.Serialize(context, args, (TTo)Enum.ToObject(typeof(TTo), (object)value.Value));
213+
}
214+
}
215+
}
216+
}
217+
}

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

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,32 @@ public static string PrependFieldName(this ISerializationExpression node, string
4545
return CombineFieldNames(prefix, field == null ? null : field.FieldName);
4646
}
4747

48-
public static BsonValue SerializeValue(this ISerializationExpression field, object value)
48+
public static BsonValue SerializeValue(this ISerializationExpression field, Type valueType, object value)
4949
{
5050
Ensure.IsNotNull(field, nameof(field));
5151

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

5454
var tempDocument = new BsonDocument();
5555
using (var bsonWriter = new BsonDocumentWriter(tempDocument))
5656
{
5757
var context = BsonSerializationContext.CreateRoot(bsonWriter);
5858
bsonWriter.WriteStartDocument();
5959
bsonWriter.WriteName("value");
60-
field.Serializer.Serialize(context, value);
60+
valueSerializer.Serialize(context, value);
6161
bsonWriter.WriteEndDocument();
6262
return tempDocument[0];
6363
}
6464
}
6565

66-
public static BsonArray SerializeValues(this ISerializationExpression field, IEnumerable values)
66+
public static BsonArray SerializeValues(this ISerializationExpression field, Type itemType, IEnumerable values)
6767
{
68+
Ensure.IsNotNull(field, nameof(field));
69+
Ensure.IsNotNull(itemType, nameof(itemType));
70+
Ensure.IsNotNull(values, nameof(values));
71+
72+
var itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, itemType);
73+
6874
var tempDocument = new BsonDocument();
6975
using (var bsonWriter = new BsonDocumentWriter(tempDocument))
7076
{
@@ -74,7 +80,7 @@ public static BsonArray SerializeValues(this ISerializationExpression field, IEn
7480
bsonWriter.WriteStartArray();
7581
foreach (var value in values)
7682
{
77-
field.Serializer.Serialize(context, ConvertIfNecessary(field.Serializer.ValueType, value));
83+
itemSerializer.Serialize(context, value);
7884
}
7985
bsonWriter.WriteEndArray();
8086
bsonWriter.WriteEndDocument();
@@ -96,30 +102,5 @@ private static string CombineFieldNames(string prefix, string suffix)
96102

97103
return prefix + "." + suffix;
98104
}
99-
100-
private static object ConvertIfNecessary(Type targetType, object value)
101-
{
102-
if (targetType.GetTypeInfo().IsEnum || targetType.IsNullableEnum())
103-
{
104-
if (value != null)
105-
{
106-
if (targetType.IsNullableEnum())
107-
{
108-
targetType = targetType.GetNullableUnderlyingType();
109-
}
110-
111-
value = Enum.ToObject(targetType, value);
112-
}
113-
}
114-
else if (targetType != typeof(BsonValue) && !targetType.IsNullable())
115-
{
116-
if (value != null && targetType != value.GetType())
117-
{
118-
value = Convert.ChangeType(value, targetType);
119-
}
120-
}
121-
122-
return value;
123-
}
124105
}
125106
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ private BsonValue TranslateSelect(SelectExpression node)
548548

549549
private BsonValue TranslateSerializedConstant(SerializedConstantExpression node)
550550
{
551-
return node.SerializeValue(node.Value);
551+
return node.SerializeValue(node.Type, node.Value);
552552
}
553553

554554
private BsonValue TranslateSkip(SkipExpression node)

0 commit comments

Comments
 (0)