Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSHARP-4820: Always use the configured serializer. #1258

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/MongoDB.Driver/FieldDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public override RenderedFieldDefinition<TField> Render(
}
else if (underlyingSerializer != null)
{
valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField);
valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField, linqProvider);
}
else
{
Expand Down
62 changes: 34 additions & 28 deletions src/MongoDB.Driver/FieldValueSerializerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Linq;
using MongoDB.Driver.Support;

namespace MongoDB.Driver
{
internal static class FieldValueSerializerHelper
{
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType)
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, LinqProvider linqProvider)
{
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false);
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false, linqProvider);
}

public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, bool allowScalarValueForArrayField)
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, bool allowScalarValueForArrayField, LinqProvider linqProvider)
{
var fieldType = fieldSerializer.ValueType;

Expand All @@ -48,15 +49,18 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer
return fieldSerializer;
}

// serialize numeric values without converting them
if (fieldType.IsNumeric() && valueType.IsNumeric())
// serialize numeric values without converting them (only when using LINQ2)
if (linqProvider == LinqProvider.V2)
{
var valueSerializer = BsonSerializer.SerializerRegistry.GetSerializer(valueType);
if (HasStringRepresentation(fieldSerializer))
if (fieldType.IsNumeric() && valueType.IsNumeric())
{
valueSerializer = WithStringRepresentation(valueSerializer);
var valueSerializer = BsonSerializer.SerializerRegistry.GetSerializer(valueType);
if (HasStringRepresentation(fieldSerializer))
{
valueSerializer = WithStringRepresentation(valueSerializer);
}
return valueSerializer;
}
return valueSerializer;
}

var fieldTypeInfo = fieldType.GetTypeInfo();
Expand Down Expand Up @@ -105,21 +109,24 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer
return (IBsonSerializer)nullableEnumConvertingSerializerConstructor.Invoke(new object[] { nonNullableFieldSerializer });
}

// synthesize an IEnumerableSerializer serializer using the item serializer from the field serializer
Type fieldIEnumerableInterfaceType;
Type valueIEnumerableInterfaceType;
Type itemType;
if (
(fieldIEnumerableInterfaceType = fieldType.FindIEnumerable()) != null &&
(valueIEnumerableInterfaceType = valueType.FindIEnumerable()) != null &&
(itemType = fieldIEnumerableInterfaceType.GetSequenceElementType()) == valueIEnumerableInterfaceType.GetSequenceElementType() &&
fieldSerializer is IChildSerializerConfigurable)
// synthesize an IEnumerableSerializer serializer using the item serializer from the field serializer (only when using LINQ2)
if (linqProvider == LinqProvider.V2)
{
var itemSerializer = ((IChildSerializerConfigurable)fieldSerializer).ChildSerializer;
var itemSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(itemType);
var ienumerableSerializerType = typeof(IEnumerableSerializer<>).MakeGenericType(itemType);
var ienumerableSerializerConstructor = ienumerableSerializerType.GetTypeInfo().GetConstructor(new[] { itemSerializerInterfaceType });
return (IBsonSerializer)ienumerableSerializerConstructor.Invoke(new object[] { itemSerializer });
Type fieldIEnumerableInterfaceType;
Type valueIEnumerableInterfaceType;
Type itemType;
if (
(fieldIEnumerableInterfaceType = fieldType.FindIEnumerable()) != null &&
(valueIEnumerableInterfaceType = valueType.FindIEnumerable()) != null &&
(itemType = fieldIEnumerableInterfaceType.GetSequenceElementType()) == valueIEnumerableInterfaceType.GetSequenceElementType() &&
fieldSerializer is IChildSerializerConfigurable)
{
var itemSerializer = ((IChildSerializerConfigurable)fieldSerializer).ChildSerializer;
var itemSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(itemType);
var ienumerableSerializerType = typeof(IEnumerableSerializer<>).MakeGenericType(itemType);
var ienumerableSerializerConstructor = ienumerableSerializerType.GetTypeInfo().GetConstructor(new[] { itemSerializerInterfaceType });
return (IBsonSerializer)ienumerableSerializerConstructor.Invoke(new object[] { itemSerializer });
}
}

if (allowScalarValueForArrayField)
Expand All @@ -132,7 +139,7 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer
if (arraySerializer.TryGetItemSerializationInfo(out itemSerializationInfo))
{
var itemSerializer = itemSerializationInfo.Serializer;
return GetSerializerForValueType(itemSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false);
return GetSerializerForValueType(itemSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false, linqProvider);
}
}
}
Expand All @@ -141,15 +148,15 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer
return ConvertIfPossibleSerializer.Create(valueType, fieldType, fieldSerializer, serializerRegistry);
}

public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, object value)
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, object value, LinqProvider linqProvider)
{
if (!valueType.GetTypeInfo().IsValueType && value == null)
{
return fieldSerializer;
}
else
{
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false);
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false, linqProvider);
}
}

