Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/MongoDB.Bson/Serialization/BsonClassMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling GetOrRegisterDiscriminatorConvention instead of RegsiterDiscriminatorConvention is in order to avoid collisions between threads.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it avoid collisions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new GetOrRegisterDiscriminatorConvention method uses the same lock as RegsiterDiscriminatorConvention to handle thread safety.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does GetOrRegisterDiscriminatorConvention returns the value we are looking for? Can we return it straight away without additional lookup below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling LookupDiscriminatorConvention below has some side effects that we don't want to skip.

The main side effect is that it registers the convention at all levels between the _classType and where the discriminator convention is found.

break;
}

classMap = classMap._baseClassMap;
}

return BsonSerializer.LookupDiscriminatorConvention(_classType);
}
}

// private methods
Expand Down
45 changes: 45 additions & 0 deletions src/MongoDB.Bson/Serialization/BsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,51 @@ public static object Deserialize(TextReader textReader, Type nominalType, Action
}
}

internal static IDiscriminatorConvention GetOrRegisterDiscriminatorConvention(Type type, IDiscriminatorConvention discriminatorConvention)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't need to be public.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to have slightly different method signature to mimic ConcurrentDictionary.GetOrAdd method signature:

internal static IDiscriminatorConvention GetOrAddDiscriminatorConvention(Type type, Func<Type, IDiscriminatorConvention> discriminatorConventionFactory)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
__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();
}
}

/// <summary>
/// Returns whether the given type has any discriminators registered for any of its subclasses.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<BaseNoRoot> GetCollectionOfBaseNoRoot()
{
var collection = GetCollection<BaseNoRoot>("test");
CreateCollection(
collection,
new DerivedNoRoot { Id = 1 });
return collection;
}

private IMongoCollection<BaseWithRoot> GetCollectionOfBaseWithRoot()
{
var collection = GetCollection<BaseWithRoot>("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
{
}
}
}