You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6下Protobuf-net反序列化多层继承类的字段丢失与回调问题

问题描述

在.NET 6应用中,类由工具动态生成且存在多层继承结构,使用protobuf-net 3.2.30序列化时遇到两个问题:

  1. 反序列化后父类的PrimaryKeyId变为null,字段值丢失
  2. 父类的[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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 08:29:55