当前位置: 首页 > news >正文

C# 统一处理mongodb中Protobuf中只读属性(RepeatedField和MapField)的序列化和反序列化映射

前言

众所周知,C#中的mongodb驱动默认是不会序列化和反序列只读属性的。所以当我们存储Protobuf的类型时,如果有属性是RepeatedField和MapField类型,那么该属性并不会被存储到mongodb数据库中。如果要正常存储,则需要自己调用RegisterClassMap方法注册该Protobuf类型的映射。类似下面的代码:

class Test
{public RepeatedField<int> RepeatedField { get;  }
}static Test CreateTest(RepeatedField<int> repeatedField)
{var res = new Test();res.RepeatedField.AddRange(repeatedField);return res;
}
static void RegisterTestClassMap()
{BsonClassMap.RegisterClassMap<Test>(cm =>{cm.AutoMap();// SetDefaultValue是为了防止Protobuf新加了属性,但是数据库的老数据没有该属性,导致反序列化失败的问题cm.MapProperty(x => x.RepeatedField).SetDefaultValue(new RepeatedField<int>());cm.MapCreator(p => CreateTest(p.RepeatedField));});
}

我们需要为每一个类型都注册类似的映射,非常的麻烦。而且如果protobuf的字段有新增,还需要改动代码,如果修改不及时还会导致线上的数据丢失。麻烦且不安全。

自动映射

我们自定义一个IClassMapConvention,自动生成并注册包含只读属性的类的构造函数的委托。
核心是利用表达式树,自动生成对应类的构造函数的委托。
生成的委托只支持protobuf里的RepeatedField和MapField类型。需要支持其他的需要自己修改。

    /// <summary>///  Protobuf的默认映射(映射只读属性及其默认值和构造函数)/// </summary>public class ProtobufDefaultMapConvention : ConventionBase, IClassMapConvention{private readonly BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;public ProtobufDefaultMapConvention() { }private string RepeatedFieldName = typeof(RepeatedField<>).Name;private string MapFieldName = typeof(MapField<,>).Name;/// <summary>/// 应用/// </summary>/// <param name="classMap"></param>public void Apply(BsonClassMap classMap){var readOnlyProperties = classMap.ClassType.GetTypeInfo().GetProperties(_bindingFlags).Where(p => IsReadOnlyProperty(classMap, p)).ToList();foreach (var property in readOnlyProperties){//<c cref="RepeatedField{T}">和<c cref="MapField{TKey, TValue}">和值类型所有对象共用同一个默认实例。要自己映射请覆盖默认值。// 必须设置默认值。否则会无法匹配构造函数if (property.PropertyType.Name == RepeatedFieldName || property.PropertyType.Name == MapFieldName || property.PropertyType.IsValueType){var defaultVal = Activator.CreateInstance(property.PropertyType);classMap.MapMember(property).SetDefaultValue(defaultVal);}else{classMap.MapMember(property).SetDefaultValue(() => Activator.CreateInstance(property.PropertyType));LogUtil.Warn($"ProtobufDefaultMapConvention->PB发现异常只读类型:{property.PropertyType.Name}");}}var res = GetProtobufCreatorFunc(classMap.ClassType, readOnlyProperties);classMap.MapCreator(res.Item1, res.Item2);}/// <summary>/// 获取protobuf的构造函数的委托(赋值只读属性)/// </summary>/// <param name="targetType"></param>/// <param name="readOnlyPropList"></param>/// <returns></returns>public (Delegate, string[]) GetProtobufCreatorFunc(Type targetType, List<PropertyInfo> readOnlyPropList){// 返回值var result = Expression.Variable(targetType, "result");// 初始化要创建的对象var initializeResult = Expression.Assign(result, Expression.New(targetType));var ctorParamList = new List<ParameterExpression>();// 构造函数func的方法体语句var ctorBlockExperssionList = new List<Expression>() { initializeResult };// 构造函数func的泛型类型列表var ctorFuncTypeList = new List<Type>(readOnlyPropList.Count + 1);// 构造函数func的参数名称列表var paramNames = new List<string>(readOnlyPropList.Count);foreach (var item in readOnlyPropList){ctorFuncTypeList.Add(item.PropertyType);paramNames.Add(item.Name);// 将需要赋值的只读属性设置为参数var ctorParam = Expression.Parameter(item.PropertyType, item.Name);ctorParamList.Add(ctorParam);// 取要赋值的只读属性var readOnlyMember = Expression.Property(result, item.Name);// 赋值语句Expression methodCall = null;if (item.PropertyType.Name == RepeatedFieldName){// 如果传入的参数不为null的话,则调用RepeatedField的AddRange方法//methodCall = Expression.IfThen(Expression.NotEqual(ctorParam, Expression.Constant(null)), Expression.Call(readOnlyMember, nameof(RepeatedField<int>.AddRange), null, ctorParam));// 不判null,如果要自己设置默认值请勿设置为nullmethodCall = Expression.Call(readOnlyMember, nameof(RepeatedField<int>.AddRange), null, ctorParam);}else if (item.PropertyType.Name == MapFieldName){// 如果传入的参数不为null的话,则调用MapField的Add方法//methodCall = Expression.IfThen(Expression.NotEqual(ctorParam, Expression.Constant(null)), Expression.Call(readOnlyMember, nameof(MapField<string, int>.Add), null, ctorParam));// 不判null,如果要自己设置默认值请勿设置为nullmethodCall = Expression.Call(readOnlyMember, nameof(MapField<string, int>.Add), null, ctorParam);}if (methodCall != null){ctorBlockExperssionList.Add(methodCall);}}ctorFuncTypeList.Add(targetType);ctorBlockExperssionList.Add(result);// 构造函数func的方法块var ctorFuncBody = Expression.Block(new[] { result }, ctorBlockExperssionList);// 构造函数func的Typevar ctorFuncType = Expression.GetFuncType(ctorFuncTypeList.ToArray());// 构造函数func的lambda表达式var ctorlambda = Expression.Lambda(ctorFuncType, ctorFuncBody, $"create{targetType.Name}", true, ctorParamList).Compile();return (ctorlambda, paramNames.ToArray());}/// <summary>/// 只读属性/// </summary>/// <param name="classMap"></param>/// <param name="propertyInfo"></param>/// <returns></returns>private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo propertyInfo){if (!propertyInfo.CanRead) return false;if (propertyInfo.CanWrite) return false; // already handled by default conventionif (propertyInfo.GetIndexParameters().Length != 0) return false; // skip indexersvar getMethodInfo = propertyInfo.GetMethod;if (getMethodInfo == null){return false;}// skip overridden properties (they are already included by the base class)if (getMethodInfo.IsVirtual && getMethodInfo.GetBaseDefinition().DeclaringType != classMap.ClassType) return false;return true;}}

最后注册该Convention

ConventionRegistry.Register("PBConventionsPack", new ConventionPack { new ProtobufDefaultMapConvention() }, t => t.IsAssignableTo(typeof(IMessage)) && t is { IsAbstract: false });
http://www.zskr.cn/news/1494623.html

相关文章:

  • 你的QQ空间记忆,真的安全吗?
  • 嵌入式硬件设计:从MCU时序参数到信号完整性的实战指南
  • 怎样实现终极数据安全:vaultwarden-backup多远程目标备份方案
  • NXP KMA320车规级角度传感器:AMR原理、SENT协议与ASIL安全设计详解
  • 如何构建基于YOLOv8的智能瞄准系统:从技术原理到实战配置
  • 深度解析Slick轮播dots分页指示器的架构设计与实现机制
  • 小程序毕设选题推荐:基于微信小程序校园二手交易平台系统小程序基于spring boot的校园二手交易平台系统小程序【附源码、mysql、文档、调试+代码讲解+全bao等】
  • Node.js 环境、NPM Yarn 安装与镜像源配置(优化精简版)
  • HS2-HF Patch终极指南:3分钟解锁完整汉化与去码功能
  • 小程序毕设选题推荐:nodejs基于微信小程序印象台院大学资讯新闻设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 从数据手册到可靠设计:K30微控制器外设电气特性深度解析与实践指南
  • IPATool深度解析:iOS应用包下载与逆向工程的专业实践
  • 2026秋招Java面试1000题(最新高频·大厂考点汇总)
  • 嵌入式硬件设计实战:从Kinetis K50数据手册参数到系统级优化
  • 半导体设备通信入门:从RS232到TCP/IP,一文搞懂SECS I、SECS II、HSMS和GEM的关系
  • 单组分高温环氧结构胶 K-EP280 完整技术参数与工程选型分析
  • PyTorch手写数字识别一键运行包:带图形界面、训练代码、预训练权重和手绘识别功能
  • Kinetis K61 MCG时钟与16位ADC电气特性解析与高精度设计实践
  • K30 I2S/SAI接口时序规范与引脚复用配置实战指南
  • 当OpenClaw遇见Linode:一键部署7×24h云端AI助理
  • 日记 2
  • 嵌入式开发时序规范解析:从SPI、I2C到I2S的硬件设计实践
  • i.MX 6SLL工业级SoC:从核心架构到硬件设计的嵌入式实战指南
  • Adobe-GenP 3.0:设计师的创意解放工具,告别订阅制束缚
  • Hitboxer深度解析:游戏键盘SOCD处理的技术实现与性能优化
  • 记录使用AI-coding
  • Axure RP中文语言包实战指南:快速实现专业原型设计工具汉化
  • 5个关键问题解析:如何高效获取macOS Big Sur官方安装包?
  • 猫抓cat-catch:3分钟解决你的浏览器视频下载痛点
  • 如何实现抖音内容批量下载:面向内容创作者和技术开发者的完整解决方案