对于protobuf网,版本控制和代理各类最佳实践(Best practice for protob

2019-07-18 14:47发布

我试图确定如何使用protobuf网(马克Gravell的实现)来解决这个用例。

  • 我们有A级,这被认为是版本1
  • A类的实例已经系列化到磁盘
  • 我们现在有B类,这被认为是A级的第2版(有这么多事情错A级,我们必须为下一个版本创建B类)。 A级仍然存在的代码,但仅限于传统的目的。
  • 我想反序列化版本:1点的数据(存储到磁盘),为B类的实例,并且使用逻辑例程将数据从先前的类A的实例转换为类B的一个新实例
  • B类的实例将操作过程中被序列化到磁盘。
  • 该应用程序应该预料到这两个序列化类A和B的情况下,

数据合同代理人和DataContractSerializer的概念浮现在脑海中。 我们的目标是过渡版本:1点的数据到新的B级结构。

一个例子:

[DataContract]
public class A {

     public A(){}

     [DataMember]
     public bool IsActive {get;set;]

     [DataMember]
     public int VersionNumber {
          get { return 1; }
          set { }
     }

     [DataMember]
     public int TimeInSeconds {get;set;}

     [DataMember]
     public string Name {get;set;}

     [DataMember]
     public CustomObject CustomObj {get;set;} //Also a DataContract

     [DataMember]
     public List<ComplexThing> ComplexThings {get;set;} //Also a DataContract
     ...
}

[DataContract]
public class B {

     public B(A a) {
          this.Enabled = a.IsActive; //Property now has a different name
          this.TimeInMilliseconds = a.TimeInSeconds * 1000; //Property requires math for correctness
          this.Name = a.Name;
          this.CustomObject2 = new CustomObject2(a.CustomObj); //Reference objects change, too
          this.ComplexThings = new List<ComplexThings>();
          this.ComplexThings.AddRange(a.ComplexThings);
          ...
     }

     public B(){}

     [DataMember]
     public bool Enabled {get;set;]

     [DataMember]
     public int Version {
          get { return 2; }
          set { }
     }

     [DataMember]
     public double TimeInMilliseconds {get;set;}

     [DataMember]
     public string Name {get;set;}

     [DataMember]
     public CustomObject2 CustomObject {get;set;} //Also a DataContract

     [DataMember]
     public List<ComplexThing> ComplexThings {get;set;} //Also a DataContract
     ...
}

A级是我们的对象的第一次迭代,并积极使用。 数据存在于V1格式,使用A类进行序列化。

实现我们的方法错误后,我们创建了一个名为B类新的结构有,我们觉得这是更好地创造B A和B之间这么多的变化,而不是适应原来的A类。

但是,我们的应用程序已经存在,A级被用来序列数据。 我们已经准备好了我们改变了世界,但我们必须先反序列化下的版本1(使用A类)创建的数据和实例作为B级的数据是不够显著,我们不能想当然地认为在课堂上的默认值B中丢失的数据,而是我们必须从A类的实例一旦我们有一个B类实例转移数据到B类,应用程序将在B类格式(第2版)再次序列化数据。

我们假设,我们将在未来进行修改,B类,我们希望能够迭代到第3版,也许在一类新的“C”。 我们有两个目标:地址数据已经存在,并为未来的准备迁移我们的对象。

现有的“过渡”属性(OnSerializing / OnSerialized,OnDeserializing / OnDeserialized等)不提供访问以前的数据。

在这种情况下使用protobuf网时,什么是预期的做法?

Answer 1:

对; 看着他们,你确实完全改变了合同。 我知道,是要爱你,没有基于合同的序列化和protobuf网是没有什么不同。 如果你已经有了一个根节点,你可以这样做(在伪代码):

[Contract]
class Wrapper {
    [Member] public A A {get;set;}
    [Member] public B B {get;set;}
    [Member] public C C {get;set;}
}

和只挑选A的取/ B / C是非空,也许加入它们之间的一些转换运算符。 不过,如果你只是有一个裸体的旧数据,这变得很难。 还有我能想到的两种方法:

  • 添加大量的兼容性垫片性能; 不是很维护,我不建议这样做
  • 嗅探Version作为第一步,并告诉串行什么期望。

例如,你可以这样做:

int version = -1;
using(var reader = new ProtoReader(inputStream)) {
    while(reader.ReadFieldHeader() > 0) {
        const int VERSION_FIELD_NUMBER = /* todo */;
        if(reader.FieldNumber == VERSION_FIELD_NUMBER) {
            version = reader.ReadInt32();
            // optional short-circuit; we're not expecting 2 Version numbers
            break;
        } else {
            reader.SkipField();
        }
    }
}
inputStream.Position = 0; // rewind before deserializing

现在你可以使用串行,告诉它什么version它被序列化为; 无论是通过通用Serializer.Deserialize<T> API,或者通过Type实例从两个非一般API( Serializer.NonGeneric.DeserializeRuntimeTypeModel.Default.Deserialize -无论哪种方式,你到了同一个地方,它是真的是否的情况下,一般的或非通用是最方便的)。

那么你就需要之间的一些转换代码A / B / C -无论是通过自己的运营商定制/方法,或者通过类似自动映射。

如果你不希望任何ProtoReader代码踢身边,你也可以这样做:

[DataContract]
class VersionStub {
    [DataMember(Order=VERSION_FIELD_NUMBER)]
    public int Version {get;set;}
}

并运行Deserialize<VersionStub>这将给你访问Version ,然后你就可以用做特定类型的反序列化; 这里的主要区别是, ProtoReader代码,只要你有一个版本号,您可以短路。



Answer 2:

我没有预期的做法,但是这是我会怎么做。

鉴于你仍然有你的V1级接入你的V1类,提供了一个V2实例添加属性。

在你ProtoAfterDeserialization V1的创建V2的实例,并看到它是一个移民我会建议手动跨越你所需要的转移(或者如果它不是太硬,尝试Merge因人而异)。

此外,在您的ProtoBeforeSerialization抛出一些形式的异常,所以,你不要试图写出旧了。

编辑:使用这些(VB代码)的实例

<ProtoBeforeSerialization()>
Private Sub BeforeSerialisaton()

End Sub

<ProtoAfterSerialization()>
Private Sub AfterSerialisaton()

End Sub

<ProtoBeforeDeserialization()>
Private Sub BeforeDeserialisation()

End Sub

<ProtoAfterDeserialization()>
Private Sub AfterDeserialisation()

End Sub

看到你的例子后,我希望这满足你正在尝试做的。 Class1是你如何加载/转换。

using ProtoBuf;
using System.Collections.Generic;
using System.IO;

public class Class1
{
    public Class1()
    {
        using (FileStream fs = new FileStream("c:\\formatADataFile.dat",
               FileMode.Open, FileAccess.Read))
        {
            A oldA = Serializer.Deserialize<A>(fs);
            B newB = oldA.ConvertedToB;
        }
    }
}


[ProtoContract()]
public class B
{

    public B(A a)
    {
        //Property now has a different name
        this.Enabled = a.IsActive; 
        //Property requires math for correctness
        this.TimeInMilliseconds = a.TimeInSeconds * 1000; 
        this.Name = a.Name;
        //Reference objects change, too
        this.CustomObject2 = new CustomObject2(a.CustomObj); 
        this.ComplexThings = new List<ComplexThings>();
        this.ComplexThings.AddRange(a.ComplexThings);
        //...
    }

    public B() { }

    //[DataMember]
    [ProtoMember(1)]
    public bool Enabled { get; set; }

    //[DataMember]
    public int Version
    {
        get { return 2; }
        private set { }
    }

    [ProtoMember(2)]
    public double TimeInMilliseconds { get; set; }

    [ProtoMember(3)]
    public string Name { get; set; }

    [ProtoMember(4)]
    public CustomObject2 CustomObject { get; set; } //Also a DataContract

    [ProtoMember(5)]
    public List<ComplexThing> ComplexThings { get; set; } //Also a DataContract

    ///...
}

[ProtoContract()]
public class CustomObject2
{
    public CustomObject2()
    {
        Something = string.Empty;
    }

    [ProtoMember(1)]
    public string Something { get; set; }
}


[ProtoContract()]
public class A
{

    public A()
    {
        mBConvert = new B();
    }

    [ProtoMember(1)]
    public bool IsActive { get; set; }

    [ProtoMember(2)]
    public int VersionNumber
    {
        get { return 1; }
        private set { }
    }

    [ProtoBeforeSerialization()]
    private void NoMoreSavesForA()
    {
        throw new System.InvalidOperationException("Do Not Save A");
    }

    private B mBConvert;

    [ProtoAfterDeserialization()]
    private void TranslateToB()
    {
        mBConvert = new B(this);
    }

    public B ConvertedToB
    {
        get
        {
            return mBConvert;
        }
    }



    [ProtoMember(3)]
    public int TimeInSeconds { get; set; }

    [ProtoMember(4)]
    public string Name { get; set; }

    [ProtoMember(5)]
    public CustomObject CustomObj { get; set; } //Also a DataContract

    [ProtoMember(6)]
    public List<ComplexThing> ComplexThings { get; set; } //Also a DataContract
    //...
}

[ProtoContract()]
public class CustomObject
{
    public CustomObject()
    {

    }
    [ProtoMember(1)]
    public int Something { get; set; }
}

[ProtoContract()]
public class ComplexThing
{
    public ComplexThing()
    {

    }
    [ProtoMember(1)]
    public int SomeOtherThing { get; set; }
}


文章来源: Best practice for protobuf-net, versioning and surrogate types