Expand Down Expand Up @@ -216,8 +223,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
}
else
{
var serializer = _serializerRegistry.GetSerializer<TFrom>();
serializer.Serialize(context, args, value);
throw new InvalidOperationException($"Value could not be converted from type {typeof(TFrom)} to type {typeof(TTo)} to be serialized by the proper serializer.");
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/MongoDB.Driver/FilterDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1813,7 +1813,7 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
throw new InvalidOperationException(message);
}
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem));
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem), linqProvider);
}
else
{
Expand Down Expand Up @@ -2445,7 +2445,7 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
throw new InvalidOperationException(message);
}
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem));
itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem), linqProvider);
}
else
{
Expand Down Expand Up @@ -2494,7 +2494,7 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName);
throw new InvalidOperationException(message);
}
itemSerializer = (IBsonSerializer<TItem>)FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem));
itemSerializer = (IBsonSerializer<TItem>)FieldValueSerializerHelper.GetSerializerForValueType(itemSerializationInfo.Serializer, serializerRegistry, typeof(TItem), linqProvider);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static BsonValue SerializeValue(this ISerializationExpression field, Type
{
Ensure.IsNotNull(field, nameof(field));

var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, BsonSerializer.SerializerRegistry, valueType, value);
var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, BsonSerializer.SerializerRegistry, valueType, value, LinqProvider.V2);

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

var itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, BsonSerializer.SerializerRegistry, itemType);
var itemSerializer = FieldValueSerializerHelper.GetSerializerForValueType(field.Serializer, BsonSerializer.SerializerRegistry, itemType, LinqProvider.V2);

var tempDocument = new BsonDocument();
using (var bsonWriter = new BsonDocumentWriter(tempDocument))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ internal override RenderedFieldDefinition<TField> TranslateExpressionToField<TDo

var underlyingSerializer = field.Serializer;
var fieldSerializer = underlyingSerializer as IBsonSerializer<TField>;
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField);
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField, LinqProvider.V2);

return new RenderedFieldDefinition<TField>(field.FieldName, fieldSerializer, valueSerializer, underlyingSerializer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ private FilterDefinition<BsonDocument> TranslateComparison(Expression variableEx

var fieldExpression = GetFieldExpression(variableExpression);

var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(fieldExpression.Serializer, _serializerRegistry, constantExpression.Type, value);
var valueSerializer = FieldValueSerializerHelper.GetSerializerForValueType(fieldExpression.Serializer, _serializerRegistry, constantExpression.Type, value, LinqProvider.V2);
var serializedValue = valueSerializer.ToBsonValue(value);

switch (operatorType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ internal override RenderedFieldDefinition<TField> TranslateExpressionToField<TDo

var underlyingSerializer = field.Serializer;
var fieldSerializer = underlyingSerializer as IBsonSerializer<TField>;
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField);
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField, LinqProvider.V3);

return new RenderedFieldDefinition<TField>(field.Path, fieldSerializer, valueSerializer, underlyingSerializer);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Linq;
using MongoDB.TestHelpers.XunitExtensions;
using Xunit;

namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
{
public class CSharp4820Tests : Linq3IntegrationTest
{
static CSharp4820Tests()
{
BsonClassMap.RegisterClassMap<C>(cm =>
{
cm.AutoMap();
var readonlyCollectionMemberMap = cm.GetMemberMap(x => x.ReadOnlyCollection);
var readOnlyCollectionSerializer = readonlyCollectionMemberMap.GetSerializer();
var bracketingCollectionSerializer = ((IChildSerializerConfigurable)readOnlyCollectionSerializer).WithChildSerializer(new StringBracketingSerializer());
readonlyCollectionMemberMap.SetSerializer(bracketingCollectionSerializer);
});
}

[Theory]
[ParameterAttributeData]
public void Update_Set_with_List_should_work(
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var values = new List<string>() { "abc", "def" };
var update = Builders<C>.Update.Set(x => x.ReadOnlyCollection, values);
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<C>();

var rendered = (BsonDocument)update.Render(documentSerializer, serializerRegistry, linqProvider);

rendered.Should().Be("{ $set : { ReadOnlyCollection : ['[abc]', '[def]'] } }");
}

[Theory]
[ParameterAttributeData]
public void Update_Set_with_Enumerable_should_throw(
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var values = new[] { "abc", "def" }.Select(x => x);
var update = Builders<C>.Update.Set(x => x.ReadOnlyCollection, values);
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<C>();

BsonDocument rendered = null;
var exception = Record.Exception(() => rendered = (BsonDocument)update.Render(documentSerializer, serializerRegistry, linqProvider));

if (linqProvider == LinqProvider.V2)
{
rendered.Should().Be("{ $set : { ReadOnlyCollection : ['[abc]', '[def]'] } }");
}
else
{
exception.Should().BeOfType<InvalidOperationException>();
exception.Message.Should().Contain("Value could not be converted");
}
}

[Theory]
[ParameterAttributeData]
public void Update_Set_with_Enumerable_ToList_should_work(
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var values = new[] { "abc", "def" }.Select(x => x);
var update = Builders<C>.Update.Set(x => x.ReadOnlyCollection, values.ToList());
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<C>();

var rendered = (BsonDocument)update.Render(documentSerializer, serializerRegistry, linqProvider);

rendered.Should().Be("{ $set : { ReadOnlyCollection : ['[abc]', '[def]'] } }");
}

private class C
{
public int Id { get; set; }
public IReadOnlyCollection<string> ReadOnlyCollection { get; set; }
}

private class StringBracketingSerializer : SerializerBase<string>
{
public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bracketedValue = StringSerializer.Instance.Deserialize(context, args);
return bracketedValue.Substring(1, bracketedValue.Length - 2);
}

public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
{
var bracketedValue = "[" + value + "]";
StringSerializer.Instance.Serialize(context, bracketedValue);
}
}
}
}