.NET 6下Protobuf-net反序列化多层继承类的字段丢失与回调问题
问题描述
在.NET 6应用中,类由工具动态生成且存在多层继承结构,使用protobuf-net 3.2.30序列化时遇到两个问题:
- 反序列化后父类的
PrimaryKeyId变为null,字段值丢失 - 父类的
[OnDeserialized]回调未自动执行
由于类数量过多且为动态生成,无法使用[ProtoInclude()]特性。示例类结构与序列化代码如下:
示例类结构
[DataContract] [Serializable] [ProtoContract] public partial class Person : TrackingObject { [DataMember(Name = "A1")] [ProtoMember(1)] internal int __id_person; [XmlIgnore] public int ID_PERSON { get { return __id_person; } set { if (value != __id_person) { __id_person = value; SetPrimaryKeyId(value); } } } [DataMember(Name = "A2")] [ProtoMember(2)] public string NAME { get; set; } [DataMember(Name = "A3")] [ProtoMember(3)] public DateTime DOB { get; set; } } [DataContract] [Serializable] [ProtoContract] public class TrackingObject : PrimaryKeyObject { [DataMember(Name = "Z1")] [ProtoMember(1)] internal HashSet<string> __propertyChangedList; [DataMember(Name = "Z2")] [ProtoMember(2)] internal EntityStateFlag __entityState; public TrackingObject() : base() { } [OnDeserialized] private void UpdateEntityState() { if (this.EntityState == EntityStateFlag.Detached) __entityState = EntityStateFlag.Unchanged; } [XmlIgnore] public EntityStateFlag EntityState { get { if (__entityState == 0) __entityState = EntityStateFlag.Detached; return __entityState; } set { if (value != __entityState) { __entityState = value; if ((value == EntityStateFlag.Unchanged || value == EntityStateFlag.Detached) && __propertyChangedList != null) __propertyChangedList = null; } } } } [DataContract] [Serializable] [ProtoContract] public class PrimaryKeyObject { [DataMember(Name = "Y1")] [ProtoMember(1)] internal int? __primaryKeyId; [XmlIgnore] public int? PrimaryKeyId { get { return __primaryKeyId; } } public void SetPrimaryKeyId(int? primaryKeyId) { __primaryKeyId = primaryKeyId; } }
序列化与反序列化代码
internal class Program { static void Main(string[] args) { var person = new Person() { ID_PERSON = 1, NAME = "Abc", DOB = DateTime.Now.AddYears(-10) }; MemoryStream objectSerializer = new MemoryStream(); objectSerializer.Seek(0, SeekOrigin.Begin); WriteNext(objectSerializer, person); objectSerializer.Seek(0, SeekOrigin.Begin); var i1 = DeSerialized<Person>(objectSerializer); } static readonly Dictionary<int, Type> typeLookup = new Dictionary<int, Type>(); static void WriteNext(Stream stream, object obj) { Type type = obj.GetType(); if (!typeLookup.ContainsValue(type)) { var key = typeLookup.Count > 0 ? typeLookup.Max(i => i.Key) : 0; typeLookup.Add(key + 1, type); int field = typeLookup.Single(pair => pair.Value == type).Key; Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field); } } static T DeSerialized<T>(Stream stream) { object obj; if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj)) { if (obj.GetType() == typeof(T)) return (T)obj; } return default(T); } }
解决方案
1. 解决多层继承字段丢失问题
protobuf-net需要明确继承关系才能正确序列化父类字段,动态生成类无法用特性标注时,可通过RuntimeTypeModel动态注册继承层次:
// 在应用启动或首次序列化前执行 var model = RuntimeTypeModel.Default; // 为父类注册子类,分配唯一的字段标识(需避免与现有ProtoMember冲突) model.Add(typeof(PrimaryKeyObject), true).AddSubType(100, typeof(TrackingObject)); model.Add(typeof(TrackingObject), true).AddSubType(101, typeof(Person));
如果是批量动态类,可通过反射遍历所有类型,自动递归注册继承链。
2. 触发父类的OnDeserialized回调
protobuf-net默认不执行DataContract的[OnDeserialized]特性,有两种处理方式:
方式一:改用protobuf-net原生回调
将父类的[OnDeserialized]替换为[ProtoAfterDeserialization]特性,protobuf-net会自动执行:
[ProtoAfterDeserialization] private void UpdateEntityState() { if (this.EntityState == EntityStateFlag.Detached) __entityState = EntityStateFlag.Unchanged; }
方式二:手动触发DataContract回调
若无法修改类特性,可在反序列化后通过反射触发所有层级的[OnDeserialized]方法:
static void TriggerOnDeserialized(object obj) { var type = obj.GetType(); // 遍历当前类及所有父类 while (type != null) { var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(m => m.GetCustomAttributes(typeof(OnDeserializedAttribute), false).Any()); foreach (var method in methods) { method.Invoke(obj, new object[] { new StreamingContext() }); } type = type.BaseType; } } // 修改反序列化方法,添加回调触发 static T DeSerialized<T>(Stream stream) { object obj; if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj)) { if (obj.GetType() == typeof(T)) { TriggerOnDeserialized(obj); return (T)obj; } } return default(T); }
3. 修复PrimaryKeyId为空的问题
反序列化时protobuf-net直接赋值__id_person字段,不会触发ID_PERSON的setter,导致__primaryKeyId未被赋值。可在Person类中添加反序列化后回调手动同步:
[DataMember(Name = "A1")] [ProtoMember(1)] internal int __id_person; [ProtoAfterDeserialization] private void SyncPrimaryKey() { SetPrimaryKeyId(__id_person); }
内容的提问来源于stack exchange,提问作者Pradeep
相关产品推荐
相关产品推荐

