Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

CSHARP-3985: Support multiple SerializerRegistries #1592

Draft
wants to merge 51 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5a93d57
First test
papafe Jan 8, 2025
bc33071
Revert "First test"
papafe Jan 8, 2025
5e94e62
Added manager
papafe Jan 8, 2025
a317994
Delegating all the methods to the BsonSerializationManager
papafe Jan 8, 2025
0e2f7b9
Added interface
papafe Jan 8, 2025
a051d18
Small fix
papafe Jan 8, 2025
bb60887
Rename
papafe Jan 10, 2025
56a9b7e
Added internal interface
papafe Jan 10, 2025
3f634d5
Improvements
papafe Jan 13, 2025
550eb97
Initial use of domain
papafe Jan 13, 2025
d2e1746
Fix
papafe Jan 13, 2025
8b14426
Added comments
papafe Jan 13, 2025
3dadb07
Test with multiple interfaces
papafe Jan 15, 2025
cf05d41
Improved interfaces
papafe Jan 15, 2025
35f461a
Fix to interface
papafe Jan 15, 2025
7c2beb3
Small renaming
papafe Jan 15, 2025
3ad811e
Added comments
papafe Jan 27, 2025
0ae768c
Finished comments in the Bson lib
papafe Jan 27, 2025
01330fd
Corrected conventions
papafe Jan 27, 2025
7bec37f
Correction
papafe Jan 27, 2025
bb395b0
Corrected convention tests
papafe Jan 27, 2025
d7756c6
Removed multiple interfaces plus more fixes
papafe Jan 27, 2025
70f351b
More improvements
papafe Jan 27, 2025
cc40c72
Small fixes
papafe Jan 27, 2025
04d45fa
Small fixes
papafe Jan 27, 2025
8305dc1
improvements
papafe Jan 28, 2025
402a889
Small fix
papafe Jan 30, 2025
6d2faf9
Various fixes
papafe Jan 30, 2025
5ac0c06
First test passing
papafe Jan 30, 2025
7628f31
Small fixes
papafe Jan 30, 2025
3f5a047
Some saving
papafe Jan 30, 2025
9ccfe7a
Naming correction
papafe Feb 3, 2025
d3973e1
Added test and removed comment
papafe Feb 3, 2025
a587d6d
Improved tests
papafe Feb 3, 2025
856cfb8
Improved deserialization to work as serialization
papafe Feb 3, 2025
2cc6a8d
Trying to fix class map
papafe Feb 4, 2025
5742541
Fixed class
papafe Feb 4, 2025
d6d9ddc
Various corrections
papafe Feb 4, 2025
3b27b43
Moved class map to another file
papafe Feb 4, 2025
98682bb
Removed comments
papafe Feb 4, 2025
dc33112
Passing domain in serializer registry
papafe Feb 4, 2025
a45b027
Small improvements
papafe Feb 5, 2025
3f9c37c
Small corrections
papafe Feb 5, 2025
f0487d3
Renamin to serialization domain
papafe Feb 10, 2025
2ed6fb6
Small naming correction
papafe Feb 10, 2025
0074837
Fixed tests
papafe Feb 10, 2025
9011589
removed comment
papafe Feb 11, 2025
8b15445
Added convention registry
papafe Feb 12, 2025
2157ae8
Some improvements
papafe Feb 12, 2025
ab7c5de
Removed domain from translation context
papafe Feb 18, 2025
e683791
Made something internal
papafe Feb 18, 2025
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
Prev Previous commit
Next Next commit
Moved class map to another file
papafe committed Feb 4, 2025
commit 3b27b43d7963b92f4b3a6f738e314b9879ba999d
257 changes: 257 additions & 0 deletions src/MongoDB.Bson/Serialization/BsonClassMapDomain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace MongoDB.Bson.Serialization;

internal class BsonClassMapDomain : IBsonClassMapDomain
{
// private fields
private readonly Dictionary<Type, BsonClassMap> _classMaps = new();

/// <summary>
/// Gets all registered class maps.
/// </summary>
/// <returns>All registered class maps.</returns>
public IEnumerable<BsonClassMap> GetRegisteredClassMaps()
{
BsonSerializer.ConfigLock.EnterReadLock(); //TODO It would make sense to look at this after the PR by Robert is merged
try
{
return _classMaps.Values.ToList(); // return a copy for thread safety
}
finally
{
BsonSerializer.ConfigLock.ExitReadLock();
}
}

/// <summary>
/// Checks whether a class map is registered for a type.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns>True if there is a class map registered for the type.</returns>
public bool IsClassMapRegistered(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}

BsonSerializer.ConfigLock.EnterReadLock();
try
{
return _classMaps.ContainsKey(type);
}
finally
{
BsonSerializer.ConfigLock.ExitReadLock();
}
}

/// <summary>
/// Looks up a class map (will AutoMap the class if no class map is registered).
/// </summary>
/// <param name="classType">The class type.</param>
/// <returns>The class map.</returns>
public 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();
}
}

/// <summary>
/// Creates and registers a class map.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <returns>The class map.</returns>
public BsonClassMap<TClass> RegisterClassMap<TClass>()
{
return RegisterClassMap<TClass>(cm => { cm.AutoMap(); });
}

/// <summary>
/// Creates and registers a class map.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMapInitializer">The class map initializer.</param>
/// <returns>The class map.</returns>
public BsonClassMap<TClass> RegisterClassMap<TClass>(Action<BsonClassMap<TClass>> classMapInitializer)
{
var classMap = new BsonClassMap<TClass>(classMapInitializer);
RegisterClassMap(classMap);
return classMap;
}

