如何通过protobuf-net成员序号替代字段名用MongoDB C#驱动存储文档
问题原因排查
- 字段重复报错原因
你写的Apply方法逻辑错误:对每个传入的BsonMemberMap,循环遍历了所有字段并重复调用SetElementName,最终所有属性的字段名都被覆盖为最后一个遍历到的字段编号30,自然会触发重复字段名错误。 - 拿不到基类成员原因
protobuf-net的RuntimeTypeModel中,子类的GetFields()方法默认仅返回当前子类定义的字段,基类的字段需要向上遍历继承链从基类的元数据中获取。
正确实现代码
1. 实现通用约定类(不需要泛型)
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using ProtoBuf.Meta; using System.Reflection; public class NumberedElementNameConvention : ConventionBase, IMemberMapConvention { public void Apply(BsonMemberMap memberMap) { var member = memberMap.MemberInfo; // 查找当前成员对应的ProtoMember编号 var fieldNumber = GetProtoFieldNumber(member.DeclaringType, member.Name); if (fieldNumber.HasValue) { // 如果你要保留_id作为主键名,这里加个判断跳过Id字段即可 memberMap.SetElementName(fieldNumber.Value.ToString()); } } private int? GetProtoFieldNumber(Type type, string memberName) { // 递归遍历继承链查找ProtoMember配置 var currentType = type; while (currentType != null && currentType != typeof(object)) { var metaType = RuntimeTypeModel.Default[currentType]; var field = metaType.GetFields().FirstOrDefault(f => f.Member.Name == memberName); if (field != null) { return field.FieldNumber; } currentType = currentType.BaseType; } return null; } }
2. 注册约定
var pack = new ConventionPack { new NumberedElementNameConvention() }; // 过滤规则可以按自己需求调整,这里对所有继承自Base的类生效 ConventionRegistry.Register("NumberedName", pack, type => type.IsSubclassOf(typeof(Base)));
继承链成员遍历方法
如果你需要输出某个类型所有的Proto成员(包括基类),可以用下面的代码:
public static void PrintAllProtoMembers(Type type) { var currentType = type; while (currentType != null && currentType != typeof(object)) { var fields = RuntimeTypeModel.Default[currentType].GetFields(); foreach (var field in fields) { Console.WriteLine($"{field.FieldNumber}: {field.Member.Name}"); } currentType = currentType.BaseType; } } // 调用测试 PrintAllProtoMembers(typeof(Todo));
输出结果会包含基类和子类的所有成员:
10: Title 20: Content 30: Category 1: Id 2: CreatedDate 3: UpdatedDate
注意事项
- 确保所有需要序列化的属性都加了
[ProtoMember]特性,且所有编号(包括基类和子类)全局唯一,否则会触发MongoDB重复字段名错误 - 如果要保留MongoDB默认的
_id主键名,只需要在Apply方法中对Id字段做特殊跳过处理即可
内容的提问来源于stack exchange,提问作者Sinan
相关产品推荐
相关产品推荐

