protobuf-net、版本控制和代理类型的最佳实践
本文关键字:最佳 类型 代理 版本控制 protobuf-net | 更新日期: 2023-09-27 17:55:26
我正在尝试确定如何使用protobuf-net(Marc Gravell的实现)解决此用例。
- 我们有 A 类,它被认为是版本 1
- 类 A 的实例已序列化到磁盘 我们现在有 B 类,
- 它被认为是 A 类的版本 2(A 类有很多问题,我们不得不为下一个版本创建 B 类)。类 A 仍然存在于代码中,但仅用于遗留目的。
- 我想将 version:1 数据(存储到磁盘)反序列化为 B 类实例,并使用逻辑例程将数据从以前的 A 类实例转换为类 B 的新实例。
- 在操作期间,类 B 的实例将序列化到磁盘。
- 应用程序应期望反序列化类 A 和 B 的实例。
数据协定代理项的概念浮现在脑海中。目标是将 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时的预期做法是什么?
对;看着他们,你确实完全改变了合同。我知道没有基于合约的序列化程序会因此而爱你,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中的非null,也许在它们之间添加一些转换运算符。但是,如果您在旧数据中只有一个裸 A,这很难。我能想到的有两种方法:
- 添加大量填充码属性以实现兼容性;不是很好维护,我不推荐它 嗅
- 探
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,要么通过两个非泛型API的Type
实例(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
代码允许您在获得版本号后立即短路。
我没有预期的练习,但这就是我会做的。
假设您仍然可以访问 V1 类,请在 V1 类上添加一个提供 V2 实例的属性。
在 V1 ProtoAfterDeserialization
中创建 V2 的实例,并看到它是迁移,我建议手动传输所需的内容(或者如果不是太难,请尝试Merge
YMMV)。
同样在你的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; }
}