最终我找到了解决方案,它位于原始解决方案2和3之间。
主要思想是,在“client”数据结构中查找所有的struct,并为其注册特殊的Struct序列化程序。以下是挑战:
查找“client”数据结构中的所有struct类型
必须进行递归查找,即使该结构是隐藏在集合中的类中的一部分,等等...因此我们必须在所有情况下找到它。幸运的是,MongoDB有助于查找所有实例,因为在序列化期间,MongoDB对每个类型进行递归遍历。因此,我们注册了一个序列化提供程序,它可以“检测”所有结构并为其提供特殊的序列化程序。
检测给定类型是否为struct
为了完成这项工作,在StackOverflow上有很多答案,但其中没有一个完美。也许我的解决方案也不完美,但我们将所有想法合并起来。因此,我们检查类型不是基元类型,不是枚举类型,而是值类型,并且不是某些已经在MongoDB中具有序列化程序的默认结构。
以下是代码:
1. 为MongoDB注册序列化程序提供程序:
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注册序列化程序。代码中有一个函数,它做了一些小魔术: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的所有序列化代码中都使用了'object',而结构体也是对象。并且在驱动程序代码中没有成员更改。只有在我们的代码中重写的反序列化中才有更改。
因此,如果他们想要,MongoDB社区也可以做到! :)
P.S. 感谢您阅读这篇长篇文章,这里有一个土豆
ValueType
,请参见什么使ValueType类特殊?和System.ValueType理解。2) 请参见如何以编程方式检查类型是结构体还是类?。但您可能需要排除“原始”值,例如,请参见如何告诉类型是否为“简单”类型?即只包含一个值。Int32
是一个结构体, 参见 https://referencesource.microsoft.com/#mscorlib/system/int32.cs,35:public struct Int32 : IComparable, IFormattable, IConvertible ...
- dbc