在MongoDB中,无法进行结构(值类型)序列化和反序列化,因为MongoDB抛出异常:BsonClassMapSerializer.cs行:84。
但是我想一般解决这个问题。
我们要创建一个库,将其称为PolyglotPersistence.Lib。我的“客户”使用此库将其数据结构保存到数据库中,该数据库可以是MongoDB Azure CosomosDB,也可以是自己实现的MemoryDB和其他解决方案。
但是由于结构问题,MongoDB无法保存所有类型的数据结构。
我已经在Stackoverflow中找到了一些问题/答案,但是这些解决方案不是通用解决方案。
示例1 How do you serialize value types with MongoDB C# serializer? 这根本不是一般性的,在应用此解决方案时,我必须为每个结构创建一个Serialize / Deserializer。没关系,可以使用通用的StructSerializer <>完成,但是仍然“客户端”必须为所有结构注册它。这是不可接受的,因为他们不知道数据将在何处进行序列化(Cosmos / Mongo / Memory / etc ...)。
示例2 Serializing Immutable Value types with Mongo C# Driver 这几乎是相同的解决方案。必须通过“客户端”注册特殊的序列化器。
示例3 Deserialize a nested struct with MongoDB C# driver 他们换了课,这对我们来说不是一个好方法。
可能的解决方案1 我们创建了一条新规则:当“客户端”在其数据结构中使用结构时,他必须是特殊基类的继承者,比如说“ IStruct”。并且我们为这种类型注册了一个序列化器,问题就解决了。
但这对我们的客户来说有点不舒服,而不是防弹解决方案。
可能的解决方案2 当用户为我们的库添加新类型(PolyglotPersistence.Lib)时,我们必须递归遍历该类型,并检测其中是否有结构。找到它后,我们必须为该类型注册一个序列化器,而尚未注册。
但是对于此解决方案,我们必须在客户端数据结构中找到所有结构。
可能的解决方案3 注册一个序列化器以获取struct的基类型。我不知道它是否存在。但这将是最好的解决方案。 struct的最终基类:)
所以问题是:
谢谢大家的回答,请不要将此问题标记为重复,因为已经回答的解决方案不适合我们的问题。并且,请在标记问题之前阅读问题。 谢谢
P.S。所有评论将不胜感激:)
答案 0 :(得分:9)
最后我找到了解决方案,它在原始解决方案2和3之间。
主要思想是,在“客户端”数据结构中找到所有结构,并为其注册特殊结构串行器。挑战如下:
在“客户端”数据结构中找到所有结构类型
必须以递归方式找到它,即使结构是集合的一部分,也隐藏在集合中涵盖的类等中。...所以我们必须在所有情况下都找到它。幸运的是,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社区也能够实现struct序列化。他们感到遗憾的是无法完成,因为该结构是值类型,因此这就是为什么将值复制而不是引用的原因,因此,当一个函数更改内部的值时,原始值不会更改。但! MongoDB中所有使用'object'和structs的序列化代码也是对象。在驱动程序代码中的任何地方都没有成员更改。仅在反序列化时会被我们的代码覆盖。
P.S。比起您阅读长篇文章,这里是Potato
答案 1 :(得分:0)
补充GyörgyGulyás的答案:
如果您的模型具有可为空的类型,只需将以下内容添加到public static bool IsStruct( Type type )
中,作为方法的第一行:
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) type = Nullable.GetUnderlyingType(type);