protobuf-net:将扩展关键字反序列化为继承层次结构

3

我有一个使用protobuf-net进行继承的特定用例,我还没有发现这里涵盖的(虽然很愿意被重定向到任何有用的答案)。

我必须从第三方protobuf feed(GTFS-RT)中反序列化一些对象,并且已提供的.proto使用extend关键字扩展了基本类型(我们还在其他feeds中使用),从客观角度来看,这似乎是合理的。 但是,我无法让protobuf-net将以这种格式提供的feeds反序列化为适当的继承层次结构。

例如,基本.proto定义了一个名为FeedHeader的东西(在名为transit_realtime的程序包下):

message FeedHeader {
  required string gtfs_realtime_version = 1;

  enum Incrementality {
    FULL_DATASET = 0;
    DIFFERENTIAL = 1;
  }
  optional Incrementality incrementality = 2 [default = FULL_DATASET];

  optional uint64 timestamp = 3;

  extensions 1000 to 1999;
}

第三方将FeedHeader扩展以包含另一个属性:
extend transit_realtime.FeedHeader {
 optional NyctFeedHeader nyct_feed_header = 1001;
}

我希望将其反序列化为以下类层次结构:
namespace Base.GTFS 
{
    [ProtoContract(Name = nameof(FeedHeader))]
    public class FeedHeader
    {
        [ProtoMember(1, IsRequired = true, Name = nameof(gtfs_realtime_version), DataFormat = DataFormat.Default)]
        public string gtfs_realtime_version { get; set; }

        [ProtoMember(2, IsRequired = false, Name = nameof(incrementality), DataFormat = DataFormat.TwosComplement)]
        [DefaultValue(Incrementality.FULL_DATASET)]
        public Incrementality incrementality { get; set; } = Incrementality.FULL_DATASET;

        [ProtoMember(3, IsRequired = false, Name = nameof(timestamp), DataFormat = DataFormat.TwosComplement)]
        [DefaultValue(default(ulong))]
        public ulong timestamp { get; set; } = default(ulong);

        public FeedHeader() { }

        #region Nested Enums
        [ProtoContract(Name = nameof(Incrementality))]
        public enum Incrementality
        {

            [ProtoEnum(Name = nameof(FULL_DATASET), Value = 0)]
            FULL_DATASET = 0,

            [ProtoEnum(Name = nameof(DIFFERENTIAL), Value = 1)]
            DIFFERENTIAL = 1
        }
        #endregion
    }
}

namespace Other.GTFS 
{
    [ProtoContract(Name = nameof(FeedHeader))]
    public class FeedHeader : Base.GTFS.FeedHeader
    {
        /// <summary>
        /// NYCT Subway extensions for the feed header
        /// </summary>
        [ProtoMember(1001, Name = nameof(nyct_feed_header), IsRequired = false, DataFormat = DataFormat.Default)]
        public NyctFeedHeader nyct_feed_header { get; set; } = null;

        public FeedHeader() : base() { }
    }
}

阅读了此处和其他地方的帖子后,我尝试使用AddSubType方法和AddSurrogate方法,但发现只有在覆盖基类中所有字段时才能可靠地对所有字段进行反序列化。这似乎非常低效,并且如果基本类型更改,则会出现问题。我们还需要将序列化用于其他源,因此我需要一个易于扩展的解决方案。
是否有人知道如何支持这种情况或有任何建议?
1个回答

0

proto2中的extendextensions关键字与继承层次结构不对应。相反,它们更接近于C#中的partial关键字,允许在一个文件中部分定义消息,在另一个文件中添加。根据Google的proto2语言文档

扩展允许您声明消息中一系列字段号可用于第三方扩展。其他人可以使用这些数字标签在自己的.proto文件中为您的消息类型声明新字段,而无需编辑原始文件。

因此,如果我创建包含以下内容的文件Question40863857_1.proto

package transit_realtime;

message FeedHeader {
  required string gtfs_realtime_version = 1;

  enum Incrementality {
    FULL_DATASET = 0;
    DIFFERENTIAL = 1;
  }
  optional Incrementality incrementality = 2 [default = FULL_DATASET];

  optional uint64 timestamp = 3;

  extensions 1000 to 1999;
}

还有包含Question40863857_2.proto的内容:

