6

我正在尝试确定如何使用 protobuf-net(Marc Gravell 的实现)来解决这个用例。

  • 我们有 A 类,它被认为是版本 1
  • A 类的一个实例已序列化到磁盘
  • 我们现在有了 B 类,它被认为是 A 类的第 2 版(A 类有很多问题,我们必须为下一个版本创建 B 类)。A 类仍然存在于代码中,但仅用于遗留目的。
  • 我想将 version:1 数据(存储到磁盘)反序列化为 B 类实例,并使用逻辑例程将数据从先前的 A 类实例转换为 B 类的新实例。
  • B 类的实例将在运行期间被序列化到磁盘。
  • 应用程序应该期望反序列化 A 类和 B 类的实例。

我想到了数据契约代理和 DataContractSerializer 的概念。目标是将 version: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 类。A 和 B 之间有很多变化,我们觉得创建 B 更好,而不是适应原来的 A 类。

但是我们的应用程序已经存在,并且 A 类正在用于序列化数据。我们已经准备好将我们的更改推广到世界各地,但我们必须首先反序列化在版本 1 下创建的数据(使用 A 类)并将其实例化为 B 类。数据足够重要,我们不能只假设类中的默认值B 表示缺少数据,而是我们必须将数据从 A 类实例转换到 B 类。一旦我们有了 B 类实例,应用程序将以 B 类格式(版本 2)再次序列化该数据。

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

现有的“转换”属性(OnSerializing/OnSerialized、OnDeserializing/OnDeserialized 等)不提供对先前数据的访问。

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

4

2 回答 2

4

对; 看着他们,你确实完全改变了合同。我知道没有基于合约的序列化程序会因此而爱你,protobuf-net 也不例外。如果您已经有一个根节点,您可以执行以下操作(在伪代码中):

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

并且只需选择 A/B/C 中非空的任何一个,也许在它们之间添加一些转换运算符。但是,如果您在旧数据中只有一个裸 A,这会变得很困难。我能想到的方法有两种:

  • 添加许多shim 属性以实现兼容性;不太可维护,我不推荐它
  • 嗅探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.Deserialize或者RuntimeTypeModel.Default.Deserialize- 无论哪种方式,你都会到达同一个地方;这实际上是通用还是非通用最方便的情况)。

然后,您将需要A/ B/之间的一些转换代码C- 通过您自己的自定义运算符 / 方法,或通过自动映射器之类的东西。

如果您不想要任何ProtoReader代码,您也可以这样做:

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

并运行Deserialize<VersionStub>,这将使您可以访问Version,然后您可以使用它来执行特定于类型的反序列化;这里的主要区别在于ProtoReader代码允许您在获得版本号后立即短路。

于 2013-01-28T22:47:16.543 回答
2

我没有预期的练习,但这就是我要做的。

假设您仍然可以访问您的 V1 类,请在您的 V1 类上添加一个提供 V2 实例的属性。

在你ProtoAfterDeserialization的 V1 中创建一个 V2 的实例并看到它是一个迁移我建议手动转移你需要的东西(或者如果它不太难,试试MergeYMMV)。

同样在你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; }
}
于 2013-01-24T00:26:33.147 回答