对于protobuf网,版本控制和代理各类最佳实践 [英] Best practice for protobuf-net, versioning and surrogate types

查看:204
本文介绍了对于protobuf网,版本控制和代理各类最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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




  • 我们上课A,这被认为是版本1

  • A类的一个实例已序列化到磁盘

  • 我们现在有b类,这被认为是第2版A级(有这么多的东西错了A级,我们必须为下一个版本创建B类)。 A类仍然存在代码,但对遗留目的

  • 我要反序列化的版本:1。数据(存储到磁盘)为B类的实例,并使用逻辑例程从以前的A类的实例转换数据,以b级的新实例。

  • b类的实例将操作过程中被序列化到磁盘中。

  • <李>应用程序应该预料到反序列化既类和b类的实例。


数据合同代理人的概念和DataContractSerializer的来心神。我们的目标是过渡版本:1的数据到新的B类结构



一个例子:

  [DataContract] 
公共类A {

公开发行A(){}

[数据成员]
公共BOOL IsActive {获取;设置]

[数据成员]
公众诠释VERSIONNUMBER {
{返回1; }
集合{}
}

[数据成员]
公众诠释TimeInSeconds {获取;集;}

[数据成员]
公共字符串名称{;设置;}

[数据成员]
公共CustomObject CustomObj {获取;设置;} //另外一个DataContract

[数据成员]
公开名单< ComplexThing> ComplexThings {获取;集;} //另外一个DataContract

}

[DataContract]
公共类B {

酒店的公共b(A中){
this.Enabled = a.IsActive; //属性现在有一个不同的名称
this.TimeInMilliseconds = a.TimeInSeconds * 1000; //属性需要数学的正确性
this.Name = a.Name;
this.CustomObject2 =新CustomObject2(a.CustomObj); //引用对象的变化,太
this.ComplexThings =新的List< ComplexThings>();
this.ComplexThings.AddRange(a.ComplexThings);

}

公众B(){}

[数据成员]
公共BOOL启用{获取;集;]

[数据成员]
公众诠释版本{
得到{2; }
集合{}
}

[数据成员]
公共双TimeInMilliseconds {获取;集;}

[数据成员]
公共字符串名称{;设置;}

[数据成员]
公共CustomObject2 CustomObject {获取;设置;} //另外一个DataContract

[数据成员]
公开名单< ComplexThing> ComplexThings {获取;集;} //另外一个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网时,什么是预期的做法呢?


< DIV CLASS =h2_lin>解决方案

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

  [合同] 
类包装{
[会员]公共AA {获取;集;}
[会员]公开BB {获取;集;}
[会员]大众CC {获取;集;}
}

和随便挑取其A / b / C的非空,也许在他们之间添加一些转换操作符。不过,如果你只是有一个赤身裸体的旧数据,这变得很难。还有我能想到的两种方法:




  • 添加的地段的兼容性垫片性能; 非常维护,我不推荐

  • 嗅出版本作为第一步,告诉什么期望串行



例如,你可以这样做:

  INT版本= -1;使用
(VAR读卡器=新ProtoReader(的InputStream)){
,而(reader.ReadFieldHeader()0){
const int的VERSION_FIELD_NUMBER = / * TODO * /;
如果(reader.FieldNumber == VERSION_FIELD_NUMBER){
版本= reader.ReadInt32();
//可选的短路;我们并不希望2版本号
中断;
}其他{
reader.SkipField();
}
}
}
inputStream.Position = 0; //反序列化
之前倒带

现在可以使用序列化,告诉它什么版本它被序列化为;无论是通过普通的 Serializer.Deserialize< T> API,或通过键入由两个非一般API 实例( Serializer.NonGeneric.Deserialize RuntimeTypeModel.Default.Deserialize - 无论哪种方式,你到了同一个地方,它真是是否通用或非泛型是最方便的)的情况。



那么你就需要 A / B / C - 无论是通过自己的运营商定制/方法,或者通过类似自动映射器



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

  [DataContract] 
类VersionStub {
[数据成员(订单= VERSION_FIELD_NUMBER)]
公众诠释版本{获取;设置;}
}

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


I'm trying to determine how to address this use case using protobuf-net (Marc Gravell's implementation).

  • We have class A, which is considered version 1
  • An instance of class A has been serialized to disk
  • We now have class B, which is considered version 2 of class A (there were so many things wrong with class A, we had to create class B for the next version). Class A still exists in code, but only for legacy purposes.
  • I want to deserialize the version:1 data (stored to disk) as a class B instance, and use a logic routine to translate the data from the previous class A instance to a new instance of class B.
  • The instance of class B will be serialized to disk during operation.
  • The application should expect to deserialize instances of both class A and B.

The concept of data contract surrogates and the DataContractSerializer come to mind. The goal is transition the version:1 data to the new class B structure.

An example:

[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
     ...
}

Class A was the first iteration of our object, and is actively in use. Data exists in v1 format, using class A for serialization.

After realizing the error of our ways, we create a new structure called class B. There are so many changes between A and B that we feel it's better to create B, as opposed to adapting the original class A.

But our application already exists and class A is being used to serialize data. We're ready to roll our changes out to the world, but we must first deserialize data created under version 1 (using class A) and instantiate it as class B. The data is significant enough that we can't just assume defaults in class B for missing data, but rather we must transition the data from a class A instance to class B. Once we have a class B instance, the application will serialize that data again in class B format (version 2).

We're assuming we'll make modifications to class B in the future, and we want to be able to iterate to a version 3, perhaps in a new class "C". We have two goals: address data already in existence, and prepare our objects for future migration.

The existing "transition" attributes (OnSerializing/OnSerialized,OnDeserializing/OnDeserialized,etc.) don't provide access to the previous data.

What is the expected practice when using protobuf-net in this scenario?

解决方案

Right; looking at them you have indeed completely changed the contract. I know of no contract-based serializer that is going to love you for that, and protobuf-net is no different. If you already had a root node, you could do something like (in pseudo-code):

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

and just pick whichever of A/B/C is non-null, perhaps adding some conversion operators between them. However, if you just have a naked A in the old data, this gets hard. There are two approaches I can think of:

  • add lots of shim properties for compatibility; not very maintainable, and I don't recommend it
  • sniff the Version as an initial step, and tell the serializer what to expect.

For example, you could do:

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

Now you can use the serializer, telling it what version it was serialized as; either via the generic Serializer.Deserialize<T> API, or via a Type instance from the two non-generic APIs (Serializer.NonGeneric.Deserialize or RuntimeTypeModel.Default.Deserialize - either way, you get to the same place; it is really a case of whether generic or non-generic is most convenient).

Then you would need some conversion code between A / B / C - either via your own custom operators / methods, or by something like auto-mapper.

If you don't want any ProtoReader code kicking around, you could also do:

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

and run Deserialize<VersionStub>, which will give you access to the Version, which you can then use to do the type-specific deserialize; the main difference here is that the ProtoReader code allows you to short-circuit as soon as you have a version-number.

这篇关于对于protobuf网,版本控制和代理各类最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