/// <summary>
/// Registers a class map.
/// </summary>
/// <param name="classMap">The class map.</param>
public 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();
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>()
{
return TryRegisterClassMap(ClassMapFactory);

static BsonClassMap<TClass> ClassMapFactory()
{
var classMap = new BsonClassMap<TClass>();
classMap.AutoMap();
return classMap;
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMap">The class map.</param>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>(BsonClassMap<TClass> classMap)
{
if (classMap == null)
{
throw new ArgumentNullException(nameof(classMap));
}

return TryRegisterClassMap(ClassMapFactory);

BsonClassMap<TClass> ClassMapFactory()
{
return classMap;
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMapInitializer">The class map initializer (only called if the class map is not already registered).</param>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>(Action<BsonClassMap<TClass>> classMapInitializer)
{
if (classMapInitializer == null)
{
throw new ArgumentNullException(nameof(classMapInitializer));
}

return TryRegisterClassMap(ClassMapFactory);

BsonClassMap<TClass> ClassMapFactory()
{
return new BsonClassMap<TClass>(classMapInitializer);
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMapFactory">The class map factory (only called if the class map is not already registered).</param>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>(Func<BsonClassMap<TClass>> 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();
}
}
}
284 changes: 0 additions & 284 deletions src/MongoDB.Bson/Serialization/IBsonClassMapDomain.cs
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace MongoDB.Bson.Serialization
@@ -25,13 +24,6 @@ namespace MongoDB.Bson.Serialization
/// </summary>
public interface IBsonClassMapDomain
{
/// <summary>
/// Gets the type of a member.
/// </summary>
/// <param name="memberInfo">The member info.</param>
/// <returns>The type of the member.</returns>
Type GetMemberInfoType(MemberInfo memberInfo);

/// <summary>
/// Gets all registered class maps.
/// </summary>
@@ -104,280 +96,4 @@ public interface IBsonClassMapDomain
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
bool TryRegisterClassMap<TClass>(Func<BsonClassMap<TClass>> classMapFactory);
}

internal class BsonClassMapDomain : IBsonClassMapDomain
{
// private fields
private readonly Dictionary<Type, BsonClassMap> _classMaps = new();

/// <summary>
/// Gets the type of a member.
/// </summary>
/// <param name="memberInfo">The member info.</param>
/// <returns>The type of the member.</returns>
public 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.");
}

/// <summary>
/// Gets all registered class maps.
/// </summary>
/// <returns>All registered class maps.</returns>
public IEnumerable<BsonClassMap> GetRegisteredClassMaps()
{
BsonSerializer.ConfigLock.EnterReadLock(); //TODO It would make sense to look at this after the PR by Robert is merged
try
{
return _classMaps.Values.ToList(); // return a copy for thread safety
}
finally
{
BsonSerializer.ConfigLock.ExitReadLock();
}
}

/// <summary>
/// Checks whether a class map is registered for a type.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns>True if there is a class map registered for the type.</returns>
public bool IsClassMapRegistered(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}

BsonSerializer.ConfigLock.EnterReadLock();
try
{
return _classMaps.ContainsKey(type);
}
finally
{
BsonSerializer.ConfigLock.ExitReadLock();
}
}

/// <summary>
/// Looks up a class map (will AutoMap the class if no class map is registered).
/// </summary>
/// <param name="classType">The class type.</param>
/// <returns>The class map.</returns>
public 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();
}
}

/// <summary>
/// Creates and registers a class map.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <returns>The class map.</returns>
public BsonClassMap<TClass> RegisterClassMap<TClass>()
{
return RegisterClassMap<TClass>(cm => { cm.AutoMap(); });
}

/// <summary>
/// Creates and registers a class map.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMapInitializer">The class map initializer.</param>
/// <returns>The class map.</returns>
public BsonClassMap<TClass> RegisterClassMap<TClass>(Action<BsonClassMap<TClass>> classMapInitializer)
{
var classMap = new BsonClassMap<TClass>(classMapInitializer);
RegisterClassMap(classMap);
return classMap;
}

/// <summary>
/// Registers a class map.
/// </summary>
/// <param name="classMap">The class map.</param>
public 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();
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>()
{
return TryRegisterClassMap(ClassMapFactory);

static BsonClassMap<TClass> ClassMapFactory()
{
var classMap = new BsonClassMap<TClass>();
classMap.AutoMap();
return classMap;
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMap">The class map.</param>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>(BsonClassMap<TClass> classMap)
{
if (classMap == null)
{
throw new ArgumentNullException(nameof(classMap));
}

return TryRegisterClassMap(ClassMapFactory);

BsonClassMap<TClass> ClassMapFactory()
{
return classMap;
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMapInitializer">The class map initializer (only called if the class map is not already registered).</param>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>(Action<BsonClassMap<TClass>> classMapInitializer)
{
if (classMapInitializer == null)
{
throw new ArgumentNullException(nameof(classMapInitializer));
}

return TryRegisterClassMap(ClassMapFactory);

BsonClassMap<TClass> ClassMapFactory()
{
return new BsonClassMap<TClass>(classMapInitializer);
}
}

/// <summary>
/// Registers a class map if it is not already registered.
/// </summary>
/// <typeparam name="TClass">The class.</typeparam>
/// <param name="classMapFactory">The class map factory (only called if the class map is not already registered).</param>
/// <returns>True if this call registered the class map, false if the class map was already registered.</returns>
public bool TryRegisterClassMap<TClass>(Func<BsonClassMap<TClass>> 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();
}
}
}

}