北川广海の梦

北川广海の梦

.NET Core MongoDB 自定义反序列化器

525
2021-04-19

问题场景

最近在做地理相关的项目,采用了MongoDB作为空间数据存储引擎。通过 DotNet Driver来进行交互。项目中对空间实体进行了类定义。包含一个GeoEntity基类,已经几种衍生类型,例如点、线、面等。设计采用了以基类作为数据接口的方式,即所有暴露出去交互的API都通过基类GeoEnitty进行交互。这在进行数据插入的时候是没有问题的,MongoDB 在进行对象解析的时候,能够解析出对象的所有属性,而不会出现类似Json解析时的子类转换为父类属性丢失的问题。
但是在查询数据的时候,出现了问题。

这是存储在MongoDB的一个Point数据
image.png

这是用于接收数据的实体类
image.png

可以看到由于实体类当中缺少了Coordinates属性,会导致无法赋值。

许多人给出的方案是在MongoGeoEntity添加【IgnoreExtralElement】注解。但是如果添加了这个注解,Coordninates数据不就丢失了吗。

有人说根据Type来选择接收的子类不就行了吗?
MongoDB只查寻某个字段并不方便,并且对于整体效率不高。写的代码也很不优雅,通用性不强。一旦子类数量继续扩充,许多地方都要更改。

经过思考,MongoDB查询数据库,一定是有一个将二进制数据反序列化为DotNet类型的过程,在这个反序列化过程中,如果能够自定义反序列化器,那么就可以提前读出对应子类的类型,做到灵活的类型转换与赋值,并且不用通过反射解析类型,提高性能。

自定义序列化器

经过查阅文档,MongoDB序列化器只需要继承SerializerBase类即可,其中的泛型参数T代表进行解析的目标类。
我只需要实现反序列化,那么就只重写Deserialize方法即可。
image.png
读取字段的方式比较特殊,通过指定的Reader来进行读取,所以需要手动一行一行的写读取的代码。
重点说一下对对象和数组的读取:

对象读取

可以看到,我的数据中还嵌套了一个对象,在Reader中其对应的类型是Document,需要通过ReadStartDocument和ReadEndDocument来进行开始和结束读取。并且最外层的document也是这样进行的。
image.png

然后就是针对每一个值的读取
image.png
主要方式就是根据Reader.CurrentBsonType来选择对应的类型来读。

读取Document需要以递归的方式读,在GetValue方法中,如果type是Document,就调用ReadDocument方法就行了。
image.png

数组读取

然后是数组的读取,也要注意递归,因为数组中可能嵌套数组。在GetValue方法中,如果type是Array,就调用ReadArray方法就行了。
image.png

应用自定义反序列化器

只需要在需要使用序列化器的目标类上加这样一个注解即可
image.png

注意事项

在通过Reader读取数据的时候,需要注意Reader的状态,特别是循环读取对象或者数组的时候,Reader的State会先处于Type,然后Name,然后是Value,如果对象或者数组读取完毕之后,State在Type之后会变为EndOfArray或者EndOfDocument 此时就应该退出循环,并且调用ReadEndArray或者ReadEndDocument了。