diff --git a/src/MongoDB.Bson/Serialization/BsonClassMap.cs b/src/MongoDB.Bson/Serialization/BsonClassMap.cs index 7fe7e0c6b98..87239a33bf5 100644 --- a/src/MongoDB.Bson/Serialization/BsonClassMap.cs +++ b/src/MongoDB.Bson/Serialization/BsonClassMap.cs @@ -1323,11 +1323,40 @@ internal IDiscriminatorConvention GetDiscriminatorConvention() var discriminatorConvention = _discriminatorConvention; if (discriminatorConvention == null) { - // it's possible but harmless for multiple threads to do the field initialization at the same time - discriminatorConvention = _hasRootClass ? StandardDiscriminatorConvention.Hierarchical : StandardDiscriminatorConvention.Scalar; + // it's possible but harmless for multiple threads to do the discriminator convention lookukp at the same time + discriminatorConvention = LookupDiscriminatorConvention(); _discriminatorConvention = discriminatorConvention; } return discriminatorConvention; + + IDiscriminatorConvention LookupDiscriminatorConvention() + { + var classMap = this; + while (classMap != null) + { + if (classMap._discriminatorConvention != null) + { + return classMap._discriminatorConvention; + } + + if (BsonSerializer.IsDiscriminatorConventionRegisteredAtThisLevel(classMap._classType)) + { + // in this case LookupDiscriminatorConvention below will find it + break; + } + + 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); + break; + } + + classMap = classMap._baseClassMap; + } + + return BsonSerializer.LookupDiscriminatorConvention(_classType); + } } // private methods diff --git a/src/MongoDB.Bson/Serialization/BsonSerializer.cs b/src/MongoDB.Bson/Serialization/BsonSerializer.cs index 064565f6d64..b10676062f4 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializer.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializer.cs @@ -269,6 +269,51 @@ public static object Deserialize(TextReader textReader, Type nominalType, Action } } + 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(); + } + } + + internal static 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. /// diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5349Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5349Tests.cs new file mode 100644 index 00000000000..596d0724be0 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5349Tests.cs @@ -0,0 +1,93 @@ +/* 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 FluentAssertions; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Linq; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira +{ + public class CSharp5349Tests : Linq3IntegrationTest + { + static CSharp5349Tests() + { + BsonSerializer.RegisterDiscriminatorConvention(typeof(BaseNoRoot), new ScalarDiscriminatorConvention("__type")); + BsonSerializer.RegisterDiscriminatorConvention(typeof(BaseWithRoot), new HierarchicalDiscriminatorConvention("__type")); + } + + [Fact] + public void InsertOne_BaseNoRoot_should_use_the_configured_discriminator() + { + var collection = GetCollectionOfBaseNoRoot(); + + var documents = collection.AsQueryable().As(BsonDocumentSerializer.Instance).ToList(); + + documents.Single().Should().Be("{ _id : 1, __type : 'DerivedNoRoot' }"); + } + + [Fact] + public void InsertOne_BaseWithRoot_should_use_the_configured_discriminator() + { + var collection = GetCollectionOfBaseWithRoot(); + + var documents = collection.AsQueryable().As(BsonDocumentSerializer.Instance).ToList(); + + documents.Single().Should().Be("{ _id : 1, __type : ['BaseWithRoot', 'DerivedWithRoot'] }"); + } + + private IMongoCollection GetCollectionOfBaseNoRoot() + { + var collection = GetCollection("test"); + CreateCollection( + collection, + new DerivedNoRoot { Id = 1 }); + return collection; + } + + private IMongoCollection GetCollectionOfBaseWithRoot() + { + var collection = GetCollection("test"); + CreateCollection( + collection, + new DerivedWithRoot { Id = 1 }); + return collection; + } + + private class BaseNoRoot + { + public int Id { get; set; } + } + + private class DerivedNoRoot : BaseNoRoot + { + } + + [BsonDiscriminator(RootClass = true)] + [BsonKnownTypes(typeof(DerivedWithRoot))] + private class BaseWithRoot + { + public int Id { get; set; } + } + + private class DerivedWithRoot : BaseWithRoot + { + } + } +}