我终于找到了解决方案,它在原始解决方案 2 和 3 之间。
主要思想是,找到“客户端”数据结构中的所有结构,并为其注册特殊结构序列化器。挑战如下:
在“客户端”数据结构中查找所有结构类型
它必须递归查找,甚至结构是集合的一部分,它隐藏在集合中的类中,等等......所以我们必须在所有情况下都找到它。幸运的是,MongoDB 可以帮助找到所有实例,因为在序列化过程中,MongoDB 会对每种类型进行递归遍历。因此,我们注册了一个序列化提供程序,它可以“检测”所有结构并为它提供一个特殊的序列化程序。
检测给定类型是否为结构体
要完成这项工作,StackOverflow 上有很多答案,其中最需要的是完美的。也许我的解决方案也不完美,但我们将所有想法结合在一起。所以我们检查类型不是原始的,它不是枚举,而是值类型,而不是一些默认的结构,它在 MongoDB 中已经有一个序列化器。
代码如下:
1、为MongoDB注册一个serializer provider:
BsonSerializer.RegisterSerializationProvider( new MongoDB_SerializationProvider() );
2、实现一个序列化器:
class MongoDB_SerializationProvider : BsonSerializationProviderBase
{
private static readonly object locker = new object();
private static Dictionary<Type, MongoDB_StructSerializer> _StructSerializers;
private static MongoDB_DecimalSerializer _DecimalSerializer;
static MongoDB_SerializationProvider()
{
_StructSerializers = new Dictionary<Type, MongoDB_StructSerializer>();
_DecimalSerializer = new MongoDB_DecimalSerializer();
}
public override IBsonSerializer GetSerializer( Type type, IBsonSerializerRegistry serializerRegistry )
{
if ( type == typeof( decimal ) )
{
return _DecimalSerializer;
}
else if ( Reflection.Info.IsStruct( type ) && type != typeof( ObjectId ) )
{
MongoDB_StructSerializer structSerializer = null;
lock ( locker )
{
if ( _StructSerializers.TryGetValue( type, out structSerializer ) == false )
{
structSerializer = new MongoDB_StructSerializer( type );
_StructSerializers.Add( type, structSerializer );
}
}
return structSerializer;
}
else
{
return null;
}
}
}
小数部分是另一个有趣的主题,但它不是当前问题的一部分。我们必须注意的一件事:MongoDB ObjectId 也是一个结构,我们当然不想为 ObjectId-s 注册一个序列化程序。代码中有函数,有点神奇:Reflection.Info.IsStruct( type ) 这是它的代码:
public static bool IsStruct( Type type )
{
if ( IsPrimitiveType( type ) == true )
return false;
if ( type.IsValueType == false )
return false;
return true;
}
static public bool IsPrimitiveType( Type type )
{
if ( type.GetTypeInfo().IsPrimitive == true )
return true;
if ( type.GetTypeInfo().IsEnum == true )
return true;
if ( type == typeof( decimal ) )
return true;
if ( type == typeof( string ) )
return true;
if ( type == typeof( DateTime ) )
return true;
if ( type == typeof( DateTimeOffset ) )
return true;
if ( type == typeof( TimeSpan ) )
return true;
if ( type == typeof( Guid ) )
return true;
return false;
}
3、实现序列化器
代码有点长,但我希望它仍然可以理解:
public class MongoDB_StructSerializer : IBsonSerializer
{
public Type ValueType { get; }
public MongoDB_StructSerializer( Type valueType )
{
ValueType = valueType;
}
public void Serialize( BsonSerializationContext context, BsonSerializationArgs args, object value )
{
if ( value == null )
{
context.Writer.WriteNull();
}
else
{
List<MemberInfo> members = Reflection.Serialize.GetAllSerializableMembers( ValueType );
context.Writer.WriteStartDocument();
foreach( MemberInfo member in members )
{
context.Writer.WriteName( member.Name );
BsonSerializer.Serialize( context.Writer, Reflection.Info.GetMemberType( member ), Reflection.Info.GetMemberValue( member, value ), null, args );
}
context.Writer.WriteEndDocument();
}
}
public object Deserialize( BsonDeserializationContext context, BsonDeserializationArgs args )
{
BsonType bsonType = context.Reader.GetCurrentBsonType();
if ( bsonType == BsonType.Null )
{
context.Reader.ReadNull();
return null;
}
else
{
object obj = Activator.CreateInstance( ValueType );
context.Reader.ReadStartDocument();
while ( context.Reader.ReadBsonType() != BsonType.EndOfDocument )
{
string name = context.Reader.ReadName();
FieldInfo field = ValueType.GetField( name );
if ( field != null )
{
object value = BsonSerializer.Deserialize( context.Reader, field.FieldType );
field.SetValue( obj, value );
}
PropertyInfo prop = ValueType.GetProperty( name );
if ( prop != null )
{
object value = BsonSerializer.Deserialize( context.Reader, prop.PropertyType );
prop.SetValue( obj, value, null );
}
}
context.Reader.ReadEndDocument();
return obj;
}
}
}
神奇的功能:Reflection.Serialize.GetAllSerializableMembers 包含一些非常有趣的东西,什么是可序列化成员,什么不是。
public static List<MemberInfo> GetSerializableMembers( Type type, BindingFlags bindingFlags )
{
List<MemberInfo> list = new List<MemberInfo>();
FieldInfo[] fields = type.GetFields( bindingFlags );
foreach ( FieldInfo field in fields )
{
if ( IsFieldSerializable( type, field ) == false )
continue;
list.Add( field );
}
PropertyInfo[] properties = type.GetProperties( bindingFlags );
foreach ( PropertyInfo property in properties )
{
if ( IsPropertySerializable( type, property ) == false )
continue;
list.Add( property );
}
return list;
}
public static bool IsFieldSerializable( Type type, FieldInfo field )
{
if ( field.IsInitOnly == true )
return false;
if ( field.IsLiteral == true )
return false;
if ( field.IsDefined( typeof( CompilerGeneratedAttribute ), false ) == true )
return false;
if ( field.IsDefined( typeof( IgnoreAttribute ), false ) == true )
return false;
return true;
}
public static bool IsPropertySerializable( Type type, PropertyInfo property )
{
if ( property.CanRead == false )
return false;
if ( property.CanWrite == false )
return false;
if ( property.GetIndexParameters().Length != 0 )
return false;
if ( property.GetMethod.IsVirtual && property.GetMethod.GetBaseDefinition().DeclaringType != type )
return false;
if ( property.IsDefined( typeof( IgnoreAttribute ), false ) == true )
return false;
return true;
}
总结
此解决方案经过良好测试(大约 15-20 个不同的测试用例),并且运行良好。我认为 MongoDB 社区也能够实现结构序列化。他们很难过,因为结构是值类型,所以这就是为什么值被复制而不是引用的原因,所以当一个函数改变里面的值时,原来的值没有改变。但! MongoDB 内部的所有序列化代码都使用“对象”,结构也是对象。在驱动程序代码中没有任何地方,没有成员更改。仅在反序列化中,在我们的代码中被覆盖。
所以 MongoDB 社区可以做到,只要他们愿意! :)
附:比你看长帖,这里是Potato