import "Question40863857_1.proto";

// Some random enum since the NyctFeedHeader type wasn't included in the question.
enum NyctFeedHeader {
    Value0 = 0;
    Value1 = 1;
}

extend transit_realtime.FeedHeader {
 optional NyctFeedHeader nyct_feed_header = 1001;
}

然后使用protobuf-net的protogen.exe实用程序从它们自动生成c#类,步骤如下:

protogen.exe -i:Question40863857_1.proto -i:Question40863857_2.proto

生成的类型如下:
// Generated from: Question40863857_1.proto
namespace transit_realtime
{
  [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"FeedHeader")]
  public partial class FeedHeader : global::ProtoBuf.IExtensible
  {
    public FeedHeader() {}

    private string _gtfs_realtime_version;
    [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"gtfs_realtime_version", DataFormat = global::ProtoBuf.DataFormat.Default)]
    public string gtfs_realtime_version
    {
      get { return _gtfs_realtime_version; }
      set { _gtfs_realtime_version = value; }
    }
    private transit_realtime.FeedHeader.Incrementality _incrementality = transit_realtime.FeedHeader.Incrementality.FULL_DATASET;
    [global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"incrementality", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    [global::System.ComponentModel.DefaultValue(transit_realtime.FeedHeader.Incrementality.FULL_DATASET)]
    public transit_realtime.FeedHeader.Incrementality incrementality
    {
      get { return _incrementality; }
      set { _incrementality = value; }
    }
    private ulong _timestamp = default(ulong);
    [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"timestamp", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    [global::System.ComponentModel.DefaultValue(default(ulong))]
    public ulong timestamp
    {
      get { return _timestamp; }
      set { _timestamp = value; }
    }
    private NyctFeedHeader _nyct_feed_header = NyctFeedHeader.Value0;
    [global::ProtoBuf.ProtoMember(1001, IsRequired = false, Name=@"nyct_feed_header", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    [global::System.ComponentModel.DefaultValue(NyctFeedHeader.Value0)]
    public NyctFeedHeader nyct_feed_header
    {
      get { return _nyct_feed_header; }
      set { _nyct_feed_header = value; }
    }
    [global::ProtoBuf.ProtoContract(Name=@"Incrementality")]
    public enum Incrementality
    {

      [global::ProtoBuf.ProtoEnum(Name=@"FULL_DATASET", Value=0)]
      FULL_DATASET = 0,

      [global::ProtoBuf.ProtoEnum(Name=@"DIFFERENTIAL", Value=1)]
      DIFFERENTIAL = 1
    }

    private global::ProtoBuf.IExtension extensionObject;
    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
  }

}
// Generated from: Question40863857_2.proto
// Note: requires additional types generated from: Question40863857_1.proto
namespace Question40863857_2
{
    [global::ProtoBuf.ProtoContract(Name=@"NyctFeedHeader")]
    public enum NyctFeedHeader
    {

      [global::ProtoBuf.ProtoEnum(Name=@"Value0", Value=0)]
      Value0 = 0,

      [global::ProtoBuf.ProtoEnum(Name=@"Value1", Value=1)]
      Value1 = 1
    }

}

请注意,没有继承层次结构,只有一个单一的类FeedHeader,其中包含来自两个.proto文件的字段以及辅助枚举。

实际上,如果您拥有完整的.proto文件集,可以使用protogen.exe为您生成c#类型,从而避免这种困难。或者,使用插件进行Visual Studio。

相反,如果您需要检查由c#类型T指定的合同是否与所需的.proto文件匹配,则可以执行以下操作:

Console.WriteLine(RuntimeTypeModel.Default.GetSchema(typeof(T)));

对于 typeof(Other.GTFS.FeedHeader),结果如下:

message FeedHeader {
   optional NyctFeedHeader nyct_feed_header = 1001 [default = Value0];
}
enum NyctFeedHeader {
   Value0 = 0;
   Value1 = 1;
}

这显然不是你想要的。


谢谢提供信息 - 我想这就是它的工作原理。很遗憾,因为我有一个服务层位于这些之上,声明了使用基类的接口 - 我认为我需要将基本模型的所需部分提取到接口中,并确保它们应用于部分生成的类(因为它们是部分的,所以更容易实现)。 - davide

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接