diff --git a/src/MongoDB.Bson/BsonDefaults.cs b/src/MongoDB.Bson/BsonDefaults.cs index 9352d3228ff..61e97ac8b66 100644 --- a/src/MongoDB.Bson/BsonDefaults.cs +++ b/src/MongoDB.Bson/BsonDefaults.cs @@ -24,33 +24,14 @@ namespace MongoDB.Bson /// public static class BsonDefaults { - // private static fields - private static bool __dynamicArraySerializerWasSet; - private static IBsonSerializer __dynamicArraySerializer; - private static bool __dynamicDocumentSerializerWasSet; - private static IBsonSerializer __dynamicDocumentSerializer; - private static int __maxDocumentSize = int.MaxValue; - private static int __maxSerializationDepth = 100; - // public static properties /// /// Gets or sets the dynamic array serializer. /// public static IBsonSerializer DynamicArraySerializer { - get - { - if (!__dynamicArraySerializerWasSet) - { - __dynamicArraySerializer = BsonSerializer.LookupSerializer>(); - } - return __dynamicArraySerializer; - } - set - { - __dynamicArraySerializerWasSet = true; - __dynamicArraySerializer = value; - } + get => BsonSerializationDomain.Default.BsonDefaults.DynamicArraySerializer; + set => BsonSerializationDomain.Default.BsonDefaults.DynamicArraySerializer = value; } /// @@ -58,28 +39,22 @@ public static IBsonSerializer DynamicArraySerializer /// public static IBsonSerializer DynamicDocumentSerializer { - get - { - if (!__dynamicDocumentSerializerWasSet) - { - __dynamicDocumentSerializer = BsonSerializer.LookupSerializer(); - } - return __dynamicDocumentSerializer; - } - set - { - __dynamicDocumentSerializerWasSet = true; - __dynamicDocumentSerializer = value; - } + get => BsonSerializationDomain.Default.BsonDefaults.DynamicDocumentSerializer; + set => BsonSerializationDomain.Default.BsonDefaults.DynamicDocumentSerializer = value; } + /* DOMAIN-API We should modify the API to have those two values (and in the writer/reader settings where they are used) be nullable. + * The problem is that we need to now when these values have been set externally or not. If they have not, then they should + * be retrieved from the closest domain. + */ + /// /// Gets or sets the default max document size. The default is 4MiB. /// public static int MaxDocumentSize { - get { return __maxDocumentSize; } - set { __maxDocumentSize = value; } + get => BsonSerializationDomain.Default.BsonDefaults.MaxDocumentSize; + set => BsonSerializationDomain.Default.BsonDefaults.MaxDocumentSize = value; } /// @@ -87,8 +62,8 @@ public static int MaxDocumentSize /// public static int MaxSerializationDepth { - get { return __maxSerializationDepth; } - set { __maxSerializationDepth = value; } + get => BsonSerializationDomain.Default.BsonDefaults.MaxSerializationDepth; + set => BsonSerializationDomain.Default.BsonDefaults.MaxSerializationDepth = value; } } } diff --git a/src/MongoDB.Bson/BsonDefaultsRegistry.cs b/src/MongoDB.Bson/BsonDefaultsRegistry.cs new file mode 100644 index 00000000000..f7aae832128 --- /dev/null +++ b/src/MongoDB.Bson/BsonDefaultsRegistry.cs @@ -0,0 +1,73 @@ +/* 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.Collections.Generic; +using System.Dynamic; +using MongoDB.Bson.Serialization; + +namespace MongoDB.Bson +{ + internal class BsonDefaultsRegistry : IBsonDefaults + { + private IBsonSerializationDomain _serializationDomain; + private bool _dynamicArraySerializerWasSet; + private IBsonSerializer _dynamicArraySerializer; + private bool _dynamicDocumentSerializerWasSet; + private IBsonSerializer _dynamicDocumentSerializer; + + public BsonDefaultsRegistry(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + } + + public IBsonSerializer DynamicArraySerializer + { + get + { + if (!_dynamicArraySerializerWasSet) + { + _dynamicArraySerializer = _serializationDomain.LookupSerializer>(); + } + return _dynamicArraySerializer; + } + set + { + _dynamicArraySerializerWasSet = true; + _dynamicArraySerializer = value; + } + } + + public IBsonSerializer DynamicDocumentSerializer + { + get + { + if (!_dynamicDocumentSerializerWasSet) + { + _dynamicDocumentSerializer = _serializationDomain.LookupSerializer(); + } + return _dynamicDocumentSerializer; + } + set + { + _dynamicDocumentSerializerWasSet = true; + _dynamicDocumentSerializer = value; + } + } + + public int MaxDocumentSize { get; set; } = int.MaxValue; + + public int MaxSerializationDepth { get; set; } = 100; + } +} diff --git a/src/MongoDB.Bson/BsonExtensionMethods.cs b/src/MongoDB.Bson/BsonExtensionMethods.cs index bcb41a8271f..edb285caa3a 100644 --- a/src/MongoDB.Bson/BsonExtensionMethods.cs +++ b/src/MongoDB.Bson/BsonExtensionMethods.cs @@ -84,7 +84,7 @@ public static byte[] ToBson( if (serializer == null) { - serializer = BsonSerializer.LookupSerializer(nominalType); + serializer = BsonSerializationDomain.Default.LookupSerializer(nominalType); } if (serializer.ValueType != nominalType) { @@ -165,7 +165,7 @@ public static BsonDocument ToBsonDocument( return convertibleToBsonDocument.ToBsonDocument(); // use the provided ToBsonDocument method } - serializer = BsonSerializer.LookupSerializer(nominalType); + serializer = BsonSerializationDomain.Default.LookupSerializer(nominalType); } if (serializer.ValueType != nominalType) { @@ -236,7 +236,7 @@ public static string ToJson( if (serializer == null) { - serializer = BsonSerializer.LookupSerializer(nominalType); + serializer = BsonSerializationDomain.Default.LookupSerializer(nominalType); } if (serializer.ValueType != nominalType) { diff --git a/src/MongoDB.Bson/IBsonDefaults.cs b/src/MongoDB.Bson/IBsonDefaults.cs new file mode 100644 index 00000000000..e843c8eb4a4 --- /dev/null +++ b/src/MongoDB.Bson/IBsonDefaults.cs @@ -0,0 +1,39 @@ +/* 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 MongoDB.Bson.Serialization; + +namespace MongoDB.Bson +{ + internal interface IBsonDefaults + { + /// + /// + /// + IBsonSerializer DynamicArraySerializer { get; set; } + /// + /// + /// + IBsonSerializer DynamicDocumentSerializer { get; set; } + /// + /// + /// + int MaxDocumentSize { get; set; } + /// + /// + /// + int MaxSerializationDepth { get; set; } + } +} \ No newline at end of file diff --git a/src/MongoDB.Bson/IO/BsonBinaryReaderSettings.cs b/src/MongoDB.Bson/IO/BsonBinaryReaderSettings.cs index 1c510ed71e3..89b1c4d1892 100644 --- a/src/MongoDB.Bson/IO/BsonBinaryReaderSettings.cs +++ b/src/MongoDB.Bson/IO/BsonBinaryReaderSettings.cs @@ -15,6 +15,7 @@ using System; using System.Text; +using MongoDB.Bson.Serialization; namespace MongoDB.Bson.IO { @@ -30,7 +31,7 @@ public class BsonBinaryReaderSettings : BsonReaderSettings private UTF8Encoding _encoding = Utf8Encodings.Strict; private bool _fixOldBinarySubTypeOnInput = true; private bool _fixOldDateTimeMaxValueOnInput = true; - private int _maxDocumentSize = BsonDefaults.MaxDocumentSize; + private int _maxDocumentSize = BsonSerializationDomain.Default.BsonDefaults.MaxDocumentSize; // constructors /// diff --git a/src/MongoDB.Bson/IO/BsonBinaryWriterSettings.cs b/src/MongoDB.Bson/IO/BsonBinaryWriterSettings.cs index 96c8f3168de..10a337bac29 100644 --- a/src/MongoDB.Bson/IO/BsonBinaryWriterSettings.cs +++ b/src/MongoDB.Bson/IO/BsonBinaryWriterSettings.cs @@ -15,6 +15,7 @@ using System; using System.Text; +using MongoDB.Bson.Serialization; namespace MongoDB.Bson.IO { @@ -29,7 +30,7 @@ public class BsonBinaryWriterSettings : BsonWriterSettings // private fields private UTF8Encoding _encoding = Utf8Encodings.Strict; private bool _fixOldBinarySubTypeOnOutput = true; - private int _maxDocumentSize = BsonDefaults.MaxDocumentSize; + private int _maxDocumentSize = BsonSerializationDomain.Default.BsonDefaults.MaxDocumentSize; // constructors /// diff --git a/src/MongoDB.Bson/IO/BsonWriterSettings.cs b/src/MongoDB.Bson/IO/BsonWriterSettings.cs index 06957993d70..f85cab99da5 100644 --- a/src/MongoDB.Bson/IO/BsonWriterSettings.cs +++ b/src/MongoDB.Bson/IO/BsonWriterSettings.cs @@ -14,6 +14,7 @@ */ using System; +using MongoDB.Bson.Serialization; namespace MongoDB.Bson.IO { @@ -24,7 +25,7 @@ public abstract class BsonWriterSettings { // private fields private bool _isFrozen; - private int _maxSerializationDepth = BsonDefaults.MaxSerializationDepth; + private int _maxSerializationDepth = BsonSerializationDomain.Default.BsonDefaults.MaxSerializationDepth; // constructors /// diff --git a/src/MongoDB.Bson/ObjectModel/BsonDocumentWrapper.cs b/src/MongoDB.Bson/ObjectModel/BsonDocumentWrapper.cs index 09edc32dbfa..92d507b1ec2 100644 --- a/src/MongoDB.Bson/ObjectModel/BsonDocumentWrapper.cs +++ b/src/MongoDB.Bson/ObjectModel/BsonDocumentWrapper.cs @@ -104,7 +104,12 @@ public static BsonDocumentWrapper Create(TNominalType value) /// A BsonDocumentWrapper. public static BsonDocumentWrapper Create(Type nominalType, object value) { - var serializer = BsonSerializer.LookupSerializer(nominalType); + return Create(BsonSerializationDomain.Default, nominalType, value); + } + + internal static BsonDocumentWrapper Create(IBsonSerializationDomain serializationDomain, Type nominalType, object value) + { + var serializer = serializationDomain.LookupSerializer(nominalType); return new BsonDocumentWrapper(value, serializer); } @@ -116,12 +121,21 @@ public static BsonDocumentWrapper Create(Type nominalType, object value) /// A list of BsonDocumentWrappers. public static IEnumerable CreateMultiple(IEnumerable values) { + return CreateMultiple(BsonSerializationDomain.Default, values); + } + + internal static IEnumerable CreateMultiple(IBsonSerializationDomain serializationDomain, IEnumerable values) + { + if (serializationDomain == null) + { + throw new ArgumentNullException("serializationDomain"); + } if (values == null) { throw new ArgumentNullException("values"); } - var serializer = BsonSerializer.LookupSerializer(typeof(TNominalType)); + var serializer = serializationDomain.LookupSerializer(typeof(TNominalType)); return values.Select(v => new BsonDocumentWrapper(v, serializer)); } @@ -132,6 +146,11 @@ public static IEnumerable CreateMultiple(IEnu /// A list of wrapped objects. /// A list of BsonDocumentWrappers. public static IEnumerable CreateMultiple(Type nominalType, IEnumerable values) + { + return CreateMultiple(BsonSerializationDomain.Default, nominalType, values); + } + + internal static IEnumerable CreateMultiple(IBsonSerializationDomain serializationDomain, Type nominalType, IEnumerable values) { if (nominalType == null) { @@ -142,7 +161,7 @@ public static IEnumerable CreateMultiple(Type nominalType, throw new ArgumentNullException("values"); } - var serializer = BsonSerializer.LookupSerializer(nominalType); + var serializer = serializationDomain.LookupSerializer(nominalType); return values.Cast().Select(v => new BsonDocumentWrapper(v, serializer)); } diff --git a/src/MongoDB.Bson/Properties/AssemblyInfo.cs b/src/MongoDB.Bson/Properties/AssemblyInfo.cs index ee574295e6c..2a1d28a924a 100644 --- a/src/MongoDB.Bson/Properties/AssemblyInfo.cs +++ b/src/MongoDB.Bson/Properties/AssemblyInfo.cs @@ -28,3 +28,8 @@ [assembly: InternalsVisibleTo("MongoDB.Bson.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] [assembly: InternalsVisibleTo("MongoDB.Analyzer.MQLGenerator, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Encryption, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Authentication.AWS, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.TestHelpers, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] diff --git a/src/MongoDB.Bson/Serialization/BsonClassMap.cs b/src/MongoDB.Bson/Serialization/BsonClassMap.cs index 0f524fe2326..57d3845cec3 100644 --- a/src/MongoDB.Bson/Serialization/BsonClassMap.cs +++ b/src/MongoDB.Bson/Serialization/BsonClassMap.cs @@ -29,13 +29,8 @@ namespace MongoDB.Bson.Serialization /// /// Represents a mapping between a class and a BSON document. /// - public class BsonClassMap + public class BsonClassMap : IHasSerializationDomain { - // private static fields - private readonly static Dictionary __classMaps = new Dictionary(); - private readonly static Queue __knownTypesQueue = new Queue(); - private static int __freezeNestingLevel = 0; - // private fields private readonly Type _classType; private readonly List _creatorMaps; @@ -45,6 +40,7 @@ public class BsonClassMap private readonly ReadOnlyCollection _allMemberMapsReadonly; private List _declaredMemberMaps; // only the members declared in this class private readonly BsonTrie _elementTrie; + private readonly IBsonSerializationDomain _serializationDomain; private bool _frozen; // once a class map has been frozen no further changes are allowed private BsonClassMap _baseClassMap; // null for class object and interfaces @@ -67,7 +63,13 @@ public class BsonClassMap /// /// The class type. public BsonClassMap(Type classType) + : this(BsonSerializationDomain.Default, classType) { + } + + internal BsonClassMap(IBsonSerializationDomain serializationDomain, Type classType) + { + _serializationDomain = serializationDomain; _classType = classType; _creatorMaps = new List(); _conventionPack = ConventionRegistry.Lookup(classType); @@ -86,7 +88,13 @@ public BsonClassMap(Type classType) /// Type of the class. /// The base class map. public BsonClassMap(Type classType, BsonClassMap baseClassMap) - : this(classType) + : this(BsonSerializationDomain.Default, classType, baseClassMap) + { + throw new InvalidOperationException(); + } + + internal BsonClassMap(IBsonSerializationDomain serializationDomain, Type classType, BsonClassMap baseClassMap) + : this(serializationDomain, classType) { _baseClassMap = baseClassMap; } @@ -253,132 +261,41 @@ internal int ExtraElementsMemberMapIndex get { return _extraElementsMemberIndex; } } - // public static methods - /// - /// Gets the type of a member. - /// - /// The member info. - /// The type of the member. - public static Type GetMemberInfoType(MemberInfo memberInfo) - { - if (memberInfo == null) - { - throw new ArgumentNullException("memberInfo"); - } + internal IBsonSerializationDomain SerializationDomain => _serializationDomain; - if (memberInfo is FieldInfo) - { - return ((FieldInfo)memberInfo).FieldType; - } - else if (memberInfo is PropertyInfo) - { - return ((PropertyInfo)memberInfo).PropertyType; - } - - throw new NotSupportedException("Only field and properties are supported at this time."); - } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public static methods /// /// Gets all registered class maps. /// /// All registered class maps. - public static IEnumerable GetRegisteredClassMaps() - { - BsonSerializer.ConfigLock.EnterReadLock(); - try - { - return __classMaps.Values.ToList(); // return a copy for thread safety - } - finally - { - BsonSerializer.ConfigLock.ExitReadLock(); - } - } + public static IEnumerable GetRegisteredClassMaps() => + BsonSerializationDomain.Default.ClassMapRegistry.GetRegisteredClassMaps(); /// /// Checks whether a class map is registered for a type. /// /// The type to check. /// True if there is a class map registered for the type. - public static bool IsClassMapRegistered(Type type) - { - if (type == null) - { - throw new ArgumentNullException("type"); - } - - BsonSerializer.ConfigLock.EnterReadLock(); - try - { - return __classMaps.ContainsKey(type); - } - finally - { - BsonSerializer.ConfigLock.ExitReadLock(); - } - } + public static bool IsClassMapRegistered(Type type) => + BsonSerializationDomain.Default.ClassMapRegistry.IsClassMapRegistered(type); /// /// Looks up a class map (will AutoMap the class if no class map is registered). /// /// The class type. /// The class map. - public static BsonClassMap LookupClassMap(Type classType) - { - if (classType == null) - { - throw new ArgumentNullException("classType"); - } - - BsonSerializer.ConfigLock.EnterReadLock(); - try - { - if (__classMaps.TryGetValue(classType, out var classMap)) - { - if (classMap.IsFrozen) - { - return classMap; - } - } - } - finally - { - BsonSerializer.ConfigLock.ExitReadLock(); - } - - // automatically create a new classMap for classType and register it (unless another thread does first) - // do the work of speculatively creating a new class map outside of holding any lock - var classMapDefinition = typeof(BsonClassMap<>); - var classMapType = classMapDefinition.MakeGenericType(classType); - var newClassMap = (BsonClassMap)Activator.CreateInstance(classMapType); - newClassMap.AutoMap(); - - BsonSerializer.ConfigLock.EnterWriteLock(); - try - { - if (!__classMaps.TryGetValue(classType, out var classMap)) - { - RegisterClassMap(newClassMap); - classMap = newClassMap; - } - - return classMap.Freeze(); - } - finally - { - BsonSerializer.ConfigLock.ExitWriteLock(); - } - } + public static BsonClassMap LookupClassMap(Type classType) => + BsonSerializationDomain.Default.ClassMapRegistry.LookupClassMap(classType); /// /// Creates and registers a class map. /// /// The class. /// The class map. - public static BsonClassMap RegisterClassMap() - { - return RegisterClassMap(cm => { cm.AutoMap(); }); - } + public static BsonClassMap RegisterClassMap()=> + BsonSerializationDomain.Default.ClassMapRegistry.RegisterClassMap(); /// /// Creates and registers a class map. @@ -387,35 +304,14 @@ public static BsonClassMap RegisterClassMap() /// The class map initializer. /// The class map. public static BsonClassMap RegisterClassMap(Action> classMapInitializer) - { - var classMap = new BsonClassMap(classMapInitializer); - RegisterClassMap(classMap); - return classMap; - } + => BsonSerializationDomain.Default.ClassMapRegistry.RegisterClassMap(classMapInitializer); /// /// Registers a class map. /// /// The class map. public static void RegisterClassMap(BsonClassMap classMap) - { - if (classMap == null) - { - throw new ArgumentNullException("classMap"); - } - - BsonSerializer.ConfigLock.EnterWriteLock(); - try - { - // note: class maps can NOT be replaced (because derived classes refer to existing instance) - __classMaps.Add(classMap.ClassType, classMap); - BsonSerializer.RegisterDiscriminator(classMap.ClassType, classMap.Discriminator); - } - finally - { - BsonSerializer.ConfigLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.ClassMapRegistry.RegisterClassMap(classMap); /// /// Registers a class map if it is not already registered. @@ -423,16 +319,7 @@ public static void RegisterClassMap(BsonClassMap classMap) /// The class. /// True if this call registered the class map, false if the class map was already registered. public static bool TryRegisterClassMap() - { - return TryRegisterClassMap(ClassMapFactory); - - static BsonClassMap ClassMapFactory() - { - var classMap = new BsonClassMap(); - classMap.AutoMap(); - return classMap; - } - } + => BsonSerializationDomain.Default.ClassMapRegistry.TryRegisterClassMap(); /// /// Registers a class map if it is not already registered. @@ -441,19 +328,7 @@ static BsonClassMap ClassMapFactory() /// The class map. /// True if this call registered the class map, false if the class map was already registered. public static bool TryRegisterClassMap(BsonClassMap classMap) - { - if (classMap == null) - { - throw new ArgumentNullException(nameof(classMap)); - } - - return TryRegisterClassMap(ClassMapFactory); - - BsonClassMap ClassMapFactory() - { - return classMap; - } - } + => BsonSerializationDomain.Default.ClassMapRegistry.TryRegisterClassMap(classMap); /// /// Registers a class map if it is not already registered. @@ -462,19 +337,7 @@ BsonClassMap ClassMapFactory() /// The class map initializer (only called if the class map is not already registered). /// True if this call registered the class map, false if the class map was already registered. public static bool TryRegisterClassMap(Action> classMapInitializer) - { - if (classMapInitializer == null) - { - throw new ArgumentNullException(nameof(classMapInitializer)); - } - - return TryRegisterClassMap(ClassMapFactory); - - BsonClassMap ClassMapFactory() - { - return new BsonClassMap(classMapInitializer); - } - } + => BsonSerializationDomain.Default.ClassMapRegistry.TryRegisterClassMap(classMapInitializer); /// /// Registers a class map if it is not already registered. @@ -483,45 +346,7 @@ BsonClassMap ClassMapFactory() /// The class map factory (only called if the class map is not already registered). /// True if this call registered the class map, false if the class map was already registered. public static bool TryRegisterClassMap(Func> classMapFactory) - { - if (classMapFactory == null) - { - throw new ArgumentNullException(nameof(classMapFactory)); - } - - BsonSerializer.ConfigLock.EnterReadLock(); - try - { - if (__classMaps.ContainsKey(typeof(TClass))) - { - return false; - } - } - finally - { - BsonSerializer.ConfigLock.ExitReadLock(); - } - - BsonSerializer.ConfigLock.EnterWriteLock(); - try - { - if (__classMaps.ContainsKey(typeof(TClass))) - { - return false; - } - else - { - // create a classMap for TClass and register it - var classMap = classMapFactory(); - RegisterClassMap(classMap); - return true; - } - } - finally - { - BsonSerializer.ConfigLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.ClassMapRegistry.TryRegisterClassMap(classMapFactory); // public methods /// @@ -573,13 +398,29 @@ obj is BsonClassMap other && /// public override int GetHashCode() => 0; + internal class FreezeContext + { + public int FreezeNestingLevel { get; set; } = 0; + public Queue KnownTypesQueue { get; set; } = new(); + public IBsonSerializationDomain SerializationDomain { get; set; } + } + /// /// Freezes the class map. /// /// The frozen class map. - public BsonClassMap Freeze() + public BsonClassMap Freeze() => Freeze(BsonSerializationDomain.Default); + + internal BsonClassMap Freeze(IBsonSerializationDomain serializationDomain) + { + var freezeContext = new FreezeContext { SerializationDomain = serializationDomain }; + return Freeze(freezeContext); + } + + private BsonClassMap Freeze(FreezeContext context) { - BsonSerializer.ConfigLock.EnterReadLock(); + var configLock = context.SerializationDomain!.ConfigLock; + configLock.EnterReadLock(); try { if (_frozen) @@ -589,15 +430,15 @@ public BsonClassMap Freeze() } finally { - BsonSerializer.ConfigLock.ExitReadLock(); + configLock.ExitReadLock(); } - BsonSerializer.ConfigLock.EnterWriteLock(); + configLock.EnterWriteLock(); try { if (!_frozen) { - __freezeNestingLevel++; + context.FreezeNestingLevel++; try { var baseType = _classType.GetTypeInfo().BaseType; @@ -605,9 +446,9 @@ public BsonClassMap Freeze() { if (_baseClassMap == null) { - _baseClassMap = LookupClassMap(baseType); + _baseClassMap = context.SerializationDomain.ClassMapRegistry.LookupClassMap(baseType); } - _baseClassMap.Freeze(); + _baseClassMap.Freeze(context); _discriminatorIsRequired |= _baseClassMap._discriminatorIsRequired; _hasRootClass |= (_isRootClass || _baseClassMap.HasRootClass); _allMemberMaps.AddRange(_baseClassMap.AllMemberMaps); @@ -699,28 +540,28 @@ public BsonClassMap Freeze() // this avoids infinite recursion when going back down the inheritance tree while processing known types foreach (var knownType in _knownTypes) { - __knownTypesQueue.Enqueue(knownType); + context.KnownTypesQueue.Enqueue(knownType); } // if we are back to the first level go ahead and process any queued known types - if (__freezeNestingLevel == 1) + if (context.FreezeNestingLevel == 1) { - while (__knownTypesQueue.Count != 0) + while (context.KnownTypesQueue.Count != 0) { - var knownType = __knownTypesQueue.Dequeue(); - LookupClassMap(knownType); // will AutoMap and/or Freeze knownType if necessary + var knownType = context.KnownTypesQueue.Dequeue(); + context.SerializationDomain.ClassMapRegistry.LookupClassMap(knownType); // will AutoMap and/or Freeze knownType if necessary } } } finally { - __freezeNestingLevel--; + context.FreezeNestingLevel--; } } } finally { - BsonSerializer.ConfigLock.ExitWriteLock(); + configLock.ExitWriteLock(); } return this; } @@ -1019,7 +860,7 @@ public BsonMemberMap MapMember(MemberInfo memberInfo) var memberMap = _declaredMemberMaps.Find(m => m.MemberInfo == memberInfo); if (memberMap == null) { - memberMap = new BsonMemberMap(this, memberInfo); + memberMap = new BsonMemberMap(_serializationDomain, this, memberInfo); _declaredMemberMaps.Add(memberMap); } return memberMap; @@ -1354,7 +1195,7 @@ IDiscriminatorConvention LookupDiscriminatorConvention() return classMap._discriminatorConvention; } - if (BsonSerializer.IsDiscriminatorConventionRegisteredAtThisLevel(classMap._classType)) + if (_serializationDomain.IsDiscriminatorConventionRegisteredAtThisLevel(classMap._classType)) { // in this case LookupDiscriminatorConvention below will find it break; @@ -1363,21 +1204,23 @@ IDiscriminatorConvention LookupDiscriminatorConvention() if (classMap._isRootClass) { // in this case auto-register a hierarchical convention for the root class and look it up as usual below - BsonSerializer.GetOrRegisterDiscriminatorConvention(classMap._classType, StandardDiscriminatorConvention.Hierarchical); + var discriminatorConvention = new HierarchicalDiscriminatorConvention(_serializationDomain, "_t"); + _serializationDomain.GetOrRegisterDiscriminatorConvention(classMap._classType, discriminatorConvention); break; } classMap = classMap._baseClassMap; } - return BsonSerializer.LookupDiscriminatorConvention(_classType); + return _serializationDomain.LookupDiscriminatorConvention(_classType); } } // private methods private void AutoMapClass() { - new ConventionRunner(_conventionPack).Apply(this); + var conventionPack = _serializationDomain.ConventionRegistry.Lookup(_classType); + new ConventionRunner(conventionPack).Apply(this); foreach (var memberMap in _declaredMemberMaps) { @@ -1484,7 +1327,12 @@ public class BsonClassMap : BsonClassMap /// Initializes a new instance of the BsonClassMap class. /// public BsonClassMap() - : base(typeof(TClass)) + : this(BsonSerializationDomain.Default) + { + } + + internal BsonClassMap(IBsonSerializationDomain serializationDomain) + : base(serializationDomain, typeof(TClass)) { } @@ -1493,7 +1341,12 @@ public BsonClassMap() /// /// The class map initializer. public BsonClassMap(Action> classMapInitializer) - : base(typeof(TClass)) + : this(BsonSerializationDomain.Default, classMapInitializer) + { + } + + internal BsonClassMap(IBsonSerializationDomain serializationDomain, Action> classMapInitializer) + : base(serializationDomain, typeof(TClass)) { classMapInitializer(this); } @@ -1503,7 +1356,12 @@ public BsonClassMap(Action> classMapInitializer) /// /// The base class map. public BsonClassMap(BsonClassMap baseClassMap) - : base(typeof(TClass), baseClassMap) + : this(BsonSerializationDomain.Default, baseClassMap) + { + } + + internal BsonClassMap(IBsonSerializationDomain serializationDomain, BsonClassMap baseClassMap) + : base(serializationDomain, typeof(TClass), baseClassMap) { } diff --git a/src/MongoDB.Bson/Serialization/BsonClassMapRegistry.cs b/src/MongoDB.Bson/Serialization/BsonClassMapRegistry.cs new file mode 100644 index 00000000000..bbc0930efdf --- /dev/null +++ b/src/MongoDB.Bson/Serialization/BsonClassMapRegistry.cs @@ -0,0 +1,286 @@ +/* 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 System.Reflection; + +namespace MongoDB.Bson.Serialization; + +internal class BsonClassMapRegistry : IBsonClassMapRegistry, IHasSerializationDomain +{ + // private fields + private readonly Dictionary _classMaps = new(); + private readonly IBsonSerializationDomain _serializationDomain; + + public BsonClassMapRegistry(BsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + } + + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + + /// + /// Gets all registered class maps. + /// + /// All registered class maps. + public IEnumerable GetRegisteredClassMaps() + { + _serializationDomain.ConfigLock.EnterReadLock(); + try + { + return _classMaps.Values.ToList(); // return a copy for thread safety + } + finally + { + _serializationDomain.ConfigLock.ExitReadLock(); + } + } + + /// + /// Checks whether a class map is registered for a type. + /// + /// The type to check. + /// True if there is a class map registered for the type. + public bool IsClassMapRegistered(Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + _serializationDomain.ConfigLock.EnterReadLock(); + try + { + return _classMaps.ContainsKey(type); + } + finally + { + _serializationDomain.ConfigLock.ExitReadLock(); + } + } + + /// + /// Looks up a class map (will AutoMap the class if no class map is registered). + /// + /// The class type. + /// The class map. + public BsonClassMap LookupClassMap(Type classType) + { + if (classType == null) + { + throw new ArgumentNullException("classType"); + } + + _serializationDomain.ConfigLock.EnterReadLock(); + try + { + if (_classMaps.TryGetValue(classType, out var classMap)) + { + if (classMap.IsFrozen) + { + return classMap; + } + } + } + finally + { + _serializationDomain.ConfigLock.ExitReadLock(); + } + + // automatically create a new classMap for classType and register it (unless another thread does first) + // do the work of speculatively creating a new class map outside of holding any lock + var classMapDefinition = typeof(BsonClassMap<>); + var classMapType = classMapDefinition.MakeGenericType(classType); + var bindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var newClassMap = (BsonClassMap)Activator.CreateInstance(classMapType, bindingAttr, binder: null, args: [_serializationDomain], culture: null); + newClassMap.AutoMap(); + + _serializationDomain.ConfigLock.EnterWriteLock(); + try + { + if (!_classMaps.TryGetValue(classType, out var classMap)) + { + RegisterClassMap(newClassMap); + classMap = newClassMap; + } + + return classMap.Freeze(_serializationDomain); + } + finally + { + _serializationDomain.ConfigLock.ExitWriteLock(); + } + } + + /// + /// Creates and registers a class map. + /// + /// The class. + /// The class map. + public BsonClassMap RegisterClassMap() + { + return RegisterClassMap(cm => { cm.AutoMap(); }); + } + + /// + /// Creates and registers a class map. + /// + /// The class. + /// The class map initializer. + /// The class map. + public BsonClassMap RegisterClassMap(Action> classMapInitializer) + { + var classMap = new BsonClassMap(_serializationDomain, classMapInitializer); + RegisterClassMap(classMap); + return classMap; + } + + /// + /// Registers a class map. + /// + /// The class map. + public void RegisterClassMap(BsonClassMap classMap) + { + if (classMap == null) + { + throw new ArgumentNullException("classMap"); + } + + if (classMap.SerializationDomain != _serializationDomain) + { + throw new ArgumentException($"Expected class map to be for serialization domain {_serializationDomain.Name}, but was for serialization domain {classMap.SerializationDomain.Name}."); + } + + _serializationDomain.ConfigLock.EnterWriteLock(); + try + { + // note: class maps can NOT be replaced (because derived classes refer to existing instance) + _classMaps.Add(classMap.ClassType, classMap); + _serializationDomain.RegisterDiscriminator(classMap.ClassType, classMap.Discriminator); + } + finally + { + _serializationDomain.ConfigLock.ExitWriteLock(); + } + } + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// True if this call registered the class map, false if the class map was already registered. + public bool TryRegisterClassMap() + { + return TryRegisterClassMap(() => ClassMapFactory()); + + BsonClassMap ClassMapFactory() + { + var classMap = new BsonClassMap(_serializationDomain); + classMap.AutoMap(); + return classMap; + } + } + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// The class map. + /// True if this call registered the class map, false if the class map was already registered. + public bool TryRegisterClassMap(BsonClassMap classMap) + { + if (classMap == null) + { + throw new ArgumentNullException(nameof(classMap)); + } + + return TryRegisterClassMap(ClassMapFactory); + + BsonClassMap ClassMapFactory() + { + return classMap; + } + } + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// The class map initializer (only called if the class map is not already registered). + /// True if this call registered the class map, false if the class map was already registered. + public bool TryRegisterClassMap(Action> classMapInitializer) + { + if (classMapInitializer == null) + { + throw new ArgumentNullException(nameof(classMapInitializer)); + } + + return TryRegisterClassMap(ClassMapFactory); + + BsonClassMap ClassMapFactory() + { + return new BsonClassMap(classMapInitializer); + } + } + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// The class map factory (only called if the class map is not already registered). + /// True if this call registered the class map, false if the class map was already registered. + public bool TryRegisterClassMap(Func> classMapFactory) + { + if (classMapFactory == null) + { + throw new ArgumentNullException(nameof(classMapFactory)); + } + + _serializationDomain.ConfigLock.EnterReadLock(); + try + { + if (_classMaps.ContainsKey(typeof(TClass))) + { + return false; + } + } + finally + { + _serializationDomain.ConfigLock.ExitReadLock(); + } + + _serializationDomain.ConfigLock.EnterWriteLock(); + try + { + if (_classMaps.ContainsKey(typeof(TClass))) + { + return false; + } + else + { + // create a classMap for TClass and register it + var classMap = classMapFactory(); + RegisterClassMap(classMap); + return true; + } + } + finally + { + _serializationDomain.ConfigLock.ExitWriteLock(); + } + } +} diff --git a/src/MongoDB.Bson/Serialization/BsonClassMapSerializationProvider.cs b/src/MongoDB.Bson/Serialization/BsonClassMapSerializationProvider.cs index ac088360c2d..c0bc1952598 100644 --- a/src/MongoDB.Bson/Serialization/BsonClassMapSerializationProvider.cs +++ b/src/MongoDB.Bson/Serialization/BsonClassMapSerializationProvider.cs @@ -21,8 +21,17 @@ namespace MongoDB.Bson.Serialization /// /// Represents the class map serialization provider. /// - internal class BsonClassMapSerializationProvider : BsonSerializationProviderBase + internal class BsonClassMapSerializationProvider : BsonSerializationProviderBase, IHasSerializationDomain { + private readonly IBsonSerializationDomain _serializationDomain; + + public BsonClassMapSerializationProvider(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + } + + public IBsonSerializationDomain SerializationDomain { get; } + /// public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry) { @@ -41,10 +50,11 @@ public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry !typeof(Array).GetTypeInfo().IsAssignableFrom(type) && !typeof(Enum).GetTypeInfo().IsAssignableFrom(type)) { - var classMap = BsonClassMap.LookupClassMap(type); + var classMap = _serializationDomain.ClassMapRegistry.LookupClassMap(type); var classMapSerializerDefinition = typeof(BsonClassMapSerializer<>); var classMapSerializerType = classMapSerializerDefinition.MakeGenericType(type); - return (IBsonSerializer)Activator.CreateInstance(classMapSerializerType, classMap); + var bindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + return (IBsonSerializer)Activator.CreateInstance(classMapSerializerType, bindingAttr, binder: null, args: [_serializationDomain, classMap], culture: null); } return null; diff --git a/src/MongoDB.Bson/Serialization/BsonMemberMap.cs b/src/MongoDB.Bson/Serialization/BsonMemberMap.cs index b10971d5249..3fcd43a61b2 100644 --- a/src/MongoDB.Bson/Serialization/BsonMemberMap.cs +++ b/src/MongoDB.Bson/Serialization/BsonMemberMap.cs @@ -23,13 +23,14 @@ namespace MongoDB.Bson.Serialization /// /// Represents the mapping between a field or property and a BSON element. /// - public class BsonMemberMap + public class BsonMemberMap : IHasSerializationDomain { // private fields private readonly BsonClassMap _classMap; private readonly MemberInfo _memberInfo; private readonly Type _memberType; private readonly bool _memberTypeIsBsonValue; + private readonly IBsonSerializationDomain _serializationDomain; private string _elementName; private bool _frozen; // once a class map has been frozen no further changes are allowed @@ -53,10 +54,16 @@ public class BsonMemberMap /// The class map this member map belongs to. /// The member info. public BsonMemberMap(BsonClassMap classMap, MemberInfo memberInfo) + : this(classMap.SerializationDomain, classMap, memberInfo) { + } + + internal BsonMemberMap(IBsonSerializationDomain serializationDomain, BsonClassMap classMap, MemberInfo memberInfo) + { + _serializationDomain = serializationDomain; _classMap = classMap; _memberInfo = memberInfo; - _memberType = BsonClassMap.GetMemberInfoType(memberInfo); + _memberType = GetMemberInfoType(memberInfo); _memberTypeIsBsonValue = typeof(BsonValue).GetTypeInfo().IsAssignableFrom(_memberType); Reset(); @@ -134,6 +141,8 @@ public Func Getter } } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + /// /// Gets the setter function. /// @@ -243,6 +252,8 @@ public bool IsReadOnly } } + internal IBsonSerializationDomain SerializationDomain => _serializationDomain; + // public methods /// /// Applies the default value to the member of an object. @@ -301,7 +312,7 @@ public IBsonSerializer GetSerializer() // return special serializer for BsonValue members that handles the _csharpnull representation if (_memberTypeIsBsonValue) { - var wrappedSerializer = BsonSerializer.LookupSerializer(_memberType); + var wrappedSerializer = _serializationDomain.LookupSerializer(_memberType); var isBsonArraySerializer = wrappedSerializer is IBsonArraySerializer; var isBsonDocumentSerializer = wrappedSerializer is IBsonDocumentSerializer; @@ -329,7 +340,7 @@ public IBsonSerializer GetSerializer() } else { - _serializer = BsonSerializer.LookupSerializer(_memberType); + _serializer = _serializationDomain.LookupSerializer(_memberType); } } return _serializer; @@ -650,6 +661,26 @@ private Func GetGetter() return lambdaExpression.Compile(); } + private Type GetMemberInfoType(MemberInfo memberInfo) + { + if (memberInfo == null) + { + throw new ArgumentNullException("memberInfo"); + } + + if (memberInfo is FieldInfo) + { + return ((FieldInfo)memberInfo).FieldType; + } + else if (memberInfo is PropertyInfo) + { + return ((PropertyInfo)memberInfo).PropertyType; + } + + throw new NotSupportedException("Only field and properties are supported at this time."); + } + + private Action GetPropertySetter() { var propertyInfo = (PropertyInfo)_memberInfo; diff --git a/src/MongoDB.Bson/Serialization/BsonSerializationContext.cs b/src/MongoDB.Bson/Serialization/BsonSerializationContext.cs index a14a62322a8..b30afd03bb5 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializationContext.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializationContext.cs @@ -121,9 +121,10 @@ internal Builder(BsonSerializationContext other, IBsonWriter writer) } else { + var bsonDefaults = BsonSerializationDomain.Default.BsonDefaults; _isDynamicType = t => - (BsonDefaults.DynamicArraySerializer != null && t == BsonDefaults.DynamicArraySerializer.ValueType) || - (BsonDefaults.DynamicDocumentSerializer != null && t == BsonDefaults.DynamicDocumentSerializer.ValueType); + (bsonDefaults.DynamicArraySerializer != null && t == bsonDefaults.DynamicArraySerializer.ValueType) || + (bsonDefaults.DynamicDocumentSerializer != null && t == bsonDefaults.DynamicDocumentSerializer.ValueType); } } diff --git a/src/MongoDB.Bson/Serialization/BsonSerializationDomain.cs b/src/MongoDB.Bson/Serialization/BsonSerializationDomain.cs new file mode 100644 index 00000000000..e54abf4f5ba --- /dev/null +++ b/src/MongoDB.Bson/Serialization/BsonSerializationDomain.cs @@ -0,0 +1,907 @@ +/* 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.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.IdGenerators; + +namespace MongoDB.Bson.Serialization +{ + /// + /// A class that represents the BSON serialization functionality. + /// + internal class BsonSerializationDomain : IBsonSerializationDomain, IDisposable + { + #region static + private readonly static IBsonSerializationDomain __default; + + static BsonSerializationDomain() + { + var defaultSerializationDomain = new BsonSerializationDomain("Default"); + defaultSerializationDomain.Initialize(); + defaultSerializationDomain.ConfigureDefaults(); + __default = defaultSerializationDomain; + } + + /// + /// Gets the default serialization domain. + /// + public static IBsonSerializationDomain Default => __default; + + internal static IBsonSerializationDomain Create(string name) + { + var serializationDomain = new BsonSerializationDomain(name); + serializationDomain.Initialize(); + return serializationDomain; + } + + internal static IBsonSerializationDomain CreateWithDefaultConfiguration(string name) + { + var serializationDomain = new BsonSerializationDomain(name); + serializationDomain.Initialize(); + serializationDomain.ConfigureDefaults(); + return serializationDomain; + } + #endregion + + // private fields + private IBsonDefaults _bsonDefaults; + private ReaderWriterLockSlim _configLock = new(LockRecursionPolicy.SupportsRecursion); + private IBsonClassMapRegistry _classMapRegistry; + private IConventionRegistry _conventionRegistry; + private Dictionary _idGenerators = new(); + private Dictionary _discriminatorConventions = new(); + private Dictionary> _discriminators = new(); + private HashSet _discriminatedTypes = new(); + private BsonSerializerRegistry _serializerRegistry; + private TypeMappingSerializationProvider _typeMappingSerializationProvider; + // ConcurrentDictionary is being used as a concurrent set of Type. The values will always be null. + private ConcurrentDictionary _typesWithRegisteredKnownTypes = new(); + + private bool _useNullIdChecker; + private bool _useZeroIdChecker; + + // constructor + public BsonSerializationDomain(string name = null) + { + Name = name ?? "CUSTOM"; + } + + public string Name { get; } + + // public properties + /// + /// Gets the serializer registry. + /// + public IBsonSerializerRegistry SerializerRegistry + { + get { return _serializerRegistry; } + } + + /// + /// Gets or sets whether to use the NullIdChecker on reference Id types that don't have an IdGenerator registered. + /// + public bool UseNullIdChecker + { + get { return _useNullIdChecker; } + set { _useNullIdChecker = value; } + } + + public bool UseNullIdCheckerEnabled => UseNullIdChecker; + + /// + /// Gets or sets whether to use the ZeroIdChecker on value Id types that don't have an IdGenerator registered. + /// + public bool UseZeroIdChecker + { + get { return _useZeroIdChecker; } + set { _useZeroIdChecker = value; } + } + + public bool UseZeroIdCheckerEnabled => UseZeroIdChecker; + + // internal properties + public ReaderWriterLockSlim ConfigLock + { + get { return _configLock; } + } + + /// + /// Deserializes an object from a BsonDocument. + /// + /// The nominal type of the object. + /// The BsonDocument. + /// The configurator. + /// A deserialized value. + public TNominalType Deserialize(BsonDocument document, + Action configurator = null) + { + using (var bsonReader = new BsonDocumentReader(document)) + { + return Deserialize(bsonReader, configurator); + } + } + + /// + /// Deserializes a value. + /// + /// The nominal type of the object. + /// The BsonReader. + /// The configurator. + /// A deserialized value. + public TNominalType Deserialize(IBsonReader bsonReader, + Action configurator = null) + { + var serializer = LookupSerializer(); + var context = BsonDeserializationContext.CreateRoot(bsonReader, configurator); + return serializer.Deserialize(context); + } + + /// + /// Deserializes an object from a BSON byte array. + /// + /// The nominal type of the object. + /// The BSON byte array. + /// The configurator. + /// A deserialized value. + public TNominalType Deserialize(byte[] bytes, + Action configurator = null) + { + using (var buffer = new ByteArrayBuffer(bytes, isReadOnly: true)) + using (var stream = new ByteBufferStream(buffer)) + { + return Deserialize(stream, configurator); + } + } + + /// + /// Deserializes an object from a BSON Stream. + /// + /// The nominal type of the object. + /// The BSON Stream. + /// The configurator. + /// A deserialized value. + public TNominalType Deserialize(Stream stream, + Action configurator = null) + { + using (var bsonReader = new BsonBinaryReader(stream)) + { + return Deserialize(bsonReader, configurator); + } + } + + /// + /// Deserializes an object from a JSON string. + /// + /// The nominal type of the object. + /// The JSON string. + /// The configurator. + /// A deserialized value. + public TNominalType Deserialize(string json, + Action configurator = null) + { + using (var bsonReader = new JsonReader(json)) + { + return Deserialize(bsonReader, configurator); + } + } + + /// + /// Deserializes an object from a JSON TextReader. + /// + /// The nominal type of the object. + /// The JSON TextReader. + /// The configurator. + /// A deserialized value. + public TNominalType Deserialize(TextReader textReader, + Action configurator = null) + { + using (var bsonReader = new JsonReader(textReader)) + { + return Deserialize(bsonReader, configurator); + } + } + + /// + /// Deserializes an object from a BsonDocument. + /// + /// The BsonDocument. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + public object Deserialize(BsonDocument document, Type nominalType, + Action configurator = null) + { + using (var bsonReader = new BsonDocumentReader(document)) + { + return Deserialize(bsonReader, nominalType, configurator); + } + } + + /// + /// Deserializes a value. + /// + /// The BsonReader. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + public object Deserialize(IBsonReader bsonReader, Type nominalType, + Action configurator = null) + { + var serializer = LookupSerializer(nominalType); + var context = BsonDeserializationContext.CreateRoot(bsonReader, configurator); + return serializer.Deserialize(context); + } + + /// + /// Deserializes an object from a BSON byte array. + /// + /// The BSON byte array. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + public object Deserialize(byte[] bytes, Type nominalType, + Action configurator = null) + { + using (var buffer = new ByteArrayBuffer(bytes, isReadOnly: true)) + using (var stream = new ByteBufferStream(buffer)) + { + return Deserialize(stream, nominalType, configurator); + } + } + + /// + /// Deserializes an object from a BSON Stream. + /// + /// The BSON Stream. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + public object Deserialize(Stream stream, Type nominalType, + Action configurator = null) + { + using (var bsonReader = new BsonBinaryReader(stream)) + { + return Deserialize(bsonReader, nominalType, configurator); + } + } + + /// + /// Deserializes an object from a JSON string. + /// + /// The JSON string. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + public object Deserialize(string json, Type nominalType, + Action configurator = null) + { + using (var bsonReader = new JsonReader(json)) + { + return Deserialize(bsonReader, nominalType, configurator); + } + } + + /// + /// Deserializes an object from a JSON TextReader. + /// + /// The JSON TextReader. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + public object Deserialize(TextReader textReader, Type nominalType, + Action configurator = null) + { + using (var bsonReader = new JsonReader(textReader)) + { + return Deserialize(bsonReader, nominalType, configurator); + } + } + + public BsonValue[] GetDiscriminatorsForTypeAndSubTypes(Type type) + { + // note: EnsureKnownTypesAreRegistered handles its own locking so call from outside any lock + EnsureKnownTypesAreRegistered(type); + + var discriminators = new List(); + + _configLock.EnterReadLock(); + try + { + foreach (var entry in _discriminators) + { + var discriminator = entry.Key; + var actualTypes = entry.Value; + + var matchingType = actualTypes.SingleOrDefault(t => t == type || t.IsSubclassOf(type)); + if (matchingType != null) + { + discriminators.Add(discriminator); + } + } + } + finally + { + _configLock.ExitReadLock(); + } + + return discriminators.OrderBy(x => x).ToArray(); + } + + public IDiscriminatorConvention GetOrRegisterDiscriminatorConvention(Type type, + IDiscriminatorConvention discriminatorConvention) + { + _configLock.EnterReadLock(); + try + { + if (_discriminatorConventions.TryGetValue(type, out var registeredDiscriminatorConvention)) + { + return registeredDiscriminatorConvention; + } + } + finally + { + _configLock.ExitReadLock(); + } + + _configLock.EnterWriteLock(); + try + { + if (_discriminatorConventions.TryGetValue(type, out var registeredDiscrimantorConvention)) + { + return registeredDiscrimantorConvention; + } + + RegisterDiscriminatorConvention(type, discriminatorConvention); + return discriminatorConvention; + } + finally + { + _configLock.ExitWriteLock(); + } + } + + public bool IsDiscriminatorConventionRegisteredAtThisLevel(Type type) + { + _configLock.EnterReadLock(); + try + { + return _discriminatorConventions.ContainsKey(type); + } + finally + { + _configLock.ExitReadLock(); + } + } + + /// + /// Returns whether the given type has any discriminators registered for any of its subclasses. + /// + /// A Type. + /// True if the type is discriminated. + public bool IsTypeDiscriminated(Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsInterface || _discriminatedTypes.Contains(type); + } + + /// + /// Looks up the actual type of an object to be deserialized. + /// + /// The nominal type of the object. + /// The discriminator. + /// The actual type of the object. + public Type LookupActualType(Type nominalType, BsonValue discriminator) + { + if (discriminator == null) + { + return nominalType; + } + + // note: EnsureKnownTypesAreRegistered handles its own locking so call from outside any lock + EnsureKnownTypesAreRegistered(nominalType); + + _configLock.EnterReadLock(); + try + { + Type actualType = null; + + HashSet hashSet; + var nominalTypeInfo = nominalType.GetTypeInfo(); + if (_discriminators.TryGetValue(discriminator, out hashSet)) + { + foreach (var type in hashSet) + { + if (nominalTypeInfo.IsAssignableFrom(type)) + { + if (actualType == null) + { + actualType = type; + } + else + { + string message = string.Format("Ambiguous discriminator '{0}'.", discriminator); + throw new BsonSerializationException(message); + } + } + } + + // no need for additional checks, we found the right type + if (actualType != null) + { + return actualType; + } + } + + if (discriminator.IsString) + { + actualType = TypeNameDiscriminator.GetActualType(discriminator.AsString); // see if it's a Type name + } + + if (actualType == null) + { + string message = string.Format("Unknown discriminator value '{0}'.", discriminator); + throw new BsonSerializationException(message); + } + + if (!nominalTypeInfo.IsAssignableFrom(actualType)) + { + string message = string.Format( + "Actual type {0} is not assignable to expected type {1}.", + actualType.FullName, nominalType.FullName); + throw new BsonSerializationException(message); + } + + return actualType; + } + finally + { + _configLock.ExitReadLock(); + } + } + + /// + /// Looks up the discriminator convention for a type. + /// + /// The type. + /// A discriminator convention. + public IDiscriminatorConvention LookupDiscriminatorConvention(Type type) + { + _configLock.EnterReadLock(); + try + { + IDiscriminatorConvention convention; + if (_discriminatorConventions.TryGetValue(type, out convention)) + { + return convention; + } + } + finally + { + _configLock.ExitReadLock(); + } + + _configLock.EnterWriteLock(); + try + { + IDiscriminatorConvention convention; + if (!_discriminatorConventions.TryGetValue(type, out convention)) + { + var typeInfo = type.GetTypeInfo(); + if (type == typeof(object)) + { + // if there is no convention registered for object register the default one + convention = new ObjectDiscriminatorConvention(this, "_t"); + RegisterDiscriminatorConvention(typeof(object), convention); + } + else if (typeInfo.IsInterface) + { + // TODO: should convention for interfaces be inherited from parent interfaces? + convention = LookupDiscriminatorConvention(typeof(object)); + RegisterDiscriminatorConvention(type, convention); + } + else + { + // inherit the discriminator convention from the closest parent (that isn't object) that has one + // otherwise default to the standard scalar convention + Type parentType = typeInfo.BaseType; + while (true) + { + if (parentType == typeof(object)) + { + convention = new ScalarDiscriminatorConvention(this, "_t"); + break; + } + + if (_discriminatorConventions.TryGetValue(parentType, out convention)) + { + break; + } + + parentType = parentType.GetTypeInfo().BaseType; + } + + // register this convention for all types between this and the parent type where we found the convention + var unregisteredType = type; + while (unregisteredType != parentType) + { + RegisterDiscriminatorConvention(unregisteredType, convention); + unregisteredType = unregisteredType.GetTypeInfo().BaseType; + } + } + } + + return convention; + } + finally + { + _configLock.ExitWriteLock(); + } + } + + /// + /// Looks up an IdGenerator. + /// + /// The Id type. + /// An IdGenerator for the Id type. + public IIdGenerator LookupIdGenerator(Type type) + { + _configLock.EnterReadLock(); + try + { + IIdGenerator idGenerator; + if (_idGenerators.TryGetValue(type, out idGenerator)) + { + return idGenerator; + } + } + finally + { + _configLock.ExitReadLock(); + } + + _configLock.EnterWriteLock(); + try + { + IIdGenerator idGenerator; + if (!_idGenerators.TryGetValue(type, out idGenerator)) + { + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsValueType && _useZeroIdChecker) + { + var iEquatableDefinition = typeof(IEquatable<>); + var iEquatableType = iEquatableDefinition.MakeGenericType(type); + if (iEquatableType.GetTypeInfo().IsAssignableFrom(type)) + { + var zeroIdCheckerDefinition = typeof(ZeroIdChecker<>); + var zeroIdCheckerType = zeroIdCheckerDefinition.MakeGenericType(type); + idGenerator = (IIdGenerator)Activator.CreateInstance(zeroIdCheckerType); + } + } + else if (_useNullIdChecker) + { + idGenerator = NullIdChecker.Instance; + } + else + { + idGenerator = null; + } + + _idGenerators[type] = idGenerator; // remember it even if it's null + } + + return idGenerator; + } + finally + { + _configLock.ExitWriteLock(); + } + } + + /// + /// Looks up a serializer for a Type. + /// + /// The type. + /// A serializer for type T. + public IBsonSerializer LookupSerializer() + { + return (IBsonSerializer)LookupSerializer(typeof(T)); + } + + /// + /// Looks up a serializer for a Type. + /// + /// The Type. + /// A serializer for the Type. + public IBsonSerializer LookupSerializer(Type type) + { + return _serializerRegistry.GetSerializer(type); + } + + /// + /// Registers the discriminator for a type. + /// + /// The type. + /// The discriminator. + public void RegisterDiscriminator(Type type, BsonValue discriminator) + { + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsInterface) + { + var message = string.Format("Discriminators can only be registered for classes, not for interface {0}.", + type.FullName); + throw new BsonSerializationException(message); + } + + _configLock.EnterWriteLock(); + try + { + HashSet hashSet; + if (!_discriminators.TryGetValue(discriminator, out hashSet)) + { + hashSet = new HashSet(); + _discriminators.Add(discriminator, hashSet); + } + + if (!hashSet.Contains(type)) + { + hashSet.Add(type); + + // mark all base types as discriminated (so we know that it's worth reading a discriminator) + for (var baseType = typeInfo.BaseType; baseType != null; baseType = baseType.GetTypeInfo().BaseType) + { + _discriminatedTypes.Add(baseType); + } + } + } + finally + { + _configLock.ExitWriteLock(); + } + } + + /// + /// Registers the discriminator convention for a type. + /// + /// Type type. + /// The discriminator convention. + public void RegisterDiscriminatorConvention(Type type, IDiscriminatorConvention convention) + { + _configLock.EnterWriteLock(); + try + { + if (!_discriminatorConventions.ContainsKey(type)) + { + if (convention is IHasSerializationDomain hasSerializationDomain && hasSerializationDomain.SerializationDomain != this) + { + throw new ArgumentException($"Expected discriminator convention to be for serialization domain {Name} but was for serialization domain {hasSerializationDomain.SerializationDomain.Name}."); + } + + _discriminatorConventions.Add(type, convention); + } + else + { + var message = string.Format("There is already a discriminator convention registered for type {0}.", + type.FullName); + throw new BsonSerializationException(message); + } + } + finally + { + _configLock.ExitWriteLock(); + } + } + + /// + /// Registers a generic serializer definition for a generic type. + /// + /// The generic type. + /// The generic serializer definition. + public void RegisterGenericSerializerDefinition( + Type genericTypeDefinition, + Type genericSerializerDefinition) + { + _typeMappingSerializationProvider.RegisterMapping(genericTypeDefinition, genericSerializerDefinition); + } + + /// + /// Registers an IdGenerator for an Id Type. + /// + /// The Id Type. + /// The IdGenerator for the Id Type. + public void RegisterIdGenerator(Type type, IIdGenerator idGenerator) + { + _configLock.EnterWriteLock(); + try + { + _idGenerators[type] = idGenerator; + } + finally + { + _configLock.ExitWriteLock(); + } + } + + /// + /// Registers a serialization provider. + /// + /// The serialization provider. + public void RegisterSerializationProvider(IBsonSerializationProvider provider) + { + _serializerRegistry.RegisterSerializationProvider(provider); + } + + /// + /// Registers a serializer for a type. + /// + /// The type. + /// The serializer. + public void RegisterSerializer(IBsonSerializer serializer) + { + RegisterSerializer(typeof(T), serializer); + } + + /// + /// Registers a serializer for a type. + /// + /// The type. + /// The serializer. + public void RegisterSerializer(Type type, IBsonSerializer serializer) + { + _serializerRegistry.RegisterSerializer(type, serializer); + } + + /// + /// Serializes a value. + /// + /// The nominal type of the object. + /// The BsonWriter. + /// The object. + /// The serialization context configurator. + /// The serialization args. + public void Serialize( + IBsonWriter bsonWriter, + TNominalType value, + Action configurator = null, + BsonSerializationArgs args = default(BsonSerializationArgs)) + { + args.SetOrValidateNominalType(typeof(TNominalType), ""); + var serializer = LookupSerializer(); + var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator); + serializer.Serialize(context, args, value); + } + + /// + /// Serializes a value. + /// + /// The BsonWriter. + /// The nominal type of the object. + /// The object. + /// The serialization context configurator. + /// The serialization args. + public void Serialize( + IBsonWriter bsonWriter, + Type nominalType, + object value, + Action configurator = null, + BsonSerializationArgs args = default(BsonSerializationArgs)) + { + args.SetOrValidateNominalType(nominalType, "nominalType"); + var serializer = LookupSerializer(nominalType); + var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator); + serializer.Serialize(context, args, value); + } + + public IBsonClassMapRegistry ClassMapRegistry => _classMapRegistry; + + public IConventionRegistry ConventionRegistry => _conventionRegistry; + public IBsonDefaults BsonDefaults => _bsonDefaults; + + /// + /// Tries to register a serializer for a type. + /// + /// The serializer. + /// The type. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + public bool TryRegisterSerializer(Type type, IBsonSerializer serializer) + { + return _serializerRegistry.TryRegisterSerializer(type, serializer); + } + + /// + /// Tries to register a serializer for a type. + /// + /// The type. + /// The serializer. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + public bool TryRegisterSerializer(IBsonSerializer serializer) + { + return TryRegisterSerializer(typeof(T), serializer); + } + + // internal methods + public void EnsureKnownTypesAreRegistered(Type nominalType) + { + if (_typesWithRegisteredKnownTypes.ContainsKey(nominalType)) + { + return; + } + + _configLock.EnterWriteLock(); + try + { + if (!_typesWithRegisteredKnownTypes.ContainsKey(nominalType)) + { + // only call LookupClassMap for classes with a BsonKnownTypesAttribute + var hasKnownTypesAttribute = nominalType.GetTypeInfo() + .GetCustomAttributes(typeof(BsonKnownTypesAttribute), inherit: false).Any(); + if (hasKnownTypesAttribute) + { + // try and force a scan of the known types + LookupSerializer(nominalType); + } + + // NOTE: The nominalType MUST be added to __typesWithRegisteredKnownTypes after all registration + // work is done to ensure that other threads don't access a partially registered nominalType + // when performing the initial check above outside the __config lock. + _typesWithRegisteredKnownTypes[nominalType] = null; + } + } + finally + { + _configLock.ExitWriteLock(); + } + } + + public void Dispose() + { + _configLock.Dispose(); + } + + // private methods + internal void Initialize() + { + _serializerRegistry = new BsonSerializerRegistry(this); + _typeMappingSerializationProvider = new TypeMappingSerializationProvider(); + _classMapRegistry = new BsonClassMapRegistry(this); + _conventionRegistry = new ConventionRegistryInstance(this); + _bsonDefaults = new BsonDefaultsRegistry(this); + } + + internal void ConfigureDefaults() + { + // order matters. It's in reverse order of how they'll get consumed + _serializerRegistry.RegisterSerializationProvider(new BsonClassMapSerializationProvider(this)); + _serializerRegistry.RegisterSerializationProvider(new DiscriminatedInterfaceSerializationProvider()); + _serializerRegistry.RegisterSerializationProvider(new CollectionsSerializationProvider()); + _serializerRegistry.RegisterSerializationProvider(new PrimitiveSerializationProvider()); + _serializerRegistry.RegisterSerializationProvider(new AttributedSerializationProvider()); + _serializerRegistry.RegisterSerializationProvider(_typeMappingSerializationProvider); + _serializerRegistry.RegisterSerializationProvider(new BsonObjectModelSerializationProvider()); + + RegisterIdGenerator(typeof(BsonObjectId), BsonObjectIdGenerator.Instance); + RegisterIdGenerator(typeof(Guid), GuidGenerator.Instance); + RegisterIdGenerator(typeof(ObjectId), ObjectIdGenerator.Instance); + } + } +} diff --git a/src/MongoDB.Bson/Serialization/BsonSerializationProviderBase.cs b/src/MongoDB.Bson/Serialization/BsonSerializationProviderBase.cs index b2ffb587183..76bd1d76b3b 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializationProviderBase.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializationProviderBase.cs @@ -26,7 +26,7 @@ public abstract class BsonSerializationProviderBase : IRegistryAwareBsonSerializ /// public virtual IBsonSerializer GetSerializer(Type type) { - return GetSerializer(type, BsonSerializer.SerializerRegistry); + return GetSerializer(type, BsonSerializationDomain.Default.SerializerRegistry); } /// @@ -40,7 +40,7 @@ public virtual IBsonSerializer GetSerializer(Type type) /// A serializer. protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDefinition, params Type[] typeArguments) { - return CreateGenericSerializer(serializerTypeDefinition, typeArguments, BsonSerializer.SerializerRegistry); + return CreateGenericSerializer(serializerTypeDefinition, typeArguments, BsonSerializationDomain.Default.SerializerRegistry); } /// @@ -65,7 +65,7 @@ protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDef /// A serializer. protected virtual IBsonSerializer CreateSerializer(Type serializerType) { - return CreateSerializer(serializerType, BsonSerializer.SerializerRegistry); + return CreateSerializer(serializerType, BsonSerializationDomain.Default.SerializerRegistry); } /// diff --git a/src/MongoDB.Bson/Serialization/BsonSerializer.cs b/src/MongoDB.Bson/Serialization/BsonSerializer.cs index df21c6f6bb5..175c60ad495 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializer.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializer.cs @@ -14,18 +14,10 @@ */ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; using System.Threading; - -// don't add using statement for MongoDB.Bson.Serialization.Serializers to minimize dependencies on DefaultSerializer using MongoDB.Bson.IO; -using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Conventions; -using MongoDB.Bson.Serialization.IdGenerators; namespace MongoDB.Bson.Serialization { @@ -34,43 +26,21 @@ namespace MongoDB.Bson.Serialization /// public static class BsonSerializer { - // private static fields - private static ReaderWriterLockSlim __configLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private static Dictionary __idGenerators = new Dictionary(); - private static Dictionary __discriminatorConventions = new Dictionary(); - private static Dictionary> __discriminators = new Dictionary>(); - private static ConcurrentDictionary __discriminatedTypes = new (); - private static BsonSerializerRegistry __serializerRegistry; - private static TypeMappingSerializationProvider __typeMappingSerializationProvider; - // ConcurrentDictionary is being used as a concurrent set of Type. The values will always be null. - private static ConcurrentDictionary __typesWithRegisteredKnownTypes = new ConcurrentDictionary(); - - private static bool __useNullIdChecker = false; - private static bool __useZeroIdChecker = false; - - // static constructor - static BsonSerializer() - { - CreateSerializerRegistry(); - RegisterIdGenerators(); - } - // public static properties + internal static IBsonSerializationDomain DefaultSerializationDomain => BsonSerializationDomain.Default; + /// /// Gets the serializer registry. /// - public static IBsonSerializerRegistry SerializerRegistry - { - get { return __serializerRegistry; } - } + public static IBsonSerializerRegistry SerializerRegistry => BsonSerializationDomain.Default.SerializerRegistry; /// /// Gets or sets whether to use the NullIdChecker on reference Id types that don't have an IdGenerator registered. /// public static bool UseNullIdChecker { - get { return __useNullIdChecker; } - set { __useNullIdChecker = value; } + get => BsonSerializationDomain.Default.UseNullIdChecker; + set => BsonSerializationDomain.Default.UseNullIdChecker = value; } /// @@ -78,15 +48,12 @@ public static bool UseNullIdChecker /// public static bool UseZeroIdChecker { - get { return __useZeroIdChecker; } - set { __useZeroIdChecker = value; } + get => BsonSerializationDomain.Default.UseZeroIdChecker; + set => BsonSerializationDomain.Default.UseZeroIdChecker = value; } // internal static properties - internal static ReaderWriterLockSlim ConfigLock - { - get { return __configLock; } - } + internal static ReaderWriterLockSlim ConfigLock => BsonSerializationDomain.Default.ConfigLock; // public static methods /// @@ -97,12 +64,7 @@ internal static ReaderWriterLockSlim ConfigLock /// The configurator. /// A deserialized value. public static TNominalType Deserialize(BsonDocument document, Action configurator = null) - { - using (var bsonReader = new BsonDocumentReader(document)) - { - return Deserialize(bsonReader, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(document, configurator); /// /// Deserializes a value. @@ -112,11 +74,7 @@ public static TNominalType Deserialize(BsonDocument document, Acti /// The configurator. /// A deserialized value. public static TNominalType Deserialize(IBsonReader bsonReader, Action configurator = null) - { - var serializer = LookupSerializer(); - var context = BsonDeserializationContext.CreateRoot(bsonReader, configurator); - return serializer.Deserialize(context); - } + => BsonSerializationDomain.Default.Deserialize(bsonReader, configurator); /// /// Deserializes an object from a BSON byte array. @@ -126,13 +84,7 @@ public static TNominalType Deserialize(IBsonReader bsonReader, Act /// The configurator. /// A deserialized value. public static TNominalType Deserialize(byte[] bytes, Action configurator = null) - { - using (var buffer = new ByteArrayBuffer(bytes, isReadOnly: true)) - using (var stream = new ByteBufferStream(buffer)) - { - return Deserialize(stream, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(bytes, configurator); /// /// Deserializes an object from a BSON Stream. @@ -142,12 +94,7 @@ public static TNominalType Deserialize(byte[] bytes, ActionThe configurator. /// A deserialized value. public static TNominalType Deserialize(Stream stream, Action configurator = null) - { - using (var bsonReader = new BsonBinaryReader(stream)) - { - return Deserialize(bsonReader, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(stream, configurator); /// /// Deserializes an object from a JSON string. @@ -157,12 +104,7 @@ public static TNominalType Deserialize(Stream stream, ActionThe configurator. /// A deserialized value. public static TNominalType Deserialize(string json, Action configurator = null) - { - using (var bsonReader = new JsonReader(json)) - { - return Deserialize(bsonReader, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(json, configurator); /// /// Deserializes an object from a JSON TextReader. @@ -172,12 +114,7 @@ public static TNominalType Deserialize(string json, ActionThe configurator. /// A deserialized value. public static TNominalType Deserialize(TextReader textReader, Action configurator = null) - { - using (var bsonReader = new JsonReader(textReader)) - { - return Deserialize(bsonReader, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(textReader, configurator); /// /// Deserializes an object from a BsonDocument. @@ -187,12 +124,7 @@ public static TNominalType Deserialize(TextReader textReader, Acti /// The configurator. /// A deserialized value. public static object Deserialize(BsonDocument document, Type nominalType, Action configurator = null) - { - using (var bsonReader = new BsonDocumentReader(document)) - { - return Deserialize(bsonReader, nominalType, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(document, nominalType, configurator); /// /// Deserializes a value. @@ -202,11 +134,7 @@ public static object Deserialize(BsonDocument document, Type nominalType, Action /// The configurator. /// A deserialized value. public static object Deserialize(IBsonReader bsonReader, Type nominalType, Action configurator = null) - { - var serializer = LookupSerializer(nominalType); - var context = BsonDeserializationContext.CreateRoot(bsonReader, configurator); - return serializer.Deserialize(context); - } + => BsonSerializationDomain.Default.Deserialize(bsonReader, nominalType, configurator); /// /// Deserializes an object from a BSON byte array. @@ -216,13 +144,7 @@ public static object Deserialize(IBsonReader bsonReader, Type nominalType, Actio /// The configurator. /// A deserialized value. public static object Deserialize(byte[] bytes, Type nominalType, Action configurator = null) - { - using (var buffer = new ByteArrayBuffer(bytes, isReadOnly: true)) - using (var stream = new ByteBufferStream(buffer)) - { - return Deserialize(stream, nominalType, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(bytes, nominalType, configurator); /// /// Deserializes an object from a BSON Stream. @@ -232,12 +154,7 @@ public static object Deserialize(byte[] bytes, Type nominalType, ActionThe configurator. /// A deserialized value. public static object Deserialize(Stream stream, Type nominalType, Action configurator = null) - { - using (var bsonReader = new BsonBinaryReader(stream)) - { - return Deserialize(bsonReader, nominalType, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(stream, nominalType, configurator); /// /// Deserializes an object from a JSON string. @@ -247,12 +164,7 @@ public static object Deserialize(Stream stream, Type nominalType, ActionThe configurator. /// A deserialized value. public static object Deserialize(string json, Type nominalType, Action configurator = null) - { - using (var bsonReader = new JsonReader(json)) - { - return Deserialize(bsonReader, nominalType, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(json, nominalType, configurator); /// /// Deserializes an object from a JSON TextReader. @@ -262,57 +174,13 @@ public static object Deserialize(string json, Type nominalType, ActionThe configurator. /// A deserialized value. public static object Deserialize(TextReader textReader, Type nominalType, Action configurator = null) - { - using (var bsonReader = new JsonReader(textReader)) - { - return Deserialize(bsonReader, nominalType, configurator); - } - } + => BsonSerializationDomain.Default.Deserialize(textReader, nominalType, configurator); internal static IDiscriminatorConvention GetOrRegisterDiscriminatorConvention(Type type, IDiscriminatorConvention discriminatorConvention) - { - __configLock.EnterReadLock(); - try - { - if (__discriminatorConventions.TryGetValue(type, out var registeredDiscriminatorConvention)) - { - return registeredDiscriminatorConvention; - } - } - finally - { - __configLock.ExitReadLock(); - } - - __configLock.EnterWriteLock(); - try - { - if (__discriminatorConventions.TryGetValue(type, out var registeredDiscrimantorConvention)) - { - return registeredDiscrimantorConvention; - } - - RegisterDiscriminatorConvention(type, discriminatorConvention); - return discriminatorConvention; - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.GetOrRegisterDiscriminatorConvention(type, discriminatorConvention); internal static bool IsDiscriminatorConventionRegisteredAtThisLevel(Type type) - { - __configLock.EnterReadLock(); - try - { - return __discriminatorConventions.ContainsKey(type); - } - finally - { - __configLock.ExitReadLock(); - } - } + => BsonSerializationDomain.Default.IsDiscriminatorConventionRegisteredAtThisLevel(type); /// /// Returns whether the given type has any discriminators registered for any of its subclasses. @@ -320,9 +188,7 @@ internal static bool IsDiscriminatorConventionRegisteredAtThisLevel(Type type) /// A Type. /// True if the type is discriminated. public static bool IsTypeDiscriminated(Type type) - { - return type.IsInterface || __discriminatedTypes.ContainsKey(type); - } + => BsonSerializationDomain.Default.IsTypeDiscriminated(type); /// /// Looks up the actual type of an object to be deserialized. @@ -331,73 +197,7 @@ public static bool IsTypeDiscriminated(Type type) /// The discriminator. /// The actual type of the object. public static Type LookupActualType(Type nominalType, BsonValue discriminator) - { - if (discriminator == null) - { - return nominalType; - } - - // note: EnsureKnownTypesAreRegistered handles its own locking so call from outside any lock - EnsureKnownTypesAreRegistered(nominalType); - - __configLock.EnterReadLock(); - try - { - Type actualType = null; - - HashSet hashSet; - var nominalTypeInfo = nominalType.GetTypeInfo(); - if (__discriminators.TryGetValue(discriminator, out hashSet)) - { - foreach (var type in hashSet) - { - if (nominalTypeInfo.IsAssignableFrom(type)) - { - if (actualType == null) - { - actualType = type; - } - else - { - string message = string.Format("Ambiguous discriminator '{0}'.", discriminator); - throw new BsonSerializationException(message); - } - } - } - - // no need for additional checks, we found the right type - if (actualType != null) - { - return actualType; - } - } - - if (discriminator.IsString) - { - actualType = TypeNameDiscriminator.GetActualType(discriminator.AsString); // see if it's a Type name - } - - if (actualType == null) - { - string message = string.Format("Unknown discriminator value '{0}'.", discriminator); - throw new BsonSerializationException(message); - } - - if (!nominalTypeInfo.IsAssignableFrom(actualType)) - { - string message = string.Format( - "Actual type {0} is not assignable to expected type {1}.", - actualType.FullName, nominalType.FullName); - throw new BsonSerializationException(message); - } - - return actualType; - } - finally - { - __configLock.ExitReadLock(); - } - } + => BsonSerializationDomain.Default.LookupActualType(nominalType, discriminator); /// /// Looks up the discriminator convention for a type. @@ -405,76 +205,7 @@ public static Type LookupActualType(Type nominalType, BsonValue discriminator) /// The type. /// A discriminator convention. public static IDiscriminatorConvention LookupDiscriminatorConvention(Type type) - { - __configLock.EnterReadLock(); - try - { - IDiscriminatorConvention convention; - if (__discriminatorConventions.TryGetValue(type, out convention)) - { - return convention; - } - } - finally - { - __configLock.ExitReadLock(); - } - - __configLock.EnterWriteLock(); - try - { - IDiscriminatorConvention convention; - if (!__discriminatorConventions.TryGetValue(type, out convention)) - { - var typeInfo = type.GetTypeInfo(); - if (type == typeof(object)) - { - // if there is no convention registered for object register the default one - convention = new ObjectDiscriminatorConvention("_t"); - RegisterDiscriminatorConvention(typeof(object), convention); - } - else if (typeInfo.IsInterface) - { - // TODO: should convention for interfaces be inherited from parent interfaces? - convention = LookupDiscriminatorConvention(typeof(object)); - RegisterDiscriminatorConvention(type, convention); - } - else - { - // inherit the discriminator convention from the closest parent (that isn't object) that has one - // otherwise default to the standard scalar convention - Type parentType = typeInfo.BaseType; - while (true) - { - if (parentType == typeof(object)) - { - convention = StandardDiscriminatorConvention.Scalar; - break; - } - if (__discriminatorConventions.TryGetValue(parentType, out convention)) - { - break; - } - parentType = parentType.GetTypeInfo().BaseType; - } - - // register this convention for all types between this and the parent type where we found the convention - var unregisteredType = type; - while (unregisteredType != parentType) - { - RegisterDiscriminatorConvention(unregisteredType, convention); - unregisteredType = unregisteredType.GetTypeInfo().BaseType; - } - } - } - - return convention; - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.LookupDiscriminatorConvention(type); /// /// Looks up an IdGenerator. @@ -482,78 +213,21 @@ public static IDiscriminatorConvention LookupDiscriminatorConvention(Type type) /// The Id type. /// An IdGenerator for the Id type. public static IIdGenerator LookupIdGenerator(Type type) - { - __configLock.EnterReadLock(); - try - { - IIdGenerator idGenerator; - if (__idGenerators.TryGetValue(type, out idGenerator)) - { - return idGenerator; - } - } - finally - { - __configLock.ExitReadLock(); - } - - __configLock.EnterWriteLock(); - try - { - IIdGenerator idGenerator; - if (!__idGenerators.TryGetValue(type, out idGenerator)) - { - var typeInfo = type.GetTypeInfo(); - if (typeInfo.IsValueType && __useZeroIdChecker) - { - var iEquatableDefinition = typeof(IEquatable<>); - var iEquatableType = iEquatableDefinition.MakeGenericType(type); - if (iEquatableType.GetTypeInfo().IsAssignableFrom(type)) - { - var zeroIdCheckerDefinition = typeof(ZeroIdChecker<>); - var zeroIdCheckerType = zeroIdCheckerDefinition.MakeGenericType(type); - idGenerator = (IIdGenerator)Activator.CreateInstance(zeroIdCheckerType); - } - } - else if (__useNullIdChecker) - { - idGenerator = NullIdChecker.Instance; - } - else - { - idGenerator = null; - } - - __idGenerators[type] = idGenerator; // remember it even if it's null - } - - return idGenerator; - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.LookupIdGenerator(type); /// /// Looks up a serializer for a Type. /// /// The type. /// A serializer for type T. - public static IBsonSerializer LookupSerializer() - { - return (IBsonSerializer)LookupSerializer(typeof(T)); - } + public static IBsonSerializer LookupSerializer() => BsonSerializationDomain.Default.LookupSerializer(); /// /// Looks up a serializer for a Type. /// /// The Type. /// A serializer for the Type. - public static IBsonSerializer LookupSerializer(Type type) - { - return __serializerRegistry.GetSerializer(type); - } + public static IBsonSerializer LookupSerializer(Type type) => BsonSerializationDomain.Default.LookupSerializer(type); /// /// Registers the discriminator for a type. @@ -561,41 +235,7 @@ public static IBsonSerializer LookupSerializer(Type type) /// The type. /// The discriminator. public static void RegisterDiscriminator(Type type, BsonValue discriminator) - { - var typeInfo = type.GetTypeInfo(); - if (typeInfo.IsInterface) - { - var message = string.Format("Discriminators can only be registered for classes, not for interface {0}.", type.FullName); - throw new BsonSerializationException(message); - } - - __configLock.EnterWriteLock(); - try - { - HashSet hashSet; - if (!__discriminators.TryGetValue(discriminator, out hashSet)) - { - hashSet = new HashSet(); - __discriminators.Add(discriminator, hashSet); - } - - if (!hashSet.Contains(type)) - { - hashSet.Add(type); - - // mark all base types as discriminated (so we know that it's worth reading a discriminator) - for (var baseType = typeInfo.BaseType; baseType != null; baseType = baseType.GetTypeInfo().BaseType) - { - // We expect that TryAdd will always return true, so no need to check the return value. - __discriminatedTypes.TryAdd(baseType, true); - } - } - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.RegisterDiscriminator(type, discriminator); /// /// Registers the discriminator convention for a type. @@ -603,37 +243,15 @@ public static void RegisterDiscriminator(Type type, BsonValue discriminator) /// Type type. /// The discriminator convention. public static void RegisterDiscriminatorConvention(Type type, IDiscriminatorConvention convention) - { - __configLock.EnterWriteLock(); - try - { - if (!__discriminatorConventions.ContainsKey(type)) - { - __discriminatorConventions.Add(type, convention); - } - else - { - var message = string.Format("There is already a discriminator convention registered for type {0}.", type.FullName); - throw new BsonSerializationException(message); - } - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.RegisterDiscriminatorConvention(type, convention); /// /// Registers a generic serializer definition for a generic type. /// /// The generic type. /// The generic serializer definition. - public static void RegisterGenericSerializerDefinition( - Type genericTypeDefinition, - Type genericSerializerDefinition) - { - __typeMappingSerializationProvider.RegisterMapping(genericTypeDefinition, genericSerializerDefinition); - } + public static void RegisterGenericSerializerDefinition(Type genericTypeDefinition, Type genericSerializerDefinition) + => BsonSerializationDomain.Default.RegisterGenericSerializerDefinition(genericTypeDefinition, genericSerializerDefinition); /// /// Registers an IdGenerator for an Id Type. @@ -641,26 +259,14 @@ public static void RegisterGenericSerializerDefinition( /// The Id Type. /// The IdGenerator for the Id Type. public static void RegisterIdGenerator(Type type, IIdGenerator idGenerator) - { - __configLock.EnterWriteLock(); - try - { - __idGenerators[type] = idGenerator; - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.RegisterIdGenerator(type, idGenerator); /// /// Registers a serialization provider. /// /// The serialization provider. public static void RegisterSerializationProvider(IBsonSerializationProvider provider) - { - __serializerRegistry.RegisterSerializationProvider(provider); - } + => BsonSerializationDomain.Default.RegisterSerializationProvider(provider); /// /// Registers a serializer for a type. @@ -668,9 +274,7 @@ public static void RegisterSerializationProvider(IBsonSerializationProvider prov /// The type. /// The serializer. public static void RegisterSerializer(IBsonSerializer serializer) - { - RegisterSerializer(typeof(T), serializer); - } + => BsonSerializationDomain.Default.RegisterSerializer(serializer); /// /// Registers a serializer for a type. @@ -678,9 +282,7 @@ public static void RegisterSerializer(IBsonSerializer serializer) /// The type. /// The serializer. public static void RegisterSerializer(Type type, IBsonSerializer serializer) - { - __serializerRegistry.RegisterSerializer(type, serializer); - } + => BsonSerializationDomain.Default.RegisterSerializer(type, serializer); /// /// Serializes a value. @@ -694,13 +296,8 @@ public static void Serialize( IBsonWriter bsonWriter, TNominalType value, Action configurator = null, - BsonSerializationArgs args = default(BsonSerializationArgs)) - { - args.SetOrValidateNominalType(typeof(TNominalType), ""); - var serializer = LookupSerializer(); - var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator); - serializer.Serialize(context, args, value); - } + BsonSerializationArgs args = default) + => BsonSerializationDomain.Default.Serialize(bsonWriter, value, configurator, args); /// /// Serializes a value. @@ -716,12 +313,7 @@ public static void Serialize( object value, Action configurator = null, BsonSerializationArgs args = default(BsonSerializationArgs)) - { - args.SetOrValidateNominalType(nominalType, "nominalType"); - var serializer = LookupSerializer(nominalType); - var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator); - serializer.Serialize(context, args, value); - } + => BsonSerializationDomain.Default.Serialize(bsonWriter, nominalType, value, configurator, args); /// /// Tries to register a serializer for a type. @@ -730,9 +322,7 @@ public static void Serialize( /// The type. /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. public static bool TryRegisterSerializer(Type type, IBsonSerializer serializer) - { - return __serializerRegistry.TryRegisterSerializer(type, serializer); - } + => BsonSerializationDomain.Default.TryRegisterSerializer(type, serializer); /// /// Tries to register a serializer for a type. @@ -741,95 +331,13 @@ public static bool TryRegisterSerializer(Type type, IBsonSerializer serializer) /// The serializer. /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. public static bool TryRegisterSerializer(IBsonSerializer serializer) - { - return TryRegisterSerializer(typeof(T), serializer); - } + => BsonSerializationDomain.Default.TryRegisterSerializer(serializer); // internal static methods internal static void EnsureKnownTypesAreRegistered(Type nominalType) - { - if (__typesWithRegisteredKnownTypes.ContainsKey(nominalType)) - { - return; - } - - __configLock.EnterWriteLock(); - try - { - if (!__typesWithRegisteredKnownTypes.ContainsKey(nominalType)) - { - // only call LookupClassMap for classes with a BsonKnownTypesAttribute - var hasKnownTypesAttribute = nominalType.GetTypeInfo().GetCustomAttributes(typeof(BsonKnownTypesAttribute), inherit: false).Any(); - if (hasKnownTypesAttribute) - { - // try and force a scan of the known types - LookupSerializer(nominalType); - } - - // NOTE: The nominalType MUST be added to __typesWithRegisteredKnownTypes after all registration - // work is done to ensure that other threads don't access a partially registered nominalType - // when performing the initial check above outside the __config lock. - __typesWithRegisteredKnownTypes[nominalType] = null; - } - } - finally - { - __configLock.ExitWriteLock(); - } - } + => BsonSerializationDomain.Default.EnsureKnownTypesAreRegistered(nominalType); - // internal static methods internal static BsonValue[] GetDiscriminatorsForTypeAndSubTypes(Type type) - { - // note: EnsureKnownTypesAreRegistered handles its own locking so call from outside any lock - EnsureKnownTypesAreRegistered(type); - - var discriminators = new List(); - - __configLock.EnterReadLock(); - try - { - foreach (var entry in __discriminators) - { - var discriminator = entry.Key; - var actualTypes = entry.Value; - - var matchingType = actualTypes.SingleOrDefault(t => t == type || t.IsSubclassOf(type)); - if (matchingType != null) - { - discriminators.Add(discriminator); - } - } - } - finally - { - __configLock.ExitReadLock(); - } - - return discriminators.OrderBy(x => x).ToArray(); - } - - // private static methods - private static void CreateSerializerRegistry() - { - __serializerRegistry = new BsonSerializerRegistry(); - __typeMappingSerializationProvider = new TypeMappingSerializationProvider(); - - // order matters. It's in reverse order of how they'll get consumed - __serializerRegistry.RegisterSerializationProvider(new BsonClassMapSerializationProvider()); - __serializerRegistry.RegisterSerializationProvider(new DiscriminatedInterfaceSerializationProvider()); - __serializerRegistry.RegisterSerializationProvider(new CollectionsSerializationProvider()); - __serializerRegistry.RegisterSerializationProvider(new PrimitiveSerializationProvider()); - __serializerRegistry.RegisterSerializationProvider(new AttributedSerializationProvider()); - __serializerRegistry.RegisterSerializationProvider(__typeMappingSerializationProvider); - __serializerRegistry.RegisterSerializationProvider(new BsonObjectModelSerializationProvider()); - } - - private static void RegisterIdGenerators() - { - RegisterIdGenerator(typeof(BsonObjectId), BsonObjectIdGenerator.Instance); - RegisterIdGenerator(typeof(Guid), GuidGenerator.Instance); - RegisterIdGenerator(typeof(ObjectId), ObjectIdGenerator.Instance); - } + => BsonSerializationDomain.Default.GetDiscriminatorsForTypeAndSubTypes(type); } } diff --git a/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs b/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs index 429386f6019..cb316a0cd1d 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs @@ -22,10 +22,11 @@ namespace MongoDB.Bson.Serialization /// /// Default, global implementation of an . /// - public sealed class BsonSerializerRegistry : IBsonSerializerRegistry + public sealed class BsonSerializerRegistry : IBsonSerializerRegistry, IHasSerializationDomain { // private fields private readonly ConcurrentDictionary _cache; + private readonly IBsonSerializationDomain _serializationDomain; private readonly ConcurrentStack _serializationProviders; private readonly Func _createSerializer; @@ -34,12 +35,22 @@ public sealed class BsonSerializerRegistry : IBsonSerializerRegistry /// Initializes a new instance of the class. /// public BsonSerializerRegistry() + : this(BsonSerializationDomain.Default) { + } + + internal BsonSerializerRegistry(IBsonSerializationDomain domain) + { + _serializationDomain = domain; _cache = new ConcurrentDictionary(); _serializationProviders = new ConcurrentStack(); _createSerializer = CreateSerializer; } + internal IBsonSerializationDomain SerializationDomain => _serializationDomain; + + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Gets the serializer for the specified . @@ -94,6 +105,10 @@ public void RegisterSerializer(Type type, IBsonSerializer serializer) throw new ArgumentNullException("serializer"); } EnsureRegisteringASerializerForThisTypeIsAllowed(type); + if (serializer is IHasSerializationDomain hasSerializationDomain && hasSerializationDomain.SerializationDomain != _serializationDomain) + { + throw new ArgumentException($"Expected serializer to be for serialization domain {_serializationDomain.Name} but was for serialization domain {hasSerializationDomain.SerializationDomain.Name}."); + } if (!_cache.TryAdd(type, serializer)) { @@ -134,6 +149,10 @@ public bool TryRegisterSerializer(Type type, IBsonSerializer serializer) throw new ArgumentNullException(nameof(serializer)); } EnsureRegisteringASerializerForThisTypeIsAllowed(type); + if (serializer is IHasSerializationDomain hasSerializationDomain && hasSerializationDomain.SerializationDomain != _serializationDomain) + { + throw new ArgumentException($"Expected serializer to be for serialization domain {_serializationDomain.Name} but was for serialization domain {hasSerializationDomain.SerializationDomain.Name}."); + } if (_cache.TryAdd(type, serializer)) { @@ -170,6 +189,11 @@ private IBsonSerializer CreateSerializer(Type type) if (serializer != null) { + if (serializer is IHasSerializationDomain hasSerializationDomain && hasSerializationDomain.SerializationDomain != _serializationDomain) + { + throw new ArgumentException($"Expected serializer to be for serialization domain {_serializationDomain.Name} but was for serialization domain {hasSerializationDomain.SerializationDomain.Name}.", "serializer"); + } + return serializer; } } diff --git a/src/MongoDB.Bson/Serialization/Conventions/ConventionPack.cs b/src/MongoDB.Bson/Serialization/Conventions/ConventionPack.cs index af758baf685..193293528f2 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/ConventionPack.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/ConventionPack.cs @@ -22,17 +22,24 @@ namespace MongoDB.Bson.Serialization.Conventions /// /// A mutable pack of conventions. /// - public class ConventionPack : IConventionPack, IEnumerable + public class ConventionPack : IConventionPack, IEnumerable, IHasSerializationDomain { // private fields private readonly List _conventions; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// /// Initializes a new instance of the class. /// public ConventionPack() + : this(BsonSerializationDomain.Default) { + } + + internal ConventionPack(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; _conventions = new List(); } @@ -45,6 +52,8 @@ public IEnumerable Conventions get { return _conventions; } } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Adds the specified convention. diff --git a/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistry.cs b/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistry.cs index 217a7b69d86..38f2a4d76e2 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistry.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistry.cs @@ -14,7 +14,6 @@ */ using System; -using System.Collections.Generic; namespace MongoDB.Bson.Serialization.Conventions { @@ -23,60 +22,14 @@ namespace MongoDB.Bson.Serialization.Conventions /// public static class ConventionRegistry { - // private static fields - private readonly static List __conventionPacks = new List(); - private readonly static object __lock = new object(); - - // static constructors - static ConventionRegistry() - { - Register("__defaults__", DefaultConventionPack.Instance, t => true); - Register("__attributes__", AttributeConventionPack.Instance, t => true); - } - // public static methods /// /// Looks up the effective set of conventions that apply to a type. /// /// The type. /// The conventions for that type. - public static IConventionPack Lookup(Type type) - { - if (type == null) - { - throw new ArgumentNullException("type"); - } - - lock (__lock) - { - var pack = new ConventionPack(); - - // append any attribute packs (usually just one) at the end so attributes are processed last - var attributePacks = new List(); - foreach (var container in __conventionPacks) - { - if (container.Filter(type)) - { - - if (container.Name == "__attributes__") - { - attributePacks.Add(container.Pack); - } - else - { - pack.Append(container.Pack); - } - } - } - - foreach (var attributePack in attributePacks) - { - pack.Append(attributePack); - } - - return pack; - } - } + public static IConventionPack Lookup(Type type) => + BsonSerializationDomain.Default.ConventionRegistry.Lookup(type); /// /// Registers the conventions. @@ -84,33 +37,8 @@ public static IConventionPack Lookup(Type type) /// The name. /// The conventions. /// The filter. - public static void Register(string name, IConventionPack conventions, Func filter) - { - if (name == null) - { - throw new ArgumentNullException("name"); - } - if (conventions == null) - { - throw new ArgumentNullException("conventions"); - } - if (filter == null) - { - throw new ArgumentNullException("filter"); - } - - lock (__lock) - { - var container = new ConventionPackContainer - { - Filter = filter, - Name = name, - Pack = conventions - }; - - __conventionPacks.Add(container); - } - } + public static void Register(string name, IConventionPack conventions, Func filter) => + BsonSerializationDomain.Default.ConventionRegistry.Register(name, conventions, filter); /// /// Removes the conventions specified by the given name. @@ -119,25 +47,7 @@ public static void Register(string name, IConventionPack conventions, FuncRemoving a convention allows the removal of the special __defaults__ conventions /// and the __attributes__ conventions for those who want to completely customize the /// experience. - public static void Remove(string name) - { - if (name == null) - { - throw new ArgumentNullException("name"); - } - - lock (__lock) - { - __conventionPacks.RemoveAll(x => x.Name == name); - } - } - - // private class - private class ConventionPackContainer - { - public Func Filter; - public string Name; - public IConventionPack Pack; - } + public static void Remove(string name) => + BsonSerializationDomain.Default.ConventionRegistry.Remove(name); } } diff --git a/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistryInstance.cs b/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistryInstance.cs new file mode 100644 index 00000000000..97d5fa0c6db --- /dev/null +++ b/src/MongoDB.Bson/Serialization/Conventions/ConventionRegistryInstance.cs @@ -0,0 +1,152 @@ +/* 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; + +namespace MongoDB.Bson.Serialization.Conventions +{ + internal class ConventionRegistryInstance : IConventionRegistry, IHasSerializationDomain + { + private readonly List _conventionPacks = new(); + private readonly object _lock = new(); + private readonly IBsonSerializationDomain _serializationDomain; + + // constructors + internal ConventionRegistryInstance(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + var defaultConventionPack = new DefaultConventionPack(serializationDomain); + + Register("__defaults__", defaultConventionPack, t => true); + Register("__attributes__", AttributeConventionPack.Instance, t => true); + } + + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + + // public static methods + /// + /// Looks up the effective set of conventions that apply to a type. + /// + /// The type. + /// The conventions for that type. + public IConventionPack Lookup(Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + lock (_lock) + { + var pack = new ConventionPack(_serializationDomain); + + // append any attribute packs (usually just one) at the end so attributes are processed last + var attributePacks = new List(); + foreach (var container in _conventionPacks) + { + if (container.Filter(type)) + { + + if (container.Name == "__attributes__") + { + attributePacks.Add(container.Pack); + } + else + { + pack.Append(container.Pack); + } + } + } + + foreach (var attributePack in attributePacks) + { + pack.Append(attributePack); + } + + return pack; + } + } + + /// + /// Registers the conventions. + /// + /// The name. + /// The conventions. + /// The filter. + public void Register(string name, IConventionPack conventions, Func filter) + { + if (name == null) + { + throw new ArgumentNullException("name"); + } + + if (conventions == null) + { + throw new ArgumentNullException("conventions"); + } + + if (filter == null) + { + throw new ArgumentNullException("filter"); + } + + if (conventions is IHasSerializationDomain hasSerializationDomain && hasSerializationDomain.SerializationDomain != _serializationDomain) + { + throw new ArgumentException($"Expected convention pack to be for serialization domain {_serializationDomain.Name}, but was for serialization domain {hasSerializationDomain.SerializationDomain.Name}."); + } + + lock (_lock) + { + var container = new ConventionPackContainer + { + Filter = filter, + Name = name, + Pack = conventions + }; + + _conventionPacks.Add(container); + } + } + + /// + /// Removes the conventions specified by the given name. + /// + /// The name. + /// Removing a convention allows the removal of the special __defaults__ conventions + /// and the __attributes__ conventions for those who want to completely customize the + /// experience. + public void Remove(string name) + { + if (name == null) + { + throw new ArgumentNullException("name"); + } + + lock (_lock) + { + _conventionPacks.RemoveAll(x => x.Name == name); + } + } + + // private class + private class ConventionPackContainer + { + public Func Filter; + public string Name; + public IConventionPack Pack; + } + } +} diff --git a/src/MongoDB.Bson/Serialization/Conventions/DefaultConventionPack.cs b/src/MongoDB.Bson/Serialization/Conventions/DefaultConventionPack.cs index 48092dbcb4f..534dc335975 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/DefaultConventionPack.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/DefaultConventionPack.cs @@ -23,7 +23,7 @@ namespace MongoDB.Bson.Serialization.Conventions public class DefaultConventionPack : IConventionPack { // private static fields - private static readonly IConventionPack __defaultConventionPack = new DefaultConventionPack(); + private static readonly IConventionPack __defaultConventionPack = new DefaultConventionPack(BsonSerializationDomain.Default); // private fields private readonly IEnumerable _conventions; @@ -32,7 +32,7 @@ public class DefaultConventionPack : IConventionPack /// /// Initializes a new instance of the class. /// - private DefaultConventionPack() + internal DefaultConventionPack(IBsonSerializationDomain serializationDomain) { _conventions = new List { @@ -43,7 +43,7 @@ private DefaultConventionPack() new ImmutableTypeClassMapConvention(), new NamedParameterCreatorMapConvention(), new StringObjectIdIdGeneratorConvention(), // should be before LookupIdGeneratorConvention - new LookupIdGeneratorConvention() + new LookupIdGeneratorConvention(serializationDomain) }; } diff --git a/src/MongoDB.Bson/Serialization/Conventions/HierarchicalDiscriminatorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/HierarchicalDiscriminatorConvention.cs index beebf06230e..fe8573585bf 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/HierarchicalDiscriminatorConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/HierarchicalDiscriminatorConvention.cs @@ -30,7 +30,12 @@ public class HierarchicalDiscriminatorConvention : StandardDiscriminatorConventi /// /// The element name. public HierarchicalDiscriminatorConvention(string elementName) - : base(elementName) + : this(BsonSerializationDomain.Default, elementName) + { + } + + internal HierarchicalDiscriminatorConvention(IBsonSerializationDomain serializationDomain, string elementName) + : base(serializationDomain, elementName) { } @@ -44,7 +49,7 @@ public HierarchicalDiscriminatorConvention(string elementName) public override BsonValue GetDiscriminator(Type nominalType, Type actualType) { // TODO: this isn't quite right, not all classes are serialized using a class map serializer - var classMap = BsonClassMap.LookupClassMap(actualType); + var classMap = _serializationDomain.ClassMapRegistry.LookupClassMap(actualType); if (actualType != nominalType || classMap.DiscriminatorIsRequired || classMap.HasRootClass) { if (classMap.HasRootClass && !classMap.IsRootClass) diff --git a/src/MongoDB.Bson/Serialization/Conventions/IConventionRegistry.cs b/src/MongoDB.Bson/Serialization/Conventions/IConventionRegistry.cs new file mode 100644 index 00000000000..cd9808e88d6 --- /dev/null +++ b/src/MongoDB.Bson/Serialization/Conventions/IConventionRegistry.cs @@ -0,0 +1,43 @@ +/* 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; + +namespace MongoDB.Bson.Serialization.Conventions +{ + internal interface IConventionRegistry + { + /// + /// //TODO + /// + /// + /// + IConventionPack Lookup(Type type); + + /// + /// //TODO + /// + /// + /// + /// + void Register(string name, IConventionPack conventions, Func filter); + + /// + /// //TODO + /// + /// + void Remove(string name); + } +} diff --git a/src/MongoDB.Bson/Serialization/Conventions/LookupIdGeneratorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/LookupIdGeneratorConvention.cs index 81f31d14444..a74fda3e5a2 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/LookupIdGeneratorConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/LookupIdGeneratorConvention.cs @@ -21,8 +21,25 @@ namespace MongoDB.Bson.Serialization.Conventions /// /// A convention that looks up an id generator for the id member. /// - public class LookupIdGeneratorConvention : ConventionBase, IPostProcessingConvention + public class LookupIdGeneratorConvention : ConventionBase, IPostProcessingConvention, IHasSerializationDomain { + private readonly IBsonSerializationDomain _serializationDomain; + + /// + /// + /// + public LookupIdGeneratorConvention() + : this(BsonSerializationDomain.Default) + { + } + + internal LookupIdGeneratorConvention(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + } + + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Applies a post processing modification to the class map. @@ -35,7 +52,7 @@ public void PostProcess(BsonClassMap classMap) { if (idMemberMap.IdGenerator == null) { - var idGenerator = BsonSerializer.LookupIdGenerator(idMemberMap.MemberType); + var idGenerator = _serializationDomain.LookupIdGenerator(idMemberMap.MemberType); if (idGenerator != null) { idMemberMap.SetIdGenerator(idGenerator); diff --git a/src/MongoDB.Bson/Serialization/Conventions/NonPolymorphicDiscriminatorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/NonPolymorphicDiscriminatorConvention.cs new file mode 100644 index 00000000000..04529457e96 --- /dev/null +++ b/src/MongoDB.Bson/Serialization/Conventions/NonPolymorphicDiscriminatorConvention.cs @@ -0,0 +1,32 @@ +/* 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 MongoDB.Bson.IO; + +namespace MongoDB.Bson.Serialization.Conventions; + +internal class NonPolymorphicDiscriminatorConvention : IDiscriminatorConvention +{ + private static readonly NonPolymorphicDiscriminatorConvention __instance = new(); + + public static NonPolymorphicDiscriminatorConvention Instance => __instance; + + public string ElementName => throw new NotImplementedException(); + + public Type GetActualType(IBsonReader bsonReader, Type nominalType) => nominalType; + + public BsonValue GetDiscriminator(Type nominalType, Type actualType) => throw new NotImplementedException(); +} diff --git a/src/MongoDB.Bson/Serialization/Conventions/ObjectDiscriminatorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/ObjectDiscriminatorConvention.cs index 9a0a7683368..5463bd8a11a 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/ObjectDiscriminatorConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/ObjectDiscriminatorConvention.cs @@ -24,13 +24,14 @@ namespace MongoDB.Bson.Serialization.Conventions /// /// Represents the object discriminator convention. /// - public class ObjectDiscriminatorConvention : IDiscriminatorConvention + public class ObjectDiscriminatorConvention : IDiscriminatorConvention, IHasSerializationDomain { // private static fields private static ObjectDiscriminatorConvention __instance = new ObjectDiscriminatorConvention("_t"); // private fields private string _elementName; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// @@ -38,7 +39,16 @@ public class ObjectDiscriminatorConvention : IDiscriminatorConvention /// /// The element name. public ObjectDiscriminatorConvention(string elementName) + : this(BsonSerializationDomain.Default, elementName) { + } + + internal ObjectDiscriminatorConvention(IBsonSerializationDomain serializationDomain, string elementName) + { + if (serializationDomain == null) + { + throw new ArgumentNullException("serializationDomain"); + } if (elementName == null) { throw new ArgumentNullException("elementName"); @@ -47,6 +57,8 @@ public ObjectDiscriminatorConvention(string elementName) { throw new ArgumentException("Element names cannot contain nulls.", "elementName"); } + + _serializationDomain = serializationDomain; _elementName = elementName; } @@ -68,6 +80,8 @@ public string ElementName get { return _elementName; } } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// public override bool Equals(object obj) @@ -135,7 +149,7 @@ public Type GetActualType(IBsonReader bsonReader, Type nominalType) { discriminator = discriminator.AsBsonArray.Last(); // last item is leaf class discriminator } - actualType = BsonSerializer.LookupActualType(nominalType, discriminator); + actualType = _serializationDomain.LookupActualType(nominalType, discriminator); } bsonReader.ReturnToBookmark(bookmark); return actualType; diff --git a/src/MongoDB.Bson/Serialization/Conventions/ScalarDiscriminatorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/ScalarDiscriminatorConvention.cs index 521aff9b1f7..2322dfe5d6b 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/ScalarDiscriminatorConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/ScalarDiscriminatorConvention.cs @@ -31,7 +31,12 @@ public class ScalarDiscriminatorConvention : StandardDiscriminatorConvention, IS /// /// The element name. public ScalarDiscriminatorConvention(string elementName) - : base(elementName) + : this(BsonSerializationDomain.Default, elementName) + { + } + + internal ScalarDiscriminatorConvention(IBsonSerializationDomain serializationDomain, string elementName) + : base(serializationDomain, elementName) { } @@ -45,7 +50,7 @@ public ScalarDiscriminatorConvention(string elementName) public override BsonValue GetDiscriminator(Type nominalType, Type actualType) { // TODO: this isn't quite right, not all classes are serialized using a class map serializer - var classMap = BsonClassMap.LookupClassMap(actualType); + var classMap = _serializationDomain.ClassMapRegistry.LookupClassMap(actualType); if (actualType != nominalType || classMap.DiscriminatorIsRequired) { return classMap.Discriminator; @@ -59,7 +64,7 @@ public override BsonValue GetDiscriminator(Type nominalType, Type actualType) /// public BsonValue[] GetDiscriminatorsForTypeAndSubTypes(Type type) { - return _cachedTypeAndSubTypeDiscriminators.GetOrAdd(type, BsonSerializer.GetDiscriminatorsForTypeAndSubTypes); + return _cachedTypeAndSubTypeDiscriminators.GetOrAdd(type, _serializationDomain.GetDiscriminatorsForTypeAndSubTypes); } } } diff --git a/src/MongoDB.Bson/Serialization/Conventions/StandardDiscriminatorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/StandardDiscriminatorConvention.cs index d2b042d1fc0..6b7100ee7a9 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/StandardDiscriminatorConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/StandardDiscriminatorConvention.cs @@ -31,6 +31,7 @@ public abstract class StandardDiscriminatorConvention : IDiscriminatorConvention // private fields private string _elementName; + private protected readonly IBsonSerializationDomain _serializationDomain; // constructors /// @@ -38,7 +39,16 @@ public abstract class StandardDiscriminatorConvention : IDiscriminatorConvention /// /// The element name. protected StandardDiscriminatorConvention(string elementName) + : this(BsonSerializationDomain.Default, elementName) { + } + + private protected StandardDiscriminatorConvention(IBsonSerializationDomain serializationDomain, string elementName) + { + if (serializationDomain == null) + { + throw new ArgumentException("Serialization domain cannot be null.", nameof(serializationDomain)); + } if (string.IsNullOrEmpty(elementName)) { throw new ArgumentException("Discriminator element name name cannot be null or empty.", nameof(elementName)); @@ -48,6 +58,7 @@ protected StandardDiscriminatorConvention(string elementName) throw new ArgumentException("Discriminator element name cannot contain nulls.", nameof(elementName)); } + _serializationDomain = serializationDomain; _elementName = elementName; } @@ -102,10 +113,10 @@ public Type GetActualType(IBsonReader bsonReader, Type nominalType) if (bsonType == BsonType.Document) { // ensure KnownTypes of nominalType are registered (so IsTypeDiscriminated returns correct answer) - BsonSerializer.EnsureKnownTypesAreRegistered(nominalType); + _serializationDomain.EnsureKnownTypesAreRegistered(nominalType); // we can skip looking for a discriminator if nominalType has no discriminated sub types - if (BsonSerializer.IsTypeDiscriminated(nominalType)) + if (_serializationDomain.IsTypeDiscriminated(nominalType)) { var bookmark = bsonReader.GetBookmark(); bsonReader.ReadStartDocument(); @@ -118,7 +129,7 @@ public Type GetActualType(IBsonReader bsonReader, Type nominalType) { discriminator = discriminator.AsBsonArray.Last(); // last item is leaf class discriminator } - actualType = BsonSerializer.LookupActualType(nominalType, discriminator); + actualType = _serializationDomain.LookupActualType(nominalType, discriminator); } bsonReader.ReturnToBookmark(bookmark); return actualType; diff --git a/src/MongoDB.Bson/Serialization/Conventions/StringIdStoredAsObjectIdConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/StringIdStoredAsObjectIdConvention.cs index 923936d1508..4359810826d 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/StringIdStoredAsObjectIdConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/StringIdStoredAsObjectIdConvention.cs @@ -38,7 +38,7 @@ public void Apply(BsonMemberMap memberMap) return; } - var defaultStringSerializer = BsonSerializer.LookupSerializer(typeof(string)); + var defaultStringSerializer = memberMap.SerializationDomain.LookupSerializer(typeof(string)); if (memberMap.GetSerializer() != defaultStringSerializer) { return; diff --git a/src/MongoDB.Bson/Serialization/IBsonClassMapRegistry.cs b/src/MongoDB.Bson/Serialization/IBsonClassMapRegistry.cs new file mode 100644 index 00000000000..c122dd17f42 --- /dev/null +++ b/src/MongoDB.Bson/Serialization/IBsonClassMapRegistry.cs @@ -0,0 +1,96 @@ +/* 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.Reflection; + +namespace MongoDB.Bson.Serialization +{ + internal interface IBsonClassMapRegistry + { + /// + /// Gets all registered class maps. + /// + /// All registered class maps. + IEnumerable GetRegisteredClassMaps(); + + /// + /// Checks whether a class map is registered for a type. + /// + /// The type to check. + /// True if there is a class map registered for the type. + bool IsClassMapRegistered(Type type); + + /// + /// Looks up a class map (will AutoMap the class if no class map is registered). + /// + /// The class type. + /// The class map. + BsonClassMap LookupClassMap(Type classType); + + /// + /// Creates and registers a class map. + /// + /// The class. + /// The class map. + BsonClassMap RegisterClassMap(); + + /// + /// Creates and registers a class map. + /// + /// The class. + /// The class map initializer. + /// The class map. + BsonClassMap RegisterClassMap(Action> classMapInitializer); + + /// + /// Registers a class map. + /// + /// The class map. + void RegisterClassMap(BsonClassMap classMap); + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// True if this call registered the class map, false if the class map was already registered. + bool TryRegisterClassMap(); + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// The class map. + /// True if this call registered the class map, false if the class map was already registered. + bool TryRegisterClassMap(BsonClassMap classMap); + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// The class map initializer (only called if the class map is not already registered). + /// True if this call registered the class map, false if the class map was already registered. + bool TryRegisterClassMap(Action> classMapInitializer); + + /// + /// Registers a class map if it is not already registered. + /// + /// The class. + /// The class map factory (only called if the class map is not already registered). + /// True if this call registered the class map, false if the class map was already registered. + bool TryRegisterClassMap(Func> classMapFactory); + } +} diff --git a/src/MongoDB.Bson/Serialization/IBsonSerializationDomain.cs b/src/MongoDB.Bson/Serialization/IBsonSerializationDomain.cs new file mode 100644 index 00000000000..32d64ce6a64 --- /dev/null +++ b/src/MongoDB.Bson/Serialization/IBsonSerializationDomain.cs @@ -0,0 +1,332 @@ +/* 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.IO; +using System.Threading; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization.Conventions; + +namespace MongoDB.Bson.Serialization +{ + /// + /// //TODO + /// + internal interface IBsonSerializationDomain + { + string Name { get; } //FP This is used for debugging purposes, but we could decide to make it public if needed. + + /// + /// Returns whether the given type has any discriminators registered for any of its subclasses. + /// + /// A Type. + /// True if the type is discriminated. + bool IsTypeDiscriminated(Type type); + + /// + /// Looks up the actual type of an object to be deserialized. + /// + /// The nominal type of the object. + /// The discriminator. + /// The actual type of the object. + Type LookupActualType(Type nominalType, BsonValue discriminator); + + /// + /// Looks up the discriminator convention for a type. + /// + /// The type. + /// A discriminator convention. + IDiscriminatorConvention LookupDiscriminatorConvention(Type type); + + /// + /// Looks up an IdGenerator. + /// + /// The Id type. + /// An IdGenerator for the Id type. + IIdGenerator LookupIdGenerator(Type type); + + /// + /// Looks up a serializer for a Type. + /// + /// The type. + /// A serializer for type T. + IBsonSerializer LookupSerializer(); + + /// + /// Looks up a serializer for a Type. + /// + /// The Type. + /// A serializer for the Type. + IBsonSerializer LookupSerializer(Type type); + + /// + /// Gets the serializer registry. + /// + IBsonSerializerRegistry SerializerRegistry { get; } + + /// + /// Registers the discriminator for a type. + /// + /// The type. + /// The discriminator. + void RegisterDiscriminator(Type type, BsonValue discriminator); + + /// + /// Registers the discriminator convention for a type. + /// + /// Type type. + /// The discriminator convention. + void RegisterDiscriminatorConvention(Type type, IDiscriminatorConvention convention); + + /// + /// Registers a generic serializer definition for a generic type. + /// + /// The generic type. + /// The generic serializer definition. + void RegisterGenericSerializerDefinition( + Type genericTypeDefinition, + Type genericSerializerDefinition); + + /// + /// Registers an IdGenerator for an Id Type. + /// + /// The Id Type. + /// The IdGenerator for the Id Type. + void RegisterIdGenerator(Type type, IIdGenerator idGenerator); + + /// + /// Registers a serialization provider. + /// + /// The serialization provider. + void RegisterSerializationProvider(IBsonSerializationProvider provider); + + /// + /// Registers a serializer for a type. + /// + /// The type. + /// The serializer. + void RegisterSerializer(IBsonSerializer serializer); + + /// + /// Registers a serializer for a type. + /// + /// The type. + /// The serializer. + void RegisterSerializer(Type type, IBsonSerializer serializer); + + /// + /// Tries to register a serializer for a type. + /// + /// The serializer. + /// The type. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + bool TryRegisterSerializer(Type type, IBsonSerializer serializer); + + /// + /// Tries to register a serializer for a type. + /// + /// The type. + /// The serializer. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + bool TryRegisterSerializer(IBsonSerializer serializer); + + /// + /// Gets or sets whether to use the NullIdChecker on reference Id types that don't have an IdGenerator registered. + /// + bool UseNullIdChecker { get; set; } + + /// + /// Gets or sets whether to use the ZeroIdChecker on value Id types that don't have an IdGenerator registered. + /// + bool UseZeroIdChecker { get; set; } + + /// + /// Deserializes an object from a BsonDocument. + /// + /// The nominal type of the object. + /// The BsonDocument. + /// The configurator. + /// A deserialized value. + TNominalType Deserialize(BsonDocument document, + Action configurator = null); + + /// + /// Deserializes a value. + /// + /// The nominal type of the object. + /// The BsonReader. + /// The configurator. + /// A deserialized value. + TNominalType Deserialize(IBsonReader bsonReader, + Action configurator = null); + + /// + /// Deserializes an object from a BSON byte array. + /// + /// The nominal type of the object. + /// The BSON byte array. + /// The configurator. + /// A deserialized value. + TNominalType Deserialize(byte[] bytes, + Action configurator = null); + + /// + /// Deserializes an object from a BSON Stream. + /// + /// The nominal type of the object. + /// The BSON Stream. + /// The configurator. + /// A deserialized value. + TNominalType Deserialize(Stream stream, + Action configurator = null); + + /// + /// Deserializes an object from a JSON string. + /// + /// The nominal type of the object. + /// The JSON string. + /// The configurator. + /// A deserialized value. + TNominalType Deserialize(string json, + Action configurator = null); + + /// + /// Deserializes an object from a JSON TextReader. + /// + /// The nominal type of the object. + /// The JSON TextReader. + /// The configurator. + /// A deserialized value. + TNominalType Deserialize(TextReader textReader, + Action configurator = null); + + /// + /// Deserializes an object from a BsonDocument. + /// + /// The BsonDocument. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + object Deserialize(BsonDocument document, Type nominalType, + Action configurator = null); + + /// + /// Deserializes a value. + /// + /// The BsonReader. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + object Deserialize(IBsonReader bsonReader, Type nominalType, + Action configurator = null); + + /// + /// Deserializes an object from a BSON byte array. + /// + /// The BSON byte array. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + object Deserialize(byte[] bytes, Type nominalType, + Action configurator = null); + + /// + /// Deserializes an object from a BSON Stream. + /// + /// The BSON Stream. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + object Deserialize(Stream stream, Type nominalType, + Action configurator = null); + + /// + /// Deserializes an object from a JSON string. + /// + /// The JSON string. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + object Deserialize(string json, Type nominalType, + Action configurator = null); + + /// + /// Deserializes an object from a JSON TextReader. + /// + /// The JSON TextReader. + /// The nominal type of the object. + /// The configurator. + /// A deserialized value. + object Deserialize(TextReader textReader, Type nominalType, + Action configurator = null); + + /// + /// Serializes a value. + /// + /// The nominal type of the object. + /// The BsonWriter. + /// The object. + /// The serialization context configurator. + /// The serialization args. + void Serialize( + IBsonWriter bsonWriter, + TNominalType value, + Action configurator = null, + BsonSerializationArgs args = default(BsonSerializationArgs)); + + /// + /// Serializes a value. + /// + /// The BsonWriter. + /// The nominal type of the object. + /// The object. + /// The serialization context configurator. + /// The serialization args. + void Serialize( + IBsonWriter bsonWriter, + Type nominalType, + object value, + Action configurator = null, + BsonSerializationArgs args = default(BsonSerializationArgs)); + + /// + /// //TODO + /// + IBsonClassMapRegistry ClassMapRegistry { get; } + + /// + /// //TODO + /// + IConventionRegistry ConventionRegistry { get; } + + /// + /// //TODO + /// + IBsonDefaults BsonDefaults { get; } + + //DOMAIN-API The following methods and properties were not public on BsonSerializer + + void EnsureKnownTypesAreRegistered(Type nominalType); + + BsonValue[] GetDiscriminatorsForTypeAndSubTypes(Type type); + + IDiscriminatorConvention GetOrRegisterDiscriminatorConvention(Type type, + IDiscriminatorConvention discriminatorConvention); + + bool IsDiscriminatorConventionRegisteredAtThisLevel(Type type); + + ReaderWriterLockSlim ConfigLock { get; } + } +} diff --git a/src/MongoDB.Bson/Serialization/IBsonSerializerExtensions.cs b/src/MongoDB.Bson/Serialization/IBsonSerializerExtensions.cs index fd5998c93b3..ebe52ba5255 100644 --- a/src/MongoDB.Bson/Serialization/IBsonSerializerExtensions.cs +++ b/src/MongoDB.Bson/Serialization/IBsonSerializerExtensions.cs @@ -50,15 +50,44 @@ public static TValue Deserialize(this IBsonSerializer serializer return serializer.Deserialize(context, args); } + internal static IBsonSerializer GetSerializerForBaseType(this IBsonSerializer serializer, Type baseType) + { + throw new NotImplementedException(); + } + + internal static IBsonSerializer GetSerializerForDerivedType(this IBsonSerializer serializer, Type derivedType) + { + if (serializer is IHasSerializationDomain domainSpecificSerializer) + { + return domainSpecificSerializer.SerializationDomain.LookupSerializer(derivedType); + } + else + { + return BsonSerializationDomain.Default.LookupSerializer(derivedType); + } + } + /// /// Gets the discriminator convention for a serializer. /// /// The serializer. /// The discriminator convention. - public static IDiscriminatorConvention GetDiscriminatorConvention(this IBsonSerializer serializer) => - serializer is IHasDiscriminatorConvention hasDiscriminatorConvention - ? hasDiscriminatorConvention.DiscriminatorConvention - : BsonSerializer.LookupDiscriminatorConvention(serializer.ValueType); + public static IDiscriminatorConvention GetDiscriminatorConvention(this IBsonSerializer serializer) + { + if (serializer is IHasDiscriminatorConvention hasDiscriminatorConvention) + { + return hasDiscriminatorConvention.DiscriminatorConvention; + } + else if (serializer is IHasSerializationDomain hasSerializationDomain) + { + var serializationDomain = hasSerializationDomain.SerializationDomain; + return serializationDomain.LookupDiscriminatorConvention(serializer.ValueType); + } + else + { + return NonPolymorphicDiscriminatorConvention.Instance; + } + } /// /// Serializes a value. diff --git a/src/MongoDB.Bson/Serialization/IHasSerializationDomain.cs b/src/MongoDB.Bson/Serialization/IHasSerializationDomain.cs new file mode 100644 index 00000000000..aaf4541f286 --- /dev/null +++ b/src/MongoDB.Bson/Serialization/IHasSerializationDomain.cs @@ -0,0 +1,21 @@ +/* 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. + */ + +namespace MongoDB.Bson.Serialization; + +internal interface IHasSerializationDomain +{ + IBsonSerializationDomain SerializationDomain { get; } +} diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs index fdcd59916ed..d28f70643b4 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs @@ -27,10 +27,11 @@ namespace MongoDB.Bson.Serialization /// Represents a serializer for a class map. /// /// The type of the class. - public sealed class BsonClassMapSerializer : SerializerBase, IBsonIdProvider, IBsonDocumentSerializer, IBsonPolymorphicSerializer, IHasDiscriminatorConvention + public sealed class BsonClassMapSerializer : SerializerBase, IBsonIdProvider, IBsonDocumentSerializer, IBsonPolymorphicSerializer, IHasDiscriminatorConvention, IHasSerializationDomain { // private fields private readonly BsonClassMap _classMap; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// @@ -38,7 +39,16 @@ public sealed class BsonClassMapSerializer : SerializerBase, IBs /// /// The class map. public BsonClassMapSerializer(BsonClassMap classMap) + : this(BsonSerializationDomain.Default, classMap) { + } + + internal BsonClassMapSerializer(IBsonSerializationDomain serializationDomain, BsonClassMap classMap) + { + if (serializationDomain == null) + { + throw new ArgumentNullException(nameof(serializationDomain)); + } if (classMap == null) { throw new ArgumentNullException(nameof(classMap)); @@ -53,6 +63,7 @@ public BsonClassMapSerializer(BsonClassMap classMap) throw new ArgumentException("Class map is not frozen.", nameof(classMap)); } + _serializationDomain = serializationDomain; _classMap = classMap; } @@ -71,6 +82,8 @@ public bool IsDiscriminatorCompatibleWithObjectSerializer get { return true; } } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Deserializes a value. @@ -96,8 +109,8 @@ public override TClass Deserialize(BsonDeserializationContext context, BsonDeser return DeserializeClass(context); } - var serializer = BsonSerializer.LookupSerializer(actualType); - return (TClass)serializer.Deserialize(context); + var actualTypeSerializer = this.GetSerializerForDerivedType(actualType); + return (TClass)actualTypeSerializer.Deserialize(context); } /// @@ -392,8 +405,8 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati return; } - var serializer = BsonSerializer.LookupSerializer(actualType); - serializer.Serialize(context, args, value); + var actualTypeSerializer = this.GetSerializerForDerivedType(actualType); + actualTypeSerializer.Serialize(context, args, value); } /// diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonDocumentSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonDocumentSerializer.cs index 2ba62a8294c..ce979e5003c 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/BsonDocumentSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/BsonDocumentSerializer.cs @@ -89,7 +89,7 @@ public bool GetDocumentId( if (bsonDocument.TryGetValue("_id", out idBsonValue)) { id = idBsonValue; - idGenerator = BsonSerializer.LookupIdGenerator(id.GetType()); + idGenerator = BsonSerializationDomain.Default.LookupIdGenerator(id.GetType()); if (idGenerator == null) { diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonValueSerializerBase.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonValueSerializerBase.cs index e3a5776870c..7c90adf7010 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/BsonValueSerializerBase.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/BsonValueSerializerBase.cs @@ -70,8 +70,8 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati var actualType = value.GetType(); if (actualType != ValueType && !args.SerializeAsNominalType) { - var serializer = BsonSerializer.LookupSerializer(actualType); - serializer.Serialize(context, value); + var actualTypeSerializer = this.GetSerializerForDerivedType(actualType); + actualTypeSerializer.Serialize(context, value); return; } diff --git a/src/MongoDB.Bson/Serialization/Serializers/ClassSerializerBase.cs b/src/MongoDB.Bson/Serialization/Serializers/ClassSerializerBase.cs index 07723b9cddb..571651da546 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ClassSerializerBase.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ClassSerializerBase.cs @@ -47,8 +47,8 @@ public override TValue Deserialize(BsonDeserializationContext context, BsonDeser } else { - var serializer = BsonSerializer.LookupSerializer(actualType); - return (TValue)serializer.Deserialize(context, args); + var actualTypeSerializer = this.GetSerializerForDerivedType(actualType); + return (TValue)actualTypeSerializer.Deserialize(context, args); } } } @@ -75,8 +75,8 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati } else { - var serializer = BsonSerializer.LookupSerializer(actualType); - serializer.Serialize(context, value); + var actualTypeSerializer = this.GetSerializerForDerivedType(actualType); + actualTypeSerializer.Serialize(context, value); } } } diff --git a/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs b/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs index 96b708d8aa6..5b02568f8a4 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs @@ -58,7 +58,12 @@ public DictionarySerializerBase() /// /// The dictionary representation. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation) - : this(dictionaryRepresentation, BsonSerializer.LookupSerializer(), BsonSerializer.LookupSerializer()) + : this(BsonSerializationDomain.Default, dictionaryRepresentation) + { + } + + internal DictionarySerializerBase(IBsonSerializationDomain serializationDomain, DictionaryRepresentation dictionaryRepresentation) + : this(dictionaryRepresentation, serializationDomain.LookupSerializer(), serializationDomain.LookupSerializer()) { } @@ -386,7 +391,12 @@ public DictionarySerializerBase() /// /// The dictionary representation. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation) - : this(dictionaryRepresentation, BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default, dictionaryRepresentation) + { + } + + internal DictionarySerializerBase(IBsonSerializationDomain serializationDomain, DictionaryRepresentation dictionaryRepresentation) + : this(dictionaryRepresentation, serializationDomain.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs index eb0035489e0..8fd1454fe02 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs @@ -37,19 +37,26 @@ public interface IDiscriminatedInterfaceSerializer public sealed class DiscriminatedInterfaceSerializer : SerializerBase, IBsonDocumentSerializer, - IDiscriminatedInterfaceSerializer + IDiscriminatedInterfaceSerializer, + IHasSerializationDomain // where TInterface is an interface { #region static private static IBsonSerializer CreateInterfaceSerializer() + { + return CreateInterfaceSerializer(BsonSerializationDomain.Default); + } + + internal static IBsonSerializer CreateInterfaceSerializer(IBsonSerializationDomain serializationDomain) { var classMapDefinition = typeof(BsonClassMap<>); var classMapType = classMapDefinition.MakeGenericType(typeof(TInterface)); - var classMap = (BsonClassMap)Activator.CreateInstance(classMapType); + var bindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var classMap = (BsonClassMap)Activator.CreateInstance(classMapType, bindingAttr, binder: null, args: [serializationDomain], culture: null); classMap.AutoMap(); - classMap.SetDiscriminatorConvention(BsonSerializer.LookupDiscriminatorConvention(typeof(TInterface))); + classMap.SetDiscriminatorConvention(serializationDomain.LookupDiscriminatorConvention(typeof(TInterface))); classMap.Freeze(); - return new BsonClassMapSerializer(classMap); + return new BsonClassMapSerializer(serializationDomain, classMap); } #endregion @@ -58,13 +65,19 @@ private static IBsonSerializer CreateInterfaceSerializer() private readonly IDiscriminatorConvention _discriminatorConvention; private readonly IBsonSerializer _interfaceSerializer; private readonly IBsonSerializer _objectSerializer; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// /// Initializes a new instance of the class. /// public DiscriminatedInterfaceSerializer() - : this(discriminatorConvention: null) + : this(BsonSerializationDomain.Default) + { + } + + internal DiscriminatedInterfaceSerializer(IBsonSerializationDomain serializationDomain) + : this(serializationDomain, discriminatorConvention: null) { } @@ -75,7 +88,12 @@ public DiscriminatedInterfaceSerializer() /// interfaceType /// interfaceType public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorConvention) - : this(discriminatorConvention, CreateInterfaceSerializer(), objectSerializer: null) + : this(BsonSerializationDomain.Default, discriminatorConvention) + { + } + + internal DiscriminatedInterfaceSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention) + : this(serializationDomain, discriminatorConvention, CreateInterfaceSerializer(serializationDomain), objectSerializer: null) { } @@ -87,7 +105,12 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo /// interfaceType /// interfaceType public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorConvention, IBsonSerializer interfaceSerializer) - : this(discriminatorConvention, interfaceSerializer, objectSerializer: null) + : this(BsonSerializationDomain.Default, discriminatorConvention, interfaceSerializer) + { + } + + internal DiscriminatedInterfaceSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention, IBsonSerializer interfaceSerializer) + : this(serializationDomain, discriminatorConvention, interfaceSerializer, objectSerializer: null) { } @@ -100,6 +123,11 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo /// interfaceType /// interfaceType public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorConvention, IBsonSerializer interfaceSerializer, IBsonSerializer objectSerializer) + : this(BsonSerializationDomain.Default, discriminatorConvention, interfaceSerializer, objectSerializer) + { + } + + internal DiscriminatedInterfaceSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention, IBsonSerializer interfaceSerializer, IBsonSerializer objectSerializer) { var interfaceTypeInfo = typeof(TInterface).GetTypeInfo(); if (!interfaceTypeInfo.IsInterface) @@ -109,12 +137,13 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo } _interfaceType = typeof(TInterface); + _serializationDomain = serializationDomain; _discriminatorConvention = discriminatorConvention ?? interfaceSerializer.GetDiscriminatorConvention(); _interfaceSerializer = interfaceSerializer; if (objectSerializer == null) { - objectSerializer = BsonSerializer.LookupSerializer(); + objectSerializer = _serializationDomain.LookupSerializer(); if (objectSerializer is ObjectSerializer standardObjectSerializer) { Func allowedTypes = (Type type) => typeof(TInterface).IsAssignableFrom(type); @@ -139,6 +168,8 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo IBsonSerializer IDiscriminatedInterfaceSerializer.InterfaceSerializer => _interfaceSerializer; + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Deserializes a value. @@ -165,7 +196,7 @@ public override TInterface Deserialize(BsonDeserializationContext context, BsonD throw new FormatException(message); } - var serializer = BsonSerializer.LookupSerializer(actualType); + var serializer = this.GetSerializerForDerivedType(actualType); return (TInterface)serializer.Deserialize(context, args); } } diff --git a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedWrapperSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedWrapperSerializer.cs index d7d5be76596..94c7e65aa64 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedWrapperSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedWrapperSerializer.cs @@ -21,7 +21,7 @@ namespace MongoDB.Bson.Serialization.Serializers /// Represents a serializer that serializes values as a discriminator/value pair. /// /// The type of the value. - public sealed class DiscriminatedWrapperSerializer : SerializerBase + public sealed class DiscriminatedWrapperSerializer : SerializerBase, IHasSerializationDomain { // private constants private static class Flags @@ -35,6 +35,7 @@ private static class Flags private readonly IDiscriminatorConvention _discriminatorConvention; private readonly SerializerHelper _helper; private readonly SerializerHelper _isPositionedHelper; + private readonly IBsonSerializationDomain _serializationDomain; private readonly IBsonSerializer _wrappedSerializer; // constructors @@ -44,7 +45,13 @@ private static class Flags /// The discriminator convention. /// The wrapped serializer. public DiscriminatedWrapperSerializer(IDiscriminatorConvention discriminatorConvention, IBsonSerializer wrappedSerializer) + : this(BsonSerializationDomain.Default, discriminatorConvention, wrappedSerializer) { + } + + internal DiscriminatedWrapperSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention, IBsonSerializer wrappedSerializer) + { + _serializationDomain = serializationDomain; _discriminatorConvention = discriminatorConvention; _wrappedSerializer = wrappedSerializer; @@ -62,6 +69,8 @@ public DiscriminatedWrapperSerializer(IDiscriminatorConvention discriminatorConv ); } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Deserializes a value. @@ -74,7 +83,7 @@ public override TValue Deserialize(BsonDeserializationContext context, BsonDeser var bsonReader = context.Reader; var nominalType = args.NominalType; var actualType = _discriminatorConvention.GetActualType(bsonReader, nominalType); - var serializer = BsonSerializer.LookupSerializer(actualType); + var serializer = _serializationDomain.LookupSerializer(actualType); TValue value = default(TValue); _helper.DeserializeMembers(context, (elementName, flag) => diff --git a/src/MongoDB.Bson/Serialization/Serializers/DynamicDocumentBaseSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/DynamicDocumentBaseSerializer.cs index d020fd3895d..ac603bb68c8 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DynamicDocumentBaseSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DynamicDocumentBaseSerializer.cs @@ -24,17 +24,28 @@ namespace MongoDB.Bson.Serialization.Serializers /// Base serializer for dynamic types. /// /// The dynamic type. - public abstract class DynamicDocumentBaseSerializer : SerializerBase where T : class, IDynamicMetaObjectProvider + public abstract class DynamicDocumentBaseSerializer : SerializerBase, IHasSerializationDomain + where T : class, IDynamicMetaObjectProvider { - // private static fields - private static readonly IBsonSerializer __objectSerializer = BsonSerializer.LookupSerializer(); + // private fields + private IBsonSerializer _objectSerializer; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// /// Initializes a new instance of the class. /// protected DynamicDocumentBaseSerializer() - { } + : this(BsonSerializationDomain.Default) + { + } + + internal DynamicDocumentBaseSerializer(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + } + + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; // public methods /// @@ -58,7 +69,7 @@ public override T Deserialize(BsonDeserializationContext context, BsonDeserializ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { var name = bsonReader.ReadName(); - var value = __objectSerializer.Deserialize(dynamicContext); + var value = GetObjectSerializer().Deserialize(dynamicContext); SetValueForMember(document, name, value); } bsonReader.ReadEndDocument(); @@ -101,7 +112,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati if (TryGetValueForMember(value, memberName, out memberValue)) { bsonWriter.WriteName(memberName); - __objectSerializer.Serialize(dynamicContext, memberValue); + GetObjectSerializer().Serialize(dynamicContext, memberValue); } } bsonWriter.WriteEndDocument(); @@ -142,5 +153,11 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati /// The value. /// true if the member should be serialized; otherwise false. protected abstract bool TryGetValueForMember(T document, string memberName, out object value); + + //private methods + private IBsonSerializer GetObjectSerializer() + { + return _objectSerializer ??= _serializationDomain.LookupSerializer(); + } } } diff --git a/src/MongoDB.Bson/Serialization/Serializers/EnumerableSerializerBase.cs b/src/MongoDB.Bson/Serialization/Serializers/EnumerableSerializerBase.cs index 5d810b01861..e129cbbaa9b 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/EnumerableSerializerBase.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/EnumerableSerializerBase.cs @@ -35,7 +35,7 @@ public abstract class EnumerableSerializerBase : SerializerBase, /// Initializes a new instance of the class. /// protected EnumerableSerializerBase() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -234,7 +234,7 @@ public abstract class EnumerableSerializerBase : SerializerBase class. /// protected EnumerableSerializerBase() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -419,4 +419,3 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati protected abstract TValue FinalizeResult(object accumulator); } } - diff --git a/src/MongoDB.Bson/Serialization/Serializers/ExpandoObjectSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/ExpandoObjectSerializer.cs index 4ac504304e5..53727860e1c 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ExpandoObjectSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ExpandoObjectSerializer.cs @@ -35,8 +35,14 @@ public sealed class ExpandoObjectSerializer : DynamicDocumentBaseSerializer class. /// public ExpandoObjectSerializer() + : this(BsonSerializationDomain.Default) { - _listSerializer = BsonSerializer.LookupSerializer>(); + } + + internal ExpandoObjectSerializer(IBsonSerializationDomain serializationDomain) + : base(serializationDomain) + { + _listSerializer = serializationDomain.LookupSerializer>(); } /// diff --git a/src/MongoDB.Bson/Serialization/Serializers/IEnumerableDeserializingAsCollectionSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/IEnumerableDeserializingAsCollectionSerializer.cs index ca9ec6a771b..de8d26e7ef1 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/IEnumerableDeserializingAsCollectionSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/IEnumerableDeserializingAsCollectionSerializer.cs @@ -50,7 +50,7 @@ private static void EnsureTIEnumerableIsAnInterface() /// Initializes a new instance of the IEnumerableDeserializingAsCollectionSerializer class. /// public IEnumerableDeserializingAsCollectionSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/ImpliedImplementationInterfaceSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/ImpliedImplementationInterfaceSerializer.cs index fc8cdf81829..4370f78dcc5 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ImpliedImplementationInterfaceSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ImpliedImplementationInterfaceSerializer.cs @@ -41,18 +41,20 @@ public sealed class ImpliedImplementationInterfaceSerializer> _lazyImplementationSerializer; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// /// Initializes a new instance of the class. /// public ImpliedImplementationInterfaceSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default) { } @@ -61,7 +63,12 @@ public ImpliedImplementationInterfaceSerializer() /// /// The implementation serializer. public ImpliedImplementationInterfaceSerializer(IBsonSerializer implementationSerializer) - : this(new Lazy>(() => implementationSerializer)) + : this(BsonSerializationDomain.Default, new Lazy>(() => implementationSerializer)) + { + } + + internal ImpliedImplementationInterfaceSerializer(IBsonSerializationDomain serializationDomain, IBsonSerializer implementationSerializer) + : this(serializationDomain, new Lazy>(() => implementationSerializer)) { if (implementationSerializer == null) { @@ -74,15 +81,20 @@ public ImpliedImplementationInterfaceSerializer(IBsonSerializer /// /// The serializer registry. public ImpliedImplementationInterfaceSerializer(IBsonSerializerRegistry serializerRegistry) - : this(new Lazy>(() => serializerRegistry.GetSerializer())) + : this((serializerRegistry as BsonSerializerRegistry)?.SerializationDomain) { - if (serializerRegistry == null) + } + + internal ImpliedImplementationInterfaceSerializer(IBsonSerializationDomain serializationDomain) + : this(serializationDomain, new Lazy>(() => serializationDomain.LookupSerializer())) + { + if (serializationDomain == null) { - throw new ArgumentNullException("serializerRegistry"); + throw new ArgumentNullException("serializationDomain"); } } - private ImpliedImplementationInterfaceSerializer(Lazy> lazyImplementationSerializer) + private ImpliedImplementationInterfaceSerializer(IBsonSerializationDomain serializationDomain, Lazy> lazyImplementationSerializer) { var interfaceTypeInfo = typeof(TInterface).GetTypeInfo(); if (!interfaceTypeInfo.IsInterface) @@ -91,6 +103,7 @@ private ImpliedImplementationInterfaceSerializer(Lazy"); } + _serializationDomain = serializationDomain; _lazyImplementationSerializer = lazyImplementationSerializer; } @@ -156,6 +169,8 @@ public IBsonSerializer ImplementationSerializer IBsonSerializer IImpliedImplementationInterfaceSerializer.ImplementationSerializer => ImplementationSerializer; + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + /// /// Gets the value serializer. /// @@ -279,7 +294,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati } else { - var serializer = BsonSerializer.LookupSerializer(actualType); + var serializer = _serializationDomain.LookupSerializer(actualType); serializer.Serialize(context, value); } } diff --git a/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs index ea1f59ac971..8e314323826 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs @@ -66,7 +66,7 @@ public KeyValuePairSerializer() /// /// The representation. public KeyValuePairSerializer(BsonType representation) - : this(representation, BsonSerializer.SerializerRegistry) + : this(representation, BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/NullableSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/NullableSerializer.cs index 8740bdd3a9b..99158ce9d83 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/NullableSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/NullableSerializer.cs @@ -64,7 +64,7 @@ public sealed class NullableSerializer : /// Initializes a new instance of the class. /// public NullableSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs index 36e4403718c..0049d82e9c2 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs @@ -25,7 +25,7 @@ namespace MongoDB.Bson.Serialization.Serializers /// /// Represents a serializer for objects. /// - public sealed class ObjectSerializer : ClassSerializerBase, IHasDiscriminatorConvention + public sealed class ObjectSerializer : ClassSerializerBase, IHasDiscriminatorConvention, IHasSerializationDomain { #region static // private static fields @@ -61,13 +61,19 @@ public sealed class ObjectSerializer : ClassSerializerBase, IHasDiscrimi private readonly IDiscriminatorConvention _discriminatorConvention; private readonly GuidRepresentation _guidRepresentation; private readonly GuidSerializer _guidSerializer; + private readonly IBsonSerializationDomain _serializationDomain; // constructors /// /// Initializes a new instance of the class. /// public ObjectSerializer() - : this(BsonSerializer.LookupDiscriminatorConvention(typeof(object))) + : this(BsonSerializationDomain.Default) + { + } + + internal ObjectSerializer(IBsonSerializationDomain serializationDomain) + : this(serializationDomain, serializationDomain.LookupDiscriminatorConvention(typeof(object))) { } @@ -77,7 +83,12 @@ public ObjectSerializer() /// The discriminator convention. /// discriminatorConvention public ObjectSerializer(IDiscriminatorConvention discriminatorConvention) - : this(discriminatorConvention, GuidRepresentation.Unspecified) + : this(BsonSerializationDomain.Default, discriminatorConvention) + { + } + + internal ObjectSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention) + : this(serializationDomain, discriminatorConvention, GuidRepresentation.Unspecified) { } @@ -87,7 +98,12 @@ public ObjectSerializer(IDiscriminatorConvention discriminatorConvention) /// The discriminator convention. /// The Guid representation. public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation) - : this(discriminatorConvention, guidRepresentation, DefaultFrameworkAllowedTypes.AllowedTypes) + : this(BsonSerializationDomain.Default, discriminatorConvention, guidRepresentation) + { + } + + internal ObjectSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation) + : this(serializationDomain, discriminatorConvention, guidRepresentation, DefaultFrameworkAllowedTypes.AllowedTypes) { } @@ -96,7 +112,12 @@ public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRe /// /// A delegate that determines what types are allowed. public ObjectSerializer(Func allowedTypes) - : this(BsonSerializer.LookupDiscriminatorConvention(typeof(object)), allowedTypes) + : this(BsonSerializationDomain.Default, allowedTypes) + { + } + + internal ObjectSerializer(IBsonSerializationDomain serializationDomain, Func allowedTypes) + : this(serializationDomain, serializationDomain.LookupDiscriminatorConvention(typeof(object)), allowedTypes) { } @@ -106,7 +127,12 @@ public ObjectSerializer(Func allowedTypes) /// The discriminator convention. /// A delegate that determines what types are allowed. public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, Func allowedTypes) - : this(discriminatorConvention, GuidRepresentation.Unspecified, allowedTypes) + : this(BsonSerializationDomain.Default, discriminatorConvention, allowedTypes) + { + } + + internal ObjectSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention, Func allowedTypes) + : this(serializationDomain, discriminatorConvention, GuidRepresentation.Unspecified, allowedTypes) { } @@ -117,7 +143,12 @@ public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, FuncThe Guid representation. /// A delegate that determines what types are allowed. public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation, Func allowedTypes) - : this(discriminatorConvention, guidRepresentation, allowedTypes ?? throw new ArgumentNullException(nameof(allowedTypes)), allowedTypes) + : this(BsonSerializationDomain.Default, discriminatorConvention, guidRepresentation, allowedTypes) + { + } + + internal ObjectSerializer(IBsonSerializationDomain serializationDomain, IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation, Func allowedTypes) + : this(serializationDomain, discriminatorConvention, guidRepresentation, allowedTypes ?? throw new ArgumentNullException(nameof(allowedTypes)), allowedTypes) { } @@ -133,7 +164,18 @@ public ObjectSerializer( GuidRepresentation guidRepresentation, Func allowedDeserializationTypes, Func allowedSerializationTypes) + : this(BsonSerializationDomain.Default, discriminatorConvention, guidRepresentation, allowedDeserializationTypes, allowedSerializationTypes) { + } + + internal ObjectSerializer( + IBsonSerializationDomain serializationDomain, + IDiscriminatorConvention discriminatorConvention, + GuidRepresentation guidRepresentation, + Func allowedDeserializationTypes, + Func allowedSerializationTypes) + { + _serializationDomain = serializationDomain ?? throw new ArgumentNullException(nameof(serializationDomain)); _discriminatorConvention = discriminatorConvention ?? throw new ArgumentNullException(nameof(discriminatorConvention)); _guidRepresentation = guidRepresentation; _guidSerializer = new GuidSerializer(_guidRepresentation); @@ -162,6 +204,8 @@ public ObjectSerializer( /// public GuidRepresentation GuidRepresentation => _guidRepresentation; + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Deserializes a value. @@ -389,7 +433,7 @@ private object DeserializeDiscriminatedValue(BsonDeserializationContext context, } else { - var serializer = BsonSerializer.LookupSerializer(actualType); + var serializer = _serializationDomain.LookupSerializer(actualType); var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer) { @@ -438,7 +482,7 @@ private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonS throw new BsonSerializationException($"Type {actualType.FullName} is not configured as a type that is allowed to be serialized for this instance of ObjectSerializer."); } - var serializer = BsonSerializer.LookupSerializer(actualType); + var serializer = _serializationDomain.LookupSerializer(actualType); var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer) diff --git a/src/MongoDB.Bson/Serialization/Serializers/SerializeAsNominalTypeSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/SerializeAsNominalTypeSerializer.cs index 065ae3873f9..82bed892e68 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/SerializeAsNominalTypeSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/SerializeAsNominalTypeSerializer.cs @@ -32,7 +32,7 @@ public sealed class SerializeAsNominalTypeSerializer /// Initializes a new instance of the class. /// public SerializeAsNominalTypeSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/ThreeDimensionalArraySerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/ThreeDimensionalArraySerializer.cs index c2aaf3998fb..6f27efb02b1 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ThreeDimensionalArraySerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ThreeDimensionalArraySerializer.cs @@ -34,7 +34,7 @@ public sealed class ThreeDimensionalArraySerializer : /// Initializes a new instance of the class. /// public ThreeDimensionalArraySerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/TupleSerializers.cs b/src/MongoDB.Bson/Serialization/Serializers/TupleSerializers.cs index a5b1e5f290a..39608ba4be0 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/TupleSerializers.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/TupleSerializers.cs @@ -114,7 +114,7 @@ public sealed class TupleSerializer : SealedClassSerializerBase>, /// Initializes a new instance of the class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -208,7 +208,7 @@ public sealed class TupleSerializer : SealedClassSerializerBase class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -318,7 +318,7 @@ public sealed class TupleSerializer : SealedClassSerializerBase class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -443,7 +443,7 @@ public sealed class TupleSerializer : SealedClassSerializerBase< /// Initializes a new instance of the class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -585,7 +585,7 @@ public sealed class TupleSerializer : SealedClassSerializerB /// Initializes a new instance of the class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -743,7 +743,7 @@ public sealed class TupleSerializer : SealedClassSeriali /// Initializes a new instance of the class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -917,7 +917,7 @@ public sealed class TupleSerializer : SealedClassSer /// Initializes a new instance of the class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -1107,7 +1107,7 @@ public sealed class TupleSerializer : SealedC /// Initializes a new instance of the class. /// public TupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/TwoDimensionalArraySerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/TwoDimensionalArraySerializer.cs index afe701dd1c3..cba4a10b642 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/TwoDimensionalArraySerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/TwoDimensionalArraySerializer.cs @@ -34,7 +34,7 @@ public sealed class TwoDimensionalArraySerializer : /// Initializes a new instance of the class. /// public TwoDimensionalArraySerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/UndiscriminatedActualTypeSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/UndiscriminatedActualTypeSerializer.cs index 1587a979163..175a9e34261 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/UndiscriminatedActualTypeSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/UndiscriminatedActualTypeSerializer.cs @@ -19,19 +19,27 @@ namespace MongoDB.Bson.Serialization.Serializers /// Represents a serializer for interfaces and base classes that delegates to the actual type interface without writing a discriminator. /// /// Type type of the value. - public sealed class UndiscriminatedActualTypeSerializer : SerializerBase + public sealed class UndiscriminatedActualTypeSerializer : SerializerBase, IHasSerializationDomain { // private static fields private static readonly UndiscriminatedActualTypeSerializer __instance = new UndiscriminatedActualTypeSerializer(); + private readonly IBsonSerializationDomain _serializationDomain; + // constructors /// /// Initializes a new instance of the class. /// public UndiscriminatedActualTypeSerializer() + : this(BsonSerializationDomain.Default) { } + internal UndiscriminatedActualTypeSerializer(IBsonSerializationDomain serializationDomain) + { + _serializationDomain = serializationDomain; + } + // public static properties /// /// Gets the instance. @@ -44,6 +52,8 @@ public static UndiscriminatedActualTypeSerializer Instance get { return __instance; } } + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + // public methods /// /// Serializes a value. @@ -62,7 +72,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati else { var actualType = value.GetType(); - var serializer = BsonSerializer.LookupSerializer(actualType); + var serializer = _serializationDomain.LookupSerializer(actualType); args.NominalType = actualType; serializer.Serialize(context, args, value); } diff --git a/src/MongoDB.Bson/Serialization/Serializers/ValueTupleSerializers.cs b/src/MongoDB.Bson/Serialization/Serializers/ValueTupleSerializers.cs index d0f52484e58..c8857afee0d 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ValueTupleSerializers.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ValueTupleSerializers.cs @@ -75,7 +75,7 @@ public sealed class ValueTupleSerializer : StructSerializerBase class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -193,7 +193,7 @@ public sealed class ValueTupleSerializer : StructSerializerBase class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -329,7 +329,7 @@ public sealed class ValueTupleSerializer : StructSerializerBase class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -483,7 +483,7 @@ public sealed class ValueTupleSerializer : StructSerializerBase< /// Initializes a new instance of the class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -655,7 +655,7 @@ public sealed class ValueTupleSerializer : StructSerializerB /// Initializes a new instance of the class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -845,7 +845,7 @@ public sealed class ValueTupleSerializer : StructSeriali /// Initializes a new instance of the class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -1053,7 +1053,7 @@ public sealed class ValueTupleSerializer : StructSer /// Initializes a new instance of the class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } @@ -1280,7 +1280,7 @@ public sealed class ValueTupleSerializer : St /// Initializes a new instance of the class. /// public ValueTupleSerializer() - : this(BsonSerializer.SerializerRegistry) + : this(BsonSerializationDomain.Default.SerializerRegistry) { } diff --git a/src/MongoDB.Driver/AggregateExpressionDefinition.cs b/src/MongoDB.Driver/AggregateExpressionDefinition.cs index 85ae3f37e6a..eb898f917f0 100644 --- a/src/MongoDB.Driver/AggregateExpressionDefinition.cs +++ b/src/MongoDB.Driver/AggregateExpressionDefinition.cs @@ -140,7 +140,7 @@ internal ExpressionAggregateExpressionDefinition( public override BsonValue Render(RenderArgs args) { var contextData = _contextData?.With("SerializerRegistry", args.SerializerRegistry); - return LinqProviderAdapter.TranslateExpressionToAggregateExpression(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions, contextData); + return LinqProviderAdapter.TranslateExpressionToAggregateExpression(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions, contextData); } } diff --git a/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs b/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs index cd4b5f696b6..b8f56380c3d 100644 --- a/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs @@ -139,7 +139,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati { if (_cachedSerializer.ValueType != actualType) { - _cachedSerializer = BsonSerializer.LookupSerializer(actualType); + _cachedSerializer = _documentSerializer.GetSerializerForDerivedType(actualType); } serializer = _cachedSerializer; } diff --git a/src/MongoDB.Driver/FieldDefinition.cs b/src/MongoDB.Driver/FieldDefinition.cs index 6a3f1319da6..9b850457aff 100644 --- a/src/MongoDB.Driver/FieldDefinition.cs +++ b/src/MongoDB.Driver/FieldDefinition.cs @@ -242,7 +242,7 @@ public LambdaExpression Expression /// public override RenderedFieldDefinition Render(RenderArgs args) { - return LinqProviderAdapter.TranslateExpressionToField(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions); + return LinqProviderAdapter.TranslateExpressionToField(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions); } } @@ -275,7 +275,7 @@ public Expression> Expression /// public override RenderedFieldDefinition Render(RenderArgs args) { - return LinqProviderAdapter.TranslateExpressionToField(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions, args.PathRenderArgs.AllowScalarValueForArray); + return LinqProviderAdapter.TranslateExpressionToField(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions, args.PathRenderArgs.AllowScalarValueForArray); } } diff --git a/src/MongoDB.Driver/FieldValueSerializerHelper.cs b/src/MongoDB.Driver/FieldValueSerializerHelper.cs index 68880f7fe18..26110109b03 100644 --- a/src/MongoDB.Driver/FieldValueSerializerHelper.cs +++ b/src/MongoDB.Driver/FieldValueSerializerHelper.cs @@ -20,6 +20,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Linq.Linq3Implementation.Serializers; using MongoDB.Driver.Support; namespace MongoDB.Driver @@ -51,7 +52,7 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer // serialize numeric values without converting them if (fieldType.IsNumeric() && valueType.IsNumeric()) { - var valueSerializer = BsonSerializer.SerializerRegistry.GetSerializer(valueType); + var valueSerializer = StandardSerializers.GetSerializer(valueType); if (HasStringRepresentation(fieldSerializer)) { valueSerializer = WithStringRepresentation(valueSerializer); diff --git a/src/MongoDB.Driver/FilterDefinition.cs b/src/MongoDB.Driver/FilterDefinition.cs index a7e727110af..12e03969c72 100644 --- a/src/MongoDB.Driver/FilterDefinition.cs +++ b/src/MongoDB.Driver/FilterDefinition.cs @@ -205,11 +205,11 @@ public override BsonDocument Render(RenderArgs args) { if (args.RenderForElemMatch) { - return LinqProviderAdapter.TranslateExpressionToElemMatchFilter(_expression, elementSerializer: args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions); + return LinqProviderAdapter.TranslateExpressionToElemMatchFilter(_expression, elementSerializer: args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions); } else { - return LinqProviderAdapter.TranslateExpressionToFilter(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions); + return LinqProviderAdapter.TranslateExpressionToFilter(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions); } } } diff --git a/src/MongoDB.Driver/FindFluent.cs b/src/MongoDB.Driver/FindFluent.cs index 939aec7da71..a1830d4ec23 100644 --- a/src/MongoDB.Driver/FindFluent.cs +++ b/src/MongoDB.Driver/FindFluent.cs @@ -284,7 +284,7 @@ private TRendered Render(Func, TRendered> rende { var args = new RenderArgs( _collection.DocumentSerializer, - _collection.Settings.SerializerRegistry, + _collection.Settings.SerializationDomain, renderForFind: renderForFind, translationOptions: translationOptions); diff --git a/src/MongoDB.Driver/GridFS/GridFSFileInfoSerializer.cs b/src/MongoDB.Driver/GridFS/GridFSFileInfoSerializer.cs index 4c91bb90283..38e6631c116 100644 --- a/src/MongoDB.Driver/GridFS/GridFSFileInfoSerializer.cs +++ b/src/MongoDB.Driver/GridFS/GridFSFileInfoSerializer.cs @@ -31,7 +31,7 @@ public class GridFSFileInfoSerializer : BsonDocumentBackedClassSerializ /// Initializes a new instance of the class. /// public GridFSFileInfoSerializer() - : this(BsonSerializer.LookupSerializer()) + : this(BsonSerializer.LookupSerializer()) //FP I think this should be fine. { } diff --git a/src/MongoDB.Driver/IInheritableMongoClientSettings.cs b/src/MongoDB.Driver/IInheritableMongoClientSettings.cs index 2e54fda9d4c..d08f48e3fd9 100644 --- a/src/MongoDB.Driver/IInheritableMongoClientSettings.cs +++ b/src/MongoDB.Driver/IInheritableMongoClientSettings.cs @@ -15,6 +15,7 @@ using System; using System.Text; +using MongoDB.Bson.Serialization; namespace MongoDB.Driver { @@ -24,6 +25,7 @@ internal interface IInheritableMongoClientSettings UTF8Encoding ReadEncoding { get; } ReadPreference ReadPreference { get; } TimeSpan? Timeout { get; } + IBsonSerializationDomain SerializationDomain { get; } WriteConcern WriteConcern { get; } UTF8Encoding WriteEncoding { get; } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs index 4d62eaea95c..acf6db25f89 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs @@ -42,9 +42,8 @@ public GroupingWithOutputExpressionStageDefinition(Expression Render(RenderArgs args) { var inputSerializer = args.DocumentSerializer; - var serializerRegistry = args.SerializerRegistry; - var groupingStage = RenderGroupingStage(inputSerializer, serializerRegistry, args.TranslationOptions, out var groupingSerializer); - var projectStage = RenderProjectStage(groupingSerializer, serializerRegistry, args.TranslationOptions, out var outputSerializer); + var groupingStage = RenderGroupingStage(args.SerializationDomain, inputSerializer, args.TranslationOptions, out var groupingSerializer); + var projectStage = RenderProjectStage(args.SerializationDomain, groupingSerializer, args.TranslationOptions, out var outputSerializer); var optimizedStages = OptimizeGroupingStages(groupingStage, projectStage, inputSerializer, outputSerializer); var renderedStages = optimizedStages.Select(x => x.Render().AsBsonDocument); @@ -52,19 +51,19 @@ public override RenderedPipelineStageDefinition Render(RenderArgs inputSerializer, - IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions, out IBsonSerializer groupingOutputSerializer); private AstStage RenderProjectStage( + IBsonSerializationDomain serializationDomain, IBsonSerializer inputSerializer, - IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions, out IBsonSerializer outputSerializer) { var partiallyEvaluatedOutput = (Expression>)PartialEvaluator.EvaluatePartially(_output); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var outputTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedOutput, inputSerializer, asRoot: true); var (projectStage, projectSerializer) = ProjectionHelper.CreateProjectStage(outputTranslation); outputSerializer = (IBsonSerializer)projectSerializer; @@ -99,14 +98,13 @@ public BucketWithOutputExpressionStageDefinition( public override string OperatorName => "$bucket"; - protected override AstStage RenderGroupingStage( + protected override AstStage RenderGroupingStage(IBsonSerializationDomain serializationDomain, IBsonSerializer inputSerializer, - IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions, out IBsonSerializer> groupingOutputSerializer) { var partiallyEvaluatedGroupBy = (Expression>)PartialEvaluator.EvaluatePartially(_groupBy); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true); var valueSerializer = (IBsonSerializer)groupByTranslation.Serializer; @@ -144,13 +142,13 @@ public BucketAutoWithOutputExpressionStageDefinition( public override string OperatorName => "$bucketAuto"; protected override AstStage RenderGroupingStage( + IBsonSerializationDomain serializationDomain, IBsonSerializer inputSerializer, - IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions, out IBsonSerializer, TInput>> groupingOutputSerializer) { var partiallyEvaluatedGroupBy = (Expression>)PartialEvaluator.EvaluatePartially(_groupBy); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true); var valueSerializer = (IBsonSerializer)groupByTranslation.Serializer; @@ -182,13 +180,13 @@ public GroupWithOutputExpressionStageDefinition( public override string OperatorName => "$group"; protected override AstStage RenderGroupingStage( + IBsonSerializationDomain serializationDomain, IBsonSerializer inputSerializer, - IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions, out IBsonSerializer> groupingOutputSerializer) { var partiallyEvaluatedGroupBy = (Expression>)PartialEvaluator.EvaluatePartially(_groupBy); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true); var pushElements = AstExpression.AccumulatorField("_elements", AstUnaryAccumulatorOperator.Push, AstExpression.RootVar); var groupBySerializer = (IBsonSerializer)groupByTranslation.Serializer; diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs index ccb8f699740..4fd301aad53 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs @@ -212,10 +212,10 @@ public static bool IsEnumOrNullableEnum(this Type type, out Type enumType, out T type.IsNullableEnum(out enumType, out underlyingType); } - public static bool IsNullable(this Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } + // public static bool IsNullable(this Type type) + // { + // return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + // } public static bool IsNullable(this Type type, out Type valueType) { @@ -231,10 +231,10 @@ public static bool IsNullable(this Type type, out Type valueType) } } - public static bool IsNullableEnum(this Type type) - { - return type.IsNullable(out var valueType) && valueType.IsEnum; - } + // public static bool IsNullableEnum(this Type type) + // { + // return type.IsNullable(out var valueType) && valueType.IsEnum; + // } public static bool IsNullableEnum(this Type type, out Type enumType, out Type underlyingType) { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs index 2717a7e71d7..b873eee754b 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs @@ -57,13 +57,14 @@ protected MongoQueryProvider( public abstract ExpressionTranslationOptions GetTranslationOptions(); } - internal sealed class MongoQueryProvider : MongoQueryProvider + internal sealed class MongoQueryProvider : MongoQueryProvider, IHasSerializationDomain { // private fields private readonly IMongoCollection _collection; private readonly IMongoDatabase _database; private ExecutableQuery _executedQuery; private readonly IBsonSerializer _pipelineInputSerializer; + private readonly IBsonSerializationDomain _serializationDomain; // constructors public MongoQueryProvider( @@ -74,6 +75,7 @@ public MongoQueryProvider( { _collection = Ensure.IsNotNull(collection, nameof(collection)); _pipelineInputSerializer = collection.DocumentSerializer; + _serializationDomain = collection.Settings.SerializationDomain; } public MongoQueryProvider( @@ -84,14 +86,17 @@ public MongoQueryProvider( { _database = Ensure.IsNotNull(database, nameof(database)); _pipelineInputSerializer = NoPipelineInputSerializer.Instance; + _serializationDomain = _database.Settings.SerializationDomain; } internal MongoQueryProvider( + IBsonSerializationDomain serializationDomain, IBsonSerializer pipelineInputSerializer, IClientSessionHandle session, AggregateOptions options) : base(session, options) { + _serializationDomain = Ensure.IsNotNull(serializationDomain, nameof(serializationDomain)); _pipelineInputSerializer = Ensure.IsNotNull(pipelineInputSerializer, nameof(pipelineInputSerializer)); } @@ -101,6 +106,7 @@ internal MongoQueryProvider( public IMongoDatabase Database => _database; public override BsonDocument[] LoggedStages => _executedQuery?.LoggedStages; public override IBsonSerializer PipelineInputSerializer => _pipelineInputSerializer; + public IBsonSerializationDomain SerializationDomain => _serializationDomain; // public methods public override IQueryable CreateQuery(Expression expression) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberInitExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberInitExpressionToAggregationExpressionTranslator.cs index 20f7e81312c..2ccf395f7db 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberInitExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberInitExpressionToAggregationExpressionTranslator.cs @@ -47,7 +47,7 @@ public static TranslatedExpression Translate( var constructorInfo = newExpression.Constructor; // note: can be null when using the default constructor with a struct var constructorArguments = newExpression.Arguments; var computedFields = new List(); - var classMap = CreateClassMap(newExpression.Type, constructorInfo, out var creatorMap); + var classMap = CreateClassMap(context.SerializationDomain, newExpression.Type, constructorInfo, out var creatorMap); if (constructorInfo != null && creatorMap != null) { @@ -100,17 +100,18 @@ public static TranslatedExpression Translate( return new TranslatedExpression(expression, ast, serializer); } - private static BsonClassMap CreateClassMap(Type classType, ConstructorInfo constructorInfo, out BsonCreatorMap creatorMap) + private static BsonClassMap CreateClassMap(IBsonSerializationDomain serializationDomain, Type classType, ConstructorInfo constructorInfo, out BsonCreatorMap creatorMap) { BsonClassMap baseClassMap = null; if (classType.BaseType != null) { - baseClassMap = CreateClassMap(classType.BaseType, null, out _); + baseClassMap = CreateClassMap(serializationDomain, classType.BaseType, null, out _); } var classMapType = typeof(BsonClassMap<>).MakeGenericType(classType); - var classMapConstructorInfo = classMapType.GetConstructor(new Type[] { typeof(BsonClassMap) }); - var classMap = (BsonClassMap)classMapConstructorInfo.Invoke(new object[] { baseClassMap }); + var bindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var classMapConstructorInfo = classMapType.GetConstructor(bindingAttr, binder: null, types: [typeof(IBsonSerializationDomain), typeof(BsonClassMap)], modifiers: null); + var classMap = (BsonClassMap)classMapConstructorInfo.Invoke([serializationDomain, baseClassMap]); if (constructorInfo != null) { creatorMap = classMap.MapConstructor(constructorInfo); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExpressionToExecutableQueryTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExpressionToExecutableQueryTranslator.cs index b96a193e323..0fc5f2645cf 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExpressionToExecutableQueryTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExpressionToExecutableQueryTranslator.cs @@ -31,7 +31,7 @@ public static ExecutableQuery> Translate TranslateScalar(expression); var resultType = method.GetGenericArguments()[1]; - var outputSerializer = resultSerializer ?? BsonSerializer.LookupSerializer(resultType); + var outputSerializer = resultSerializer ?? context.SerializationDomain.LookupSerializer(resultType); pipeline = pipeline.WithNewOutputSerializer(outputSerializer); return pipeline; diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/ConcatMethodToPipelineTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/ConcatMethodToPipelineTranslator.cs index 03fb1ecb1b7..f06f27a9f12 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/ConcatMethodToPipelineTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/ConcatMethodToPipelineTranslator.cs @@ -44,7 +44,7 @@ secondProvider.CollectionNamespace is var secondCollectionNamespace && secondCollectionNamespace != null) { var secondCollectionName = secondCollectionNamespace.CollectionName; - var secondContext = TranslationContext.Create(context.TranslationOptions); + var secondContext = TranslationContext.Create(context.SerializationDomain, context.TranslationOptions); var secondPipeline = ExpressionToPipelineTranslator.Translate(secondContext, secondQueryable.Expression); if (secondPipeline.Ast.Stages.Count == 0) { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/DocumentsMethodToPipelineTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/DocumentsMethodToPipelineTranslator.cs index 6f7ae7a9a3f..c03f0a8725d 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/DocumentsMethodToPipelineTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/DocumentsMethodToPipelineTranslator.cs @@ -58,7 +58,7 @@ public static TranslatedPipeline Translate(TranslationContext context, MethodCal else { var documentType = method.GetGenericArguments()[0]; - documentSerializer = BsonSerializer.LookupSerializer(documentType); + documentSerializer = context.SerializationDomain.LookupSerializer(documentType); } var serializedDocuments = SerializationHelper.SerializeValues(documentSerializer, documents); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/LookupMethodToPipelineTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/LookupMethodToPipelineTranslator.cs index 93916b46c90..e8f8d222fc8 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/LookupMethodToPipelineTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/LookupMethodToPipelineTranslator.cs @@ -281,12 +281,12 @@ private static TranslatedPipeline TranslateDocumentsPipelineGeneric(documentSerializer, session: null, options: null); + var provider = new MongoQueryProvider(serializationDomain: context.SerializationDomain, pipelineInputSerializer: documentSerializer, session: null, options: null); var queryable = new MongoQuery(provider); body = ExpressionReplacer.Replace(body, queryableParameter, Expression.Constant(queryable)); @@ -332,7 +332,7 @@ private static TranslatedPipeline TranslateLookupPipelineAgainstQueryable throw new ExpressionNotSupportedException(expression, because: "OfType is not supported with the configured discriminator convention") }; - var resultSerializer = BsonSerializer.LookupSerializer(actualType); + var resultSerializer = pipeline.OutputSerializer.GetSerializerForDerivedType(actualType); if (wrappedValueOutputSerializer != null) { resultSerializer = WrappedValueSerializer.Create(wrappedValueOutputSerializer.FieldName, resultSerializer); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/UnionMethodToPipelineTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/UnionMethodToPipelineTranslator.cs index 6e2cd53e465..59f84fd2f8d 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/UnionMethodToPipelineTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/UnionMethodToPipelineTranslator.cs @@ -46,7 +46,7 @@ secondProvider.CollectionNamespace is var secondCollectionNamespace && secondCollectionNamespace != null) { var secondCollectionName = secondCollectionNamespace.CollectionName; - var secondContext = TranslationContext.Create(context.TranslationOptions); + var secondContext = TranslationContext.Create(context.SerializationDomain, context.TranslationOptions); var secondPipeline = ExpressionToPipelineTranslator.Translate(secondContext, secondQueryable.Expression); if (secondPipeline.Ast.Stages.Count == 0) { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/TranslationContext.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/TranslationContext.cs index b14afedd614..1af8717860b 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/TranslationContext.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/TranslationContext.cs @@ -21,31 +21,35 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Translators { - internal class TranslationContext + internal class TranslationContext : IHasSerializationDomain { #region static public static TranslationContext Create( + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions, TranslationContextData data = null) { var symbolTable = new SymbolTable(); var nameGenerator = new NameGenerator(); - return new TranslationContext(translationOptions, data, symbolTable, nameGenerator); + return new TranslationContext(serializationDomain, translationOptions, data, symbolTable, nameGenerator); } #endregion // private fields private readonly TranslationContextData _data; private readonly NameGenerator _nameGenerator; + private readonly IBsonSerializationDomain _serializationDomain; private readonly SymbolTable _symbolTable; private readonly ExpressionTranslationOptions _translationOptions; private TranslationContext( + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions, TranslationContextData data, SymbolTable symbolTable, NameGenerator nameGenerator) { + _serializationDomain = serializationDomain; _translationOptions = translationOptions ?? new ExpressionTranslationOptions(); _data = data; // can be null _symbolTable = Ensure.IsNotNull(symbolTable, nameof(symbolTable)); @@ -55,6 +59,7 @@ private TranslationContext( // public properties public TranslationContextData Data => _data; public NameGenerator NameGenerator => _nameGenerator; + public IBsonSerializationDomain SerializationDomain => _serializationDomain; public SymbolTable SymbolTable => _symbolTable; public ExpressionTranslationOptions TranslationOptions => _translationOptions; @@ -124,7 +129,7 @@ public TranslationContext WithSymbols(params Symbol[] newSymbols) public TranslationContext WithSymbolTable(SymbolTable symbolTable) { - return new TranslationContext(_translationOptions, _data, symbolTable, _nameGenerator); + return new TranslationContext(_serializationDomain, _translationOptions, _data, symbolTable, _nameGenerator); } } } diff --git a/src/MongoDB.Driver/Linq/LinqProviderAdapter.cs b/src/MongoDB.Driver/Linq/LinqProviderAdapter.cs index 67ca25b4261..df391b5efb3 100644 --- a/src/MongoDB.Driver/Linq/LinqProviderAdapter.cs +++ b/src/MongoDB.Driver/Linq/LinqProviderAdapter.cs @@ -56,12 +56,12 @@ internal static IQueryable AsQueryable( internal static BsonValue TranslateExpressionToAggregateExpression( Expression> expression, IBsonSerializer sourceSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions, TranslationContextData contextData = null) { expression = (Expression>)PartialEvaluator.EvaluatePartially(expression); - var context = TranslationContext.Create(translationOptions, contextData); + var context = TranslationContext.Create(serializationDomain, translationOptions, contextData); var translation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, expression, sourceSerializer, asRoot: true); var simplifiedAst = AstSimplifier.Simplify(translation.Ast); @@ -71,12 +71,12 @@ internal static BsonValue TranslateExpressionToAggregateExpression( LambdaExpression expression, IBsonSerializer documentSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions) { expression = (LambdaExpression)PartialEvaluator.EvaluatePartially(expression); var parameter = expression.Parameters.Single(); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var symbol = context.CreateSymbol(parameter, documentSerializer, isCurrent: true); context = context.WithSymbol(symbol); var body = RemovePossibleConvertToObject(expression.Body); @@ -100,20 +100,20 @@ static Expression RemovePossibleConvertToObject(Expression expression) internal static RenderedFieldDefinition TranslateExpressionToField( Expression> expression, IBsonSerializer documentSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions, bool allowScalarValueForArrayField) { expression = (Expression>)PartialEvaluator.EvaluatePartially(expression); var parameter = expression.Parameters.Single(); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var symbol = context.CreateSymbol(parameter, documentSerializer, isCurrent: true); context = context.WithSymbol(symbol); var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, expression.Body); var underlyingSerializer = fieldTranslation.Serializer; var fieldSerializer = underlyingSerializer as IBsonSerializer; - var valueSerializer = (IBsonSerializer)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField); + var valueSerializer = (IBsonSerializer)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializationDomain.SerializerRegistry, typeof(TField), allowScalarValueForArrayField); return new RenderedFieldDefinition(fieldTranslation.Ast.Path, fieldSerializer, valueSerializer, underlyingSerializer); } @@ -121,11 +121,11 @@ internal static RenderedFieldDefinition TranslateExpressionToField( Expression> expression, IBsonSerializer elementSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions) { expression = (Expression>)PartialEvaluator.EvaluatePartially(expression); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var parameter = expression.Parameters.Single(); var symbol = context.CreateSymbol(parameter, "@", elementSerializer); // @ represents the implied element context = context.WithSingleSymbol(symbol); // @ is the only symbol visible inside an $elemMatch @@ -138,11 +138,11 @@ internal static BsonDocument TranslateExpressionToElemMatchFilter( internal static BsonDocument TranslateExpressionToFilter( Expression> expression, IBsonSerializer documentSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions) { expression = (Expression>)PartialEvaluator.EvaluatePartially(expression); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var filter = ExpressionToFilterTranslator.TranslateLambda(context, expression, documentSerializer, asRoot: true); filter = AstSimplifier.SimplifyAndConvert(filter); @@ -152,22 +152,23 @@ internal static BsonDocument TranslateExpressionToFilter( internal static RenderedProjectionDefinition TranslateExpressionToFindProjection( Expression> expression, IBsonSerializer sourceSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions) - => TranslateExpressionToProjection(expression, sourceSerializer, translationOptions, forFind: true); + => TranslateExpressionToProjection(expression, sourceSerializer, translationOptions, forFind: true, serializationDomain); internal static RenderedProjectionDefinition TranslateExpressionToProjection( Expression> expression, IBsonSerializer inputSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions) - => TranslateExpressionToProjection(expression, inputSerializer, translationOptions, forFind: false); + => TranslateExpressionToProjection(expression, inputSerializer, translationOptions, forFind: false, serializationDomain); private static RenderedProjectionDefinition TranslateExpressionToProjection( Expression> expression, IBsonSerializer inputSerializer, ExpressionTranslationOptions translationOptions, - bool forFind) + bool forFind, + IBsonSerializationDomain serializationDomain) { if (expression.Parameters.Count == 1 && expression.Body == expression.Parameters[0]) { @@ -176,7 +177,7 @@ private static RenderedProjectionDefinition TranslateExpressionToProjec } expression = (Expression>)PartialEvaluator.EvaluatePartially(expression); - var context = TranslationContext.Create(translationOptions); + var context = TranslationContext.Create(serializationDomain, translationOptions); var simplifier = forFind ? new AstFindProjectionSimplifier() : new AstSimplifier(); try @@ -212,10 +213,10 @@ private static RenderedProjectionDefinition TranslateExpressionToProjec internal static BsonDocument TranslateExpressionToSetStage( Expression> expression, IBsonSerializer documentSerializer, - IBsonSerializerRegistry serializerRegistry, + IBsonSerializationDomain serializationDomain, ExpressionTranslationOptions translationOptions) { - var context = TranslationContext.Create(translationOptions); // do not partially evaluate expression + var context = TranslationContext.Create(serializationDomain, translationOptions); // do not partially evaluate expression var parameter = expression.Parameters.Single(); var symbol = context.CreateRootSymbol(parameter, documentSerializer); context = context.WithSymbol(symbol); diff --git a/src/MongoDB.Driver/MongoClient.cs b/src/MongoDB.Driver/MongoClient.cs index 4b70bfdfd16..d51a50234cb 100644 --- a/src/MongoDB.Driver/MongoClient.cs +++ b/src/MongoDB.Driver/MongoClient.cs @@ -532,7 +532,7 @@ private ListDatabasesOperation CreateListDatabasesOperation(ListDatabasesOptions { AuthorizedDatabases = options.AuthorizedDatabases, Comment = options.Comment, - Filter = options.Filter?.Render(new(BsonDocumentSerializer.Instance, BsonSerializer.SerializerRegistry, translationOptions: translationOptions)), + Filter = options.Filter?.Render(new(BsonDocumentSerializer.Instance, _settings.SerializationDomain, translationOptions: translationOptions)), NameOnly = options.NameOnly, RetryRequested = _settings.RetryReads }; @@ -616,8 +616,7 @@ private MessageEncoderSettings GetMessageEncoderSettings() private RenderArgs GetRenderArgs() { var translationOptions = Settings.TranslationOptions; - var serializerRegistry = BsonSerializer.SerializerRegistry; - return new RenderArgs(BsonDocumentSerializer.Instance, serializerRegistry, translationOptions: translationOptions); + return new RenderArgs(BsonDocumentSerializer.Instance, _settings.SerializationDomain, translationOptions: translationOptions); } private IClientSessionHandle StartSession(ClientSessionOptions options) diff --git a/src/MongoDB.Driver/MongoClientSettings.cs b/src/MongoDB.Driver/MongoClientSettings.cs index bd2ce52064e..7c1f91db88a 100644 --- a/src/MongoDB.Driver/MongoClientSettings.cs +++ b/src/MongoDB.Driver/MongoClientSettings.cs @@ -18,6 +18,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Text; +using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Compression; using MongoDB.Driver.Core.Configuration; using MongoDB.Driver.Core.Misc; @@ -65,6 +66,7 @@ public class MongoClientSettings : IEquatable, IInheritable private bool _retryReads; private bool _retryWrites; private ConnectionStringScheme _scheme; + private IBsonSerializationDomain _serializationDomain; private ServerApi _serverApi; private List _servers; private ServerMonitoringMode _serverMonitoringMode; @@ -117,6 +119,7 @@ public MongoClientSettings() _retryReads = true; _retryWrites = true; _scheme = ConnectionStringScheme.MongoDB; + _serializationDomain = BsonSerializer.DefaultSerializationDomain; _serverApi = null; _servers = new List { new MongoServerAddress("localhost") }; _serverMonitoringMode = ServerMonitoringMode.Auto; @@ -472,6 +475,18 @@ public ReadPreference ReadPreference } } + internal IBsonSerializationDomain SerializationDomain + { + get => _serializationDomain ?? BsonSerializer.DefaultSerializationDomain; + set + { + if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); } + _serializationDomain = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + IBsonSerializationDomain IInheritableMongoClientSettings.SerializationDomain => _serializationDomain; + /// /// Gets or sets the name of the replica set. /// @@ -944,6 +959,7 @@ public MongoClientSettings Clone() clone._retryReads = _retryReads; clone._retryWrites = _retryWrites; clone._scheme = _scheme; + clone._serializationDomain = _serializationDomain; clone._serverApi = _serverApi; clone._servers = new List(_servers); clone._serverMonitoringMode = _serverMonitoringMode; diff --git a/src/MongoDB.Driver/MongoCollectionImpl.cs b/src/MongoDB.Driver/MongoCollectionImpl.cs index 20b11c8c942..77ea2aded51 100644 --- a/src/MongoDB.Driver/MongoCollectionImpl.cs +++ b/src/MongoDB.Driver/MongoCollectionImpl.cs @@ -1307,13 +1307,13 @@ private IBsonSerializer GetItemSerializerForDistinctMany(RenderedF private RenderArgs GetRenderArgs() { var translationOptions = _database.Client.Settings.TranslationOptions; - return new RenderArgs(_documentSerializer, _settings.SerializerRegistry, translationOptions: translationOptions); + return new RenderArgs(_documentSerializer, _settings.SerializationDomain, translationOptions: translationOptions); } private RenderArgs GetRenderArgs(ExpressionTranslationOptions translationOptions) { translationOptions = translationOptions.AddMissingOptionsFrom(_database.Client.Settings.TranslationOptions); - return new RenderArgs(_documentSerializer, _settings.SerializerRegistry, translationOptions: translationOptions); + return new RenderArgs(_documentSerializer, _settings.SerializationDomain, translationOptions: translationOptions); } private IEnumerable RenderArrayFilters(IEnumerable arrayFilters) diff --git a/src/MongoDB.Driver/MongoCollectionSettings.cs b/src/MongoDB.Driver/MongoCollectionSettings.cs index 81691de6f09..7e2bba031f1 100644 --- a/src/MongoDB.Driver/MongoCollectionSettings.cs +++ b/src/MongoDB.Driver/MongoCollectionSettings.cs @@ -34,6 +34,7 @@ public class MongoCollectionSettings private TimeSpan? _timeout; private Setting _writeConcern; private Setting _writeEncoding; + private Setting _serializationDomain; // the following fields are set when Freeze is called private bool _isFrozen; @@ -116,9 +117,23 @@ public ReadPreference ReadPreference /// /// Gets the serializer registry. /// - public IBsonSerializerRegistry SerializerRegistry + public IBsonSerializerRegistry SerializerRegistry => SerializationDomain.SerializerRegistry; + + /// + /// //TODO + /// + internal IBsonSerializationDomain SerializationDomain { - get { return BsonSerializer.SerializerRegistry; } + get => _serializationDomain.Value ?? BsonSerializer.DefaultSerializationDomain; + set + { + if (_isFrozen) { throw new InvalidOperationException("MongoCollectionSettings is frozen."); } + if (value == null) + { + throw new ArgumentNullException("value"); + } + _serializationDomain.Value = value; + } } /// @@ -180,6 +195,7 @@ public virtual MongoCollectionSettings Clone() clone._timeout = _timeout; clone._writeConcern = _writeConcern.Clone(); clone._writeEncoding = _writeEncoding.Clone(); + clone._serializationDomain = _serializationDomain; //TODO .clone...? return clone; } @@ -319,6 +335,10 @@ internal void ApplyDefaultValues(MongoDatabaseSettings databaseSettings) { ReadPreference = databaseSettings.ReadPreference; } + if (!_serializationDomain.HasBeenSet) + { + SerializationDomain = databaseSettings.SerializationDomain; + } if (!_timeout.HasValue) { Timeout = databaseSettings.Timeout; diff --git a/src/MongoDB.Driver/MongoDB.Driver.csproj b/src/MongoDB.Driver/MongoDB.Driver.csproj index 34add76dfbe..734e55b8df9 100644 --- a/src/MongoDB.Driver/MongoDB.Driver.csproj +++ b/src/MongoDB.Driver/MongoDB.Driver.csproj @@ -13,11 +13,12 @@ true - - - - - + + + + + + diff --git a/src/MongoDB.Driver/MongoDatabaseSettings.cs b/src/MongoDB.Driver/MongoDatabaseSettings.cs index e88fe90ed6c..4fa0f0fd35e 100644 --- a/src/MongoDB.Driver/MongoDatabaseSettings.cs +++ b/src/MongoDB.Driver/MongoDatabaseSettings.cs @@ -33,6 +33,7 @@ public class MongoDatabaseSettings private TimeSpan? _timeout; private Setting _writeConcern; private Setting _writeEncoding; + private Setting _serializationDomain; // the following fields are set when Freeze is called private bool _isFrozen; @@ -101,9 +102,23 @@ public ReadPreference ReadPreference /// /// Gets the serializer registry. /// - public IBsonSerializerRegistry SerializerRegistry + public IBsonSerializerRegistry SerializerRegistry => SerializationDomain.SerializerRegistry; + + /// + /// //TODO + /// + internal IBsonSerializationDomain SerializationDomain { - get { return BsonSerializer.SerializerRegistry; } + get => _serializationDomain.Value ?? BsonSerializer.DefaultSerializationDomain; + set + { + if (_isFrozen) { throw new InvalidOperationException("MongoCollectionSettings is frozen."); } + if (value == null) + { + throw new ArgumentNullException("value"); + } + _serializationDomain.Value = value; + } } /// @@ -164,6 +179,7 @@ public MongoDatabaseSettings Clone() clone._timeout = _timeout; clone._writeConcern = _writeConcern.Clone(); clone._writeEncoding = _writeEncoding.Clone(); + clone._serializationDomain = _serializationDomain; return clone; } @@ -300,6 +316,10 @@ internal void ApplyDefaultValues(IInheritableMongoClientSettings clientSettings) { Timeout = clientSettings.Timeout; } + if (!_serializationDomain.HasBeenSet) + { + SerializationDomain = clientSettings.SerializationDomain; + } if (!_writeConcern.HasBeenSet) { WriteConcern = clientSettings.WriteConcern; diff --git a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs index 3ad6eca971a..1b3de33a4ae 100644 --- a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs @@ -1644,7 +1644,7 @@ public AppendedStagePipelineDefinition( public override RenderedPipelineDefinition Render(RenderArgs args) { var renderedPipeline = _pipeline.Render(args); - var renderedStage = _stage.Render(new(renderedPipeline.OutputSerializer, args.SerializerRegistry, translationOptions: args.TranslationOptions)); + var renderedStage = _stage.Render(new(renderedPipeline.OutputSerializer, args.SerializationDomain, translationOptions: args.TranslationOptions)); var documents = renderedPipeline.Documents.Concat(renderedStage.Documents); var outputSerializer = _outputSerializer ?? renderedStage.OutputSerializer; return new RenderedPipelineDefinition(documents, outputSerializer); @@ -1720,7 +1720,7 @@ public PrependedStagePipelineDefinition( public override RenderedPipelineDefinition Render(RenderArgs args) { var renderedStage = _stage.Render(args); - var renderedPipeline = _pipeline.Render(new(renderedStage.OutputSerializer, args.SerializerRegistry, translationOptions: args.TranslationOptions)); + var renderedPipeline = _pipeline.Render(new(renderedStage.OutputSerializer, args.SerializationDomain, translationOptions: args.TranslationOptions)); var documents = renderedStage.Documents.Concat(renderedPipeline.Documents); var outputSerializer = _outputSerializer ?? renderedPipeline.OutputSerializer; return new RenderedPipelineDefinition(documents, outputSerializer); diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs index b8c7ee834fb..3b0ba8cf8cb 100644 --- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs @@ -544,6 +544,7 @@ public static PipelineStageDefinition Facet( const string operatorName = "$facet"; var materializedFacets = facets.ToArray(); + var stage = new DelegatedPipelineStageDefinition( operatorName, args => @@ -556,7 +557,25 @@ public static PipelineStageDefinition Facet( facetsDocument.Add(facet.Name, renderedPipeline); } var document = new BsonDocument("$facet", facetsDocument); - var outputSerializer = options?.OutputSerializer ?? args.SerializerRegistry.GetSerializer(); + + IBsonSerializer outputSerializer; + + if (options?.OutputSerializer is not null) + { + outputSerializer = options.OutputSerializer; + } + else if (typeof(TOutput) == typeof(AggregateFacetResults)) + { + outputSerializer = (IBsonSerializer)new AggregateFacetResultsSerializer( + materializedFacets.Select(f => f.Name), + materializedFacets.Select(f => f.OutputSerializer ?? args.SerializerRegistry.GetSerializer(f.OutputType))); //QUESTION What do we do? Do we delay the setting of the serializer..? + } + else + { + outputSerializer = args.SerializerRegistry.GetSerializer(); + } + + //var outputSerializer = options?.OutputSerializer ?? args.SerializerRegistry.GetSerializer(); return new RenderedPipelineStageDefinition(operatorName, document, outputSerializer); }); @@ -572,12 +591,7 @@ public static PipelineStageDefinition Facet( public static PipelineStageDefinition Facet( IEnumerable> facets) { - Ensure.IsNotNull(facets, nameof(facets)); - var outputSerializer = new AggregateFacetResultsSerializer( - facets.Select(f => f.Name), - facets.Select(f => f.OutputSerializer ?? BsonSerializer.SerializerRegistry.GetSerializer(f.OutputType))); - var options = new AggregateFacetOptions { OutputSerializer = outputSerializer }; - return Facet(facets, options); + return Facet(facets, options: null); } /// @@ -2224,8 +2238,8 @@ public Expression> Expression } public override RenderedProjectionDefinition Render(RenderArgs args) => args.RenderForFind ? - LinqProviderAdapter.TranslateExpressionToFindProjection(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions) : - LinqProviderAdapter.TranslateExpressionToProjection(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions); + LinqProviderAdapter.TranslateExpressionToFindProjection(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions) : + LinqProviderAdapter.TranslateExpressionToProjection(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions); } internal class SortPipelineStageDefinition : PipelineStageDefinition diff --git a/src/MongoDB.Driver/ProjectionDefinition.cs b/src/MongoDB.Driver/ProjectionDefinition.cs index 6ae7985cf5b..99c58af8107 100644 --- a/src/MongoDB.Driver/ProjectionDefinition.cs +++ b/src/MongoDB.Driver/ProjectionDefinition.cs @@ -277,11 +277,11 @@ public override RenderedProjectionDefinition Render(RenderArgs /// The type of the document. - public record struct RenderArgs + public record struct RenderArgs : IHasSerializationDomain { private readonly IBsonSerializer _documentSerializer = default; private readonly PathRenderArgs _pathRenderArgs = default; @@ -38,7 +38,9 @@ public record struct RenderArgs private readonly bool _renderForFind = false; private readonly IBsonSerializerRegistry _serializerRegistry = default; private readonly ExpressionTranslationOptions _translationOptions = default; + private readonly IBsonSerializationDomain _serializationDomain = default; + //DOMAIN-API We need to stop using this constructor, and use the one with the serialization domain instead. /// /// Initializes a new instance of the record. /// @@ -65,6 +67,36 @@ public RenderArgs( _renderForFind = renderForFind; _renderForElemMatch = renderForElemMatch; _translationOptions = translationOptions; // can be null + _serializationDomain = BsonSerializer.DefaultSerializationDomain; + } + + /// + /// Initializes a new instance of the record. + /// + /// The document serializer. + /// The path render arguments. + /// Value that specifies whether full dollar for should be rendered. + /// Value that specifies whether rendering a find operation. + /// Value that specifies whether rendering an $elemMatch. + /// The translation options. + /// //TODO + internal RenderArgs( + IBsonSerializer documentSerializer, + IBsonSerializationDomain serializationDomain, + PathRenderArgs pathRenderArgs = default, + bool renderDollarForm = default, + bool renderForFind = false, + bool renderForElemMatch = false, + ExpressionTranslationOptions translationOptions = null) + { + DocumentSerializer = documentSerializer; + PathRenderArgs = pathRenderArgs; + RenderDollarForm = renderDollarForm; + _serializationDomain = serializationDomain ?? BsonSerializer.DefaultSerializationDomain; + _renderForFind = renderForFind; + _renderForElemMatch = renderForElemMatch; + _translationOptions = translationOptions; // can be null + SerializerRegistry = _serializationDomain.SerializerRegistry; } /// @@ -99,12 +131,20 @@ public readonly IBsonSerializer DocumentSerializer /// /// Gets the serializer registry. /// - public readonly IBsonSerializerRegistry SerializerRegistry + public readonly IBsonSerializerRegistry SerializerRegistry //TODO: we should probably remove this property { get => _serializerRegistry; init => _serializerRegistry = Ensure.IsNotNull(value, nameof(value)); } + internal readonly IBsonSerializationDomain SerializationDomain + { + get => _serializationDomain; + init => _serializationDomain = Ensure.IsNotNull(value, nameof(value)); + } + + IBsonSerializationDomain IHasSerializationDomain.SerializationDomain => _serializationDomain; + /// /// Gets the translation options used when translation Expressions to MQL. /// @@ -129,6 +169,6 @@ public readonly IBsonSerializer GetSerializer() => /// A new RenderArgs{TNewDocument} instance. /// public readonly RenderArgs WithNewDocumentType(IBsonSerializer serializer) => - new(serializer, _serializerRegistry, _pathRenderArgs, _renderDollarForm, _renderForFind, _renderForElemMatch, _translationOptions); + new(serializer, _serializationDomain, _pathRenderArgs, _renderDollarForm, _renderForFind, _renderForElemMatch, _translationOptions); } } diff --git a/src/MongoDB.Driver/SetFieldDefinitions.cs b/src/MongoDB.Driver/SetFieldDefinitions.cs index 76dcc1c1914..f660419bec3 100644 --- a/src/MongoDB.Driver/SetFieldDefinitions.cs +++ b/src/MongoDB.Driver/SetFieldDefinitions.cs @@ -93,7 +93,7 @@ public ExpressionSetFieldDefinitions(Expression> expres /// public override BsonDocument Render(RenderArgs args) { - var stage = LinqProviderAdapter.TranslateExpressionToSetStage(_expression, args.DocumentSerializer, args.SerializerRegistry, args.TranslationOptions); + var stage = LinqProviderAdapter.TranslateExpressionToSetStage(_expression, args.DocumentSerializer, args.SerializationDomain, args.TranslationOptions); return stage["$set"].AsBsonDocument; } } diff --git a/src/MongoDB.Driver/Support/ReflectionExtensions.cs b/src/MongoDB.Driver/Support/ReflectionExtensions.cs index 7bae4fa777e..b078332fa20 100644 --- a/src/MongoDB.Driver/Support/ReflectionExtensions.cs +++ b/src/MongoDB.Driver/Support/ReflectionExtensions.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Reflection; using MongoDB.Bson; +using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Support { @@ -62,20 +63,21 @@ public static bool IsBooleanOrNullableBoolean(this Type type) } } - public static bool IsNullable(this Type type) - { - return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - - public static bool IsNullableEnum(this Type type) - { - if (!IsNullable(type)) - { - return false; - } - - return GetNullableUnderlyingType(type).GetTypeInfo().IsEnum; - } + // Those have been commented out because there are identical methods in Bson assembly. + // public static bool IsNullable(this Type type) + // { + // return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + // } + // + // public static bool IsNullableEnum(this Type type) + // { + // if (!IsNullable(type)) + // { + // return false; + // } + // + // return GetNullableUnderlyingType(type).GetTypeInfo().IsEnum; + // } public static bool IsNumeric(this Type type) { @@ -120,7 +122,7 @@ public static bool IsConvertibleToEnum(this Type type) public static Type GetNullableUnderlyingType(this Type type) { - if (!IsNullable(type)) + if (!type.IsNullable()) { throw new ArgumentException("Type must be nullable.", "type"); } diff --git a/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentWrapperTests.cs b/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentWrapperTests.cs index a1f94fb3b41..12dc65583ec 100644 --- a/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentWrapperTests.cs +++ b/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentWrapperTests.cs @@ -305,12 +305,12 @@ public void TestCreateWithNominalTypeAndObject() { var c = CreateC(); - var wrapper = BsonDocumentWrapper.Create(typeof(C), c); + var wrapper = BsonDocumentWrapper.Create(typeof(C), value: c); Assert.Same(BsonSerializer.LookupSerializer(typeof(C)), wrapper.Serializer); Assert.Same(c, wrapper.Wrapped); Assert.Equal(false, wrapper.IsMaterialized); - wrapper = BsonDocumentWrapper.Create(typeof(C), null); + wrapper = BsonDocumentWrapper.Create(typeof(C), value: null); Assert.Same(BsonSerializer.LookupSerializer(typeof(C)), wrapper.Serializer); Assert.Same(null, wrapper.Wrapped); Assert.Equal(false, wrapper.IsMaterialized); diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DynamicDocumentBaseSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DynamicDocumentBaseSerializerTests.cs index 91c7e11d0aa..c635bd36ff7 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DynamicDocumentBaseSerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DynamicDocumentBaseSerializerTests.cs @@ -89,6 +89,16 @@ public void GetHashCode_should_return_zero() public class ConcreteDynamicDocumentBaseSerializer : DynamicDocumentBaseSerializer where T : class, IDynamicMetaObjectProvider { + public ConcreteDynamicDocumentBaseSerializer() + : this(BsonSerializationDomain.Default) + { + } + + internal ConcreteDynamicDocumentBaseSerializer(IBsonSerializationDomain serializationDomain) + : base(serializationDomain) + { + } + protected override void ConfigureDeserializationContext(BsonDeserializationContext.Builder builder) => throw new System.NotImplementedException(); protected override void ConfigureSerializationContext(BsonSerializationContext.Builder builder) => throw new System.NotImplementedException(); protected override T CreateDocument() => throw new System.NotImplementedException(); diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs index 6013fb571e3..327720b3ecf 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs @@ -462,7 +462,7 @@ public void constructor_with_discriminator_convention_and_guid_representation_an var discriminatorConvention = Mock.Of(); var guidRepresentation = GuidRepresentation.Standard; - var exception = Record.Exception(() => new ObjectSerializer(discriminatorConvention: null, guidRepresentation, allowedTypes: null)); + var exception = Record.Exception(() => new ObjectSerializer(discriminatorConvention: discriminatorConvention, guidRepresentation, allowedTypes: null)); var e = exception.Should().BeOfType().Subject; e.ParamName.Should().Be("allowedTypes"); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ModuloComparisonExpressionToFilterTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ModuloComparisonExpressionToFilterTranslatorTests.cs index 10f3f2a5d14..5dc22e46e4a 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ModuloComparisonExpressionToFilterTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ModuloComparisonExpressionToFilterTranslatorTests.cs @@ -182,8 +182,9 @@ private void Assert(AstFilter result, string path, BsonValue divisor, BsonValue private TranslationContext CreateContext(ParameterExpression parameter) { - var serializer = BsonSerializer.LookupSerializer(parameter.Type); - var context = TranslationContext.Create(translationOptions: null); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var serializer = serializationDomain.LookupSerializer(parameter.Type); + var context = TranslationContext.Create(serializationDomain, translationOptions: null); var symbol = context.CreateSymbol(parameter, serializer, isCurrent: true); return context.WithSymbol(symbol); } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/AggregateGroupTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/AggregateGroupTranslatorTests.cs index a8f7428079b..8ab35ae5672 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/AggregateGroupTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/AggregateGroupTranslatorTests.cs @@ -641,7 +641,8 @@ private ProjectedResult Group(Expression(Expression> expression, int { expression = (Expression>)PartialEvaluator.EvaluatePartially(expression); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; var parameter = expression.Parameters.Single(); - var serializer = BsonSerializer.LookupSerializer(); - var context = TranslationContext.Create(translationOptions: null); + var serializer = serializationDomain.LookupSerializer(); + var context = TranslationContext.Create(serializationDomain, translationOptions: null); var symbol = context.CreateSymbol(parameter, serializer, isCurrent: true); context = context.WithSymbol(symbol); var filterAst = ExpressionToFilterTranslator.Translate(context, expression.Body); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/PredicateTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/PredicateTranslatorTests.cs index 0869d70822e..c67621bd86c 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/PredicateTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationWithLinq2Tests/Translators/PredicateTranslatorTests.cs @@ -1152,9 +1152,10 @@ public List Assert(IMongoCollection collection, { filter = (Expression>)PartialEvaluator.EvaluatePartially(filter); - var serializer = BsonSerializer.SerializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var serializer = serializationDomain.SerializerRegistry.GetSerializer(); var parameter = filter.Parameters.Single(); - var context = TranslationContext.Create(translationOptions: null); + var context = TranslationContext.Create(serializationDomain: serializationDomain, translationOptions: null); var symbol = context.CreateSymbol(parameter, serializer, isCurrent: true); context = context.WithSymbol(symbol); var filterAst = ExpressionToFilterTranslator.Translate(context, filter.Body); diff --git a/tests/MongoDB.Driver.Tests/Linq/LinqProviderAdapterTests.cs b/tests/MongoDB.Driver.Tests/Linq/LinqProviderAdapterTests.cs index f6f0d15f647..ca49cf6a43a 100644 --- a/tests/MongoDB.Driver.Tests/Linq/LinqProviderAdapterTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/LinqProviderAdapterTests.cs @@ -20,7 +20,6 @@ using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Linq; using MongoDB.Driver.Linq.Linq3Implementation; -using MongoDB.Driver.Tests; using MongoDB.Driver.Tests.Linq.Linq3Implementation; using Moq; using Xunit; @@ -36,6 +35,9 @@ public void AsQueryable_should_return_expected_result() var session = Mock.Of(); var options = new AggregateOptions(); + var collectionSettings = new MongoCollectionSettings { SerializationDomain = BsonSerializationDomain.Default }; + Mock.Get(collection).SetupGet(x => x.Settings).Returns(collectionSettings); + var result = LinqProviderAdapter.AsQueryable(collection, session, options); var queryable = result.Should().BeOfType>().Subject; @@ -48,10 +50,10 @@ public void AsQueryable_should_return_expected_result() public void TranslateExpressionToAggregateExpression_should_return_expected_result() { Expression> expression = c => c.X; - var serializerRegistry = BsonSerializer.SerializerRegistry; - var sourceSerializer = serializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var sourceSerializer = serializationDomain.SerializerRegistry.GetSerializer(); - var result = LinqProviderAdapter.TranslateExpressionToAggregateExpression(expression, sourceSerializer, serializerRegistry, translationOptions: null); + var result = LinqProviderAdapter.TranslateExpressionToAggregateExpression(expression, sourceSerializer, serializationDomain, translationOptions: null); result.Should().Be("'$X'"); } @@ -60,10 +62,10 @@ public void TranslateExpressionToAggregateExpression_should_return_expected_resu public void TranslateExpressionToField_with_untyped_lambda_should_return_expected_result() { LambdaExpression expression = (Expression>)(c => c.X); - var serializerRegistry = BsonSerializer.SerializerRegistry; - var documentSerializer = serializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var documentSerializer = serializationDomain.SerializerRegistry.GetSerializer(); - var result = LinqProviderAdapter.TranslateExpressionToField(expression, documentSerializer, serializerRegistry, translationOptions: null); + var result = LinqProviderAdapter.TranslateExpressionToField(expression, documentSerializer, serializationDomain, translationOptions: null); result.FieldName.Should().Be("X"); result.FieldSerializer.Should().BeOfType(typeof(Int32Serializer)); @@ -73,10 +75,10 @@ public void TranslateExpressionToField_with_untyped_lambda_should_return_expecte public void TranslateExpressionToField_with_typed_lambda_should_return_expected_result() { Expression> expression = c => c.X; - var serializerRegistry = BsonSerializer.SerializerRegistry; - var documentSerializer = serializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var documentSerializer = serializationDomain.SerializerRegistry.GetSerializer(); - var result = LinqProviderAdapter.TranslateExpressionToField(expression, documentSerializer, serializerRegistry, translationOptions: null, allowScalarValueForArrayField: false); + var result = LinqProviderAdapter.TranslateExpressionToField(expression, documentSerializer, serializationDomain, translationOptions: null, allowScalarValueForArrayField: false); result.FieldName.Should().Be("X"); result.FieldSerializer.Should().BeOfType(typeof(Int32Serializer)); @@ -86,10 +88,10 @@ public void TranslateExpressionToField_with_typed_lambda_should_return_expected_ public void TranslateExpressionToFilter_should_return_expected_result() { Expression> expression = c => c.X == 0; - var serializerRegistry = BsonSerializer.SerializerRegistry; - var documentSerializer = serializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var documentSerializer = serializationDomain.SerializerRegistry.GetSerializer(); - var result = LinqProviderAdapter.TranslateExpressionToFilter(expression, documentSerializer, serializerRegistry, translationOptions: null); + var result = LinqProviderAdapter.TranslateExpressionToFilter(expression, documentSerializer, serializationDomain, translationOptions: null); result.Should().Be("{ X : 0 }"); } @@ -98,10 +100,10 @@ public void TranslateExpressionToFilter_should_return_expected_result() public void TranslateExpressionToFindProjection_should_return_expected_result() { Expression> expression = c => c.X; - var serializerRegistry = BsonSerializer.SerializerRegistry; - var documentSerializer = serializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var documentSerializer = serializationDomain.SerializerRegistry.GetSerializer(); - var result = LinqProviderAdapter.TranslateExpressionToFindProjection(expression, documentSerializer, serializerRegistry, translationOptions: null); + var result = LinqProviderAdapter.TranslateExpressionToFindProjection(expression, documentSerializer, serializationDomain, translationOptions: null); result.Document.Should().Be("{ X : 1, _id : 0 }"); result.ProjectionSerializer.ValueType.Should().Be(typeof(int)); @@ -114,10 +116,10 @@ public void TranslateExpressionToProjection_should_return_expected_result() void WithAnonymousOutputType(Expression> expression) { - var serializerRegistry = BsonSerializer.SerializerRegistry; - var inputSerializer = serializerRegistry.GetSerializer(); + var serializationDomain = BsonSerializer.DefaultSerializationDomain; + var inputSerializer = serializationDomain.SerializerRegistry.GetSerializer(); - var result = LinqProviderAdapter.TranslateExpressionToProjection(expression, inputSerializer, serializerRegistry, translationOptions: null); + var result = LinqProviderAdapter.TranslateExpressionToProjection(expression, inputSerializer, serializationDomain, translationOptions: null); result.Document.Should().Be("{ _id : '$_id', x : '$X' }"); result.ProjectionSerializer.ValueType.Should().Be(typeof(TOutput)); diff --git a/tests/MongoDB.Driver.Tests/MultipleRegistriesTests.cs b/tests/MongoDB.Driver.Tests/MultipleRegistriesTests.cs new file mode 100644 index 00000000000..0c9e5e0a77f --- /dev/null +++ b/tests/MongoDB.Driver.Tests/MultipleRegistriesTests.cs @@ -0,0 +1,357 @@ +/* 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.Linq; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Core.TestHelpers.XunitExtensions; +using MongoDB.Driver.Linq; +using Xunit; + +namespace MongoDB.Driver.Tests +{ + [Trait("Category", "Integration")] + public class MultipleRegistriesTests + { + [Fact] + public void TestSerialization() + { + RequireServer.Check(); + + // { + // var client = CreateClient(); + // var collection = GetTypedCollection(client); + // var bsonCollection = GetUntypedCollection(client); + // + // var person = new Person { Id = ObjectId.Parse("6797b56bf5495bf53aa3078f"), Name = "Mario", Age = 24 }; + // collection.InsertOne(person); + // + // var retrieved = bsonCollection.FindSync(FilterDefinition.Empty).ToList().Single(); + // var toString = retrieved.ToString(); + // + // var expectedVal = + // """{ "_id" : { "$oid" : "6797b56bf5495bf53aa3078f" }, "Name" : "Mario", "Age" : 24 }"""; + // Assert.Equal(expectedVal, toString); + // } + + //The first section demonstrates that the class maps are also separated + { + var customDomain = BsonSerializationDomain.CreateWithDefaultConfiguration("Test"); + customDomain.RegisterSerializer(new CustomStringSerializer()); + + var client = CreateClientWithDomain(customDomain); + var collection = GetTypedCollection(client); + var bsonCollection = GetUntypedCollection(client); + + var person = new Person { Id = ObjectId.Parse("6797b56bf5495bf53aa3078f"), Name = "Mario", Age = 24 }; + collection.InsertOne(person); + + var retrievedAsBson = bsonCollection.FindSync(FilterDefinition.Empty).ToList().Single(); + var toString = retrievedAsBson.ToString(); + + var expectedVal = + """{ "_id" : { "$oid" : "6797b56bf5495bf53aa3078f" }, "Name" : "Mariotest", "Age" : 24 }"""; + Assert.Equal(expectedVal, toString); + + var retrievedTyped = collection.FindSync(FilterDefinition.Empty).ToList().Single(); + Assert.Equal("Mario", retrievedTyped.Name); + } + } + + [Fact] + public void TestDeserialization() + { + RequireServer.Check(); + + { + var client = CreateClient(); + var collection = GetTypedCollection(client); + + var person = new Person1 { Id = ObjectId.Parse("6797b56bf5495bf53aa3078f"), Name = "Mariotest", Age = 24 }; + collection.InsertOne(person); + } + + { + var customDomain = BsonSerializationDomain.CreateWithDefaultConfiguration("Test"); + customDomain.RegisterSerializer(new CustomStringSerializer()); + + var client = CreateClientWithDomain(customDomain, dropCollection: false); + var collection = GetTypedCollection(client); + + var retrievedTyped = collection.FindSync(FilterDefinition.Empty).ToList().Single(); + Assert.Equal("Mario", retrievedTyped.Name); + } + } + + [Fact] + public void TestLinq() + { + RequireServer.Check(); + + var customDomain = BsonSerializationDomain.CreateWithDefaultConfiguration("Test"); + customDomain.RegisterSerializer(new CustomStringSerializer()); + + var client = CreateClientWithDomain(customDomain); + var collection = GetTypedCollection(client); + var untypedCollection = GetUntypedCollection(client); + + var person = new Person { Id = ObjectId.Parse("6797b56bf5495bf53aa3078f"), Name = "Mario", Age = 24 }; + collection.InsertOne(person); + + var retrievedAsBson = untypedCollection.FindSync(FilterDefinition.Empty).ToList().Single(); + var toString = retrievedAsBson.ToString(); + + var expectedVal = + """{ "_id" : { "$oid" : "6797b56bf5495bf53aa3078f" }, "Name" : "Mariotest", "Age" : 24 }"""; + Assert.Equal(expectedVal, toString); + + var retrievedTyped = collection.AsQueryable().Where(x => x.Name == "Mario").ToList(); //The string serializer is correctly serializing "Mario" to "Mariotest" + Assert.NotEmpty(retrievedTyped); + } + + [Fact] + public void TestConventions() + { + RequireServer.Check(); + + var customDomain = BsonSerializationDomain.CreateWithDefaultConfiguration("Test"); + + // Register an id generator convention that uses a custom ObjectIdGenerator + customDomain.RegisterIdGenerator(typeof(ObjectId), new CustomObjectIdGenerator()); + + //Register a convention to use lowercase for all fields on the Person class + var pack = new ConventionPack(); + pack.AddMemberMapConvention( + "LowerCaseElementName", + m => m.SetElementName(m.MemberName.ToLower())); + customDomain.ConventionRegistry.Register("myPack", pack, t => t == typeof(Person)); + + var client = CreateClientWithDomain(customDomain); + var collection = GetTypedCollection(client); + var untypedCollection = GetUntypedCollection(client); + + var person = new Person { Name = "Mario", Age = 24 }; //Id is not set, so the custom ObjectIdGenerator should be used + collection.InsertOne(person); + + var retrievedAsBson = untypedCollection.FindSync(FilterDefinition.Empty).ToList().Single(); + var toString = retrievedAsBson.ToString(); + + var expectedVal = + """{ "_id" : { "$oid" : "6797b56bf5495bf53aa3078f" }, "name" : "Mario", "age" : 24 }"""; + Assert.Equal(expectedVal, toString); + } + + [Fact] + public void TestDiscriminators() + { + RequireServer.Check(); + + var customDomain = BsonSerializationDomain.CreateWithDefaultConfiguration("Test"); + + customDomain.ClassMapRegistry.RegisterClassMap(cm => + { + cm.AutoMap(); + cm.SetIsRootClass(true); + }); + + customDomain.ClassMapRegistry.RegisterClassMap(cm => + { + cm.AutoMap(); + cm.SetDiscriminator("dp1"); + cm.MapMember( m => m.ExtraField1).SetSerializer(new CustomStringSerializer()); + }); + + customDomain.ClassMapRegistry.RegisterClassMap(cm => + { + cm.AutoMap(); + cm.SetDiscriminator("dp2"); + cm.MapMember( m => m.ExtraField2).SetSerializer(new CustomStringSerializer()); + }); + + var client = CreateClientWithDomain(customDomain); + var collection = GetTypedCollection(client); + + var bp1 = new DerivedPerson1 { Name = "Alice", Age = 30, ExtraField1 = "Field1" }; + var bp2 = new DerivedPerson2 { Name = "Bob", Age = 40, ExtraField2 = "Field2" }; + collection.InsertMany(new BasePerson[] { bp1, bp2 }); + + //Aggregate with OfType + var retrievedDerivedPerson1 = collection.Aggregate().OfType().Single(); + var retrievedDerivedPerson2 = collection.Aggregate().OfType().Single(); + + AssertDerivedPerson1(bp1, retrievedDerivedPerson1); + AssertDerivedPerson2(bp2, retrievedDerivedPerson2); + + //AppendStage with OfType + retrievedDerivedPerson1 = collection.AsQueryable().AppendStage(PipelineStageDefinitionBuilder.OfType()) + .OfType().Single(); + retrievedDerivedPerson2 = collection.AsQueryable().AppendStage(PipelineStageDefinitionBuilder.OfType()) + .OfType().Single(); + + AssertDerivedPerson1(bp1, retrievedDerivedPerson1); + AssertDerivedPerson2(bp2, retrievedDerivedPerson2); + + //LINQ with OfType + retrievedDerivedPerson1 = collection.AsQueryable().OfType().Single(); + retrievedDerivedPerson2 = collection.AsQueryable().OfType().Single(); + + AssertDerivedPerson1(bp1, retrievedDerivedPerson1); + AssertDerivedPerson2(bp2, retrievedDerivedPerson2); + + //Facet with OfType + + var pipeline1 = PipelineDefinition.Create( new [] { + PipelineStageDefinitionBuilder.OfType() }); + var facet1 = AggregateFacet.Create("facet1", pipeline1); + + var pipeline2 = PipelineDefinition.Create( new [] { + PipelineStageDefinitionBuilder.OfType() }); + var facet2 = AggregateFacet.Create("facet2", pipeline2); + + var result = collection.Aggregate().Facet(facet1, facet2).Single().Facets; + retrievedDerivedPerson1 = result[0].Output().Single(); + retrievedDerivedPerson2 = result[1].Output().Single(); + + AssertDerivedPerson1(bp1, retrievedDerivedPerson1); + AssertDerivedPerson2(bp2, retrievedDerivedPerson2); + + //Find with OfType + var retrievedBasePerson1 = collection.FindSync(Builders.Filter.OfType()).Single(); + var retrievedBasePerson2 = collection.FindSync(Builders.Filter.OfType()).Single(); + + AssertBasePerson(bp1, retrievedBasePerson1); + AssertBasePerson(bp2, retrievedBasePerson2); + + void AssertDerivedPerson1(DerivedPerson1 expected, DerivedPerson1 retrieved) + { + AssertBasePerson(expected, retrieved); + Assert.Equal(expected.ExtraField1, retrieved.ExtraField1); + } + + void AssertDerivedPerson2(DerivedPerson2 expected, DerivedPerson2 retrieved) + { + AssertBasePerson(expected, retrieved); + Assert.Equal(expected.ExtraField2, retrieved.ExtraField2); + } + + void AssertBasePerson(BasePerson expected, BasePerson retrieved) + { + Assert.Equal(expected.Id, retrieved.Id); + Assert.Equal(expected.Name, retrieved.Name); + Assert.Equal(expected.Age, retrieved.Age); + } + } + + private static IMongoCollection GetTypedCollection(IMongoClient client) => + client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName) + .GetCollection(DriverTestConfiguration.CollectionNamespace.CollectionName); + + private static IMongoCollection GetUntypedCollection(IMongoClient client) => + client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName) + .GetCollection(DriverTestConfiguration.CollectionNamespace.CollectionName); + + private static IMongoClient CreateClientWithDomain(IBsonSerializationDomain domain, bool dropCollection = true) + { + var client = DriverTestConfiguration.CreateMongoClient(c => c.SerializationDomain = domain); + if (dropCollection) + { + var db = client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName); + db.DropCollection(DriverTestConfiguration.CollectionNamespace.CollectionName); + } + return client; + } + + private static IMongoClient CreateClient() + { + var client = DriverTestConfiguration.CreateMongoClient(); + var db = client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName); + db.DropCollection(DriverTestConfiguration.CollectionNamespace.CollectionName); + return client; + } + + public class Person + { + [BsonId] public ObjectId Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + } + + public class Person1 + { + [BsonId] public ObjectId Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + } + + public class BasePerson + { + [BsonId] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + public string Name { get; set; } + public int Age { get; set; } + } + + public class DerivedPerson1 : BasePerson + { + public string ExtraField1 { get; set; } + } + + public class DerivedPerson2 : BasePerson + { + public string ExtraField2 { get; set; } + } + + + public class CustomStringSerializer : SealedClassSerializerBase //This serializer just adds "test" to any serialised string + { + /// + public override int GetHashCode() => 0; + + protected override string DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) + { + var bsonReader = context.Reader; + + var bsonType = bsonReader.GetCurrentBsonType(); + return bsonType switch + { + BsonType.String => bsonReader.ReadString().Replace("test", ""), + _ => throw CreateCannotDeserializeFromBsonTypeException(bsonType) + }; + } + + protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, + string value) + { + var bsonWriter = context.Writer; + bsonWriter.WriteString(value + "test"); + } + } + + public class CustomObjectIdGenerator : IIdGenerator + { + public object GenerateId(object container, object document) + { + return ObjectId.Parse("6797b56bf5495bf53aa3078f"); + } + + public bool IsEmpty(object id) + { + return true; + } + } + } +}