diff --git a/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs b/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs index 5910571aa82..614e2c20f78 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs @@ -499,6 +499,12 @@ obj is DictionarySerializerBase other && /// public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) { + if (_dictionaryRepresentation == DictionaryRepresentation.Document && !IsNumericType(typeof(TKey))) + { + serializationInfo = null; + return false; + } + var representation = _dictionaryRepresentation == DictionaryRepresentation.ArrayOfArrays ? BsonType.Array : BsonType.Document; @@ -510,6 +516,14 @@ public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationI return true; } + private static bool IsNumericType(Type type) + { + return type == typeof(int) || type == typeof(long) || + type == typeof(short) || type == typeof(byte) || + type == typeof(uint) || type == typeof(ulong) || + type == typeof(ushort) || type == typeof(sbyte); + } + /// public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderHelperMethods.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderHelperMethods.cs index 7ca8e8e5449..06f0374bf77 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderHelperMethods.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderHelperMethods.cs @@ -17,7 +17,9 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Linq.Linq3Implementation.Misc; using MongoDB.Driver.Linq.Linq3Implementation.Serializers; @@ -81,7 +83,21 @@ IBsonSerializer CreateCollectionSerializerFromCollectionSerializer(Type collecti return UnknowableSerializer.Create(collectionType); } - var itemSerializer = collectionSerializer.GetItemSerializer(); + IBsonSerializer itemSerializer; + if (collectionSerializer is IBsonDictionarySerializer dictionarySerializer && + collectionSerializer is IBsonArraySerializer arraySerializer && + !arraySerializer.TryGetItemSerializationInfo(out _)) + { + var representation = dictionarySerializer.DictionaryRepresentation == DictionaryRepresentation.ArrayOfArrays + ? BsonType.Array + : BsonType.Document; + itemSerializer = KeyValuePairSerializer.Create(representation, dictionarySerializer.KeySerializer, dictionarySerializer.ValueSerializer); + } + else + { + itemSerializer = collectionSerializer.GetItemSerializer(); + } + return CreateCollectionSerializerFromItemSerializer(collectionType, itemSerializer); } @@ -235,11 +251,22 @@ private void DeduceUnknowableSerializer(Expression node) private bool IsItemSerializerKnown(Expression node, out IBsonSerializer itemSerializer) { if (IsKnown(node, out var nodeSerializer) && - nodeSerializer is IBsonArraySerializer arraySerializer && - arraySerializer.TryGetItemSerializationInfo(out var itemSerializationInfo)) + nodeSerializer is IBsonArraySerializer arraySerializer) { - itemSerializer = itemSerializationInfo.Serializer; - return true; + if (arraySerializer.TryGetItemSerializationInfo(out var itemSerializationInfo)) + { + itemSerializer = itemSerializationInfo.Serializer; + return true; + } + + if (nodeSerializer is IBsonDictionarySerializer dictionarySerializer) + { + var representation = dictionarySerializer.DictionaryRepresentation == DictionaryRepresentation.ArrayOfArrays + ? BsonType.Array + : BsonType.Document; + itemSerializer = KeyValuePairSerializer.Create(representation, dictionarySerializer.KeySerializer, dictionarySerializer.ValueSerializer); + return true; + } } itemSerializer = null; diff --git a/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs index f19d684d00b..403e313b575 100644 --- a/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs @@ -20,6 +20,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.TestHelpers; using MongoDB.Driver.Linq; using MongoDB.TestHelpers.XunitExtensions; @@ -66,6 +67,102 @@ public void AddToSetEach_Typed() Assert(subject.AddToSetEach("FavoriteColors", new[] { "green", "violet" }), "{$addToSet: {colors: {$each: ['green', 'violet']}}}"); } + [Fact] + public void AddToSet_with_string_path_through_dictionary_to_list_value() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.myKey", "newValue"), "{$addToSet: {'buckets.myKey': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_expression_path_through_dictionary_to_list_value() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet(x => x.Buckets["myKey"], "newValue"), "{$addToSet: {'buckets.myKey': 'newValue'}}"); + } + + [Fact] + public void AddToSetEach_with_string_path_through_dictionary_to_list_value() + { + var subject = CreateSubject(); + + Assert(subject.AddToSetEach("Buckets.myKey", new[] { "a", "b" }), "{$addToSet: {'buckets.myKey': {$each: ['a', 'b']}}}"); + } + + [Fact] + public void AddToSetEach_with_expression_path_through_dictionary_to_list_value() + { + var subject = CreateSubject(); + + Assert(subject.AddToSetEach(x => x.Buckets["myKey"], new[] { "a", "b" }), "{$addToSet: {'buckets.myKey': {$each: ['a', 'b']}}}"); + } + + [Fact] + public void AddToSet_with_string_path_through_dictionary_to_nested_list() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.myKey.Items", "newValue"), "{$addToSet: {'buckets.myKey.items': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_expression_path_through_dictionary_to_nested_list() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet(x => x.Buckets["myKey"].Items, "newValue"), "{$addToSet: {'buckets.myKey.items': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_string_path_through_dictionary_to_hashset_value() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.myKey", "newValue"), "{$addToSet: {'buckets.myKey': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_expression_path_through_dictionary_to_hashset_value() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet(x => x.Buckets["myKey"], "newValue"), "{$addToSet: {'buckets.myKey': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_string_path_through_array_rep_dictionary() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.myKey", "newValue"), "{$addToSet: {'buckets.myKey': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_string_path_through_dictionary_where_value_is_IEnumerable_interface() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.myKey", "newValue"), "{$addToSet: {'buckets.myKey': 'newValue'}}"); + } + + [Fact] + public void AddToSet_with_numeric_string_key_through_dictionary() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.10", new DataRecord { Label = "test" }), "{$addToSet: {'buckets.10': { CreatedAt: ISODate('0001-01-01T00:00:00Z'), Label: 'test' }}}"); + } + + [Fact] + public void AddToSet_with_numeric_string_key_through_dictionary_of_List() + { + var subject = CreateSubject(); + + Assert(subject.AddToSet("Buckets.10", "newValue"), "{$addToSet: {'buckets.10': 'newValue'}}"); + } + [Fact] public void BitwiseAnd() { @@ -742,5 +839,70 @@ public class F { public string Z { get; set; } } + + private class DocumentWithDictionaryOfLists + { + public ObjectId Id { get; set; } + + [BsonElement("buckets")] + public Dictionary> Buckets { get; set; } + } + + private class DocumentWithDictionaryOfObjects + { + public ObjectId Id { get; set; } + + [BsonElement("buckets")] + public Dictionary Buckets { get; set; } + } + + private class DocumentWithDictionaryOfHashSets + { + public ObjectId Id { get; set; } + + [BsonElement("buckets")] + public Dictionary> Buckets { get; set; } + } + + private class DocumentWithArrayRepDictionary + { + public ObjectId Id { get; set; } + + [BsonElement("buckets")] + [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)] + public Dictionary> Buckets { get; set; } + } + + private class DocumentWithDictionaryOfIEnumerable + { + public ObjectId Id { get; set; } + + [BsonElement("buckets")] + public Dictionary> Buckets { get; set; } + } + + [BsonIgnoreExtraElements] + private class DataContainer + { + public string Id { get; set; } + + [BsonElement("buckets")] + [BsonDictionaryOptions(DictionaryRepresentation.Document)] + public IDictionary> Buckets { get; set; } + = new Dictionary>(); + } + + [BsonIgnoreExtraElements] + private class DataRecord + { + public DateTime CreatedAt { get; set; } + public string Label { get; set; } + } + + private class Bucket + { + [BsonElement("items")] + public List Items { get; set; } + } } }