我有一个使用protobuf-net进行继承的特定用例,我不知道(如果可能)如何处理。
假设我们有以下示例类(将其标记为版本1):
public class FooA
{
public double A { get; set; }
public double B { get; set; }
}
public class FooB : FooA
{
public double C { get; set; }
}
public class FooC : FooB
{
}
public class FooD : FooC
{
public double D { get; set; }
}
public class FooA1 : FooA
{
}
具有以下protobuf的模型定义:
Model = RuntimeTypeModel.Create();
Model.Add(typeof(FooA), false)
.AddSubType(201, typeof(FooB))
.AddSubType(202, typeof(FooA1))
.Add(1, "A")
.Add(2, "B");
Model[typeof(FooB)]
.AddSubType(201, typeof(FooC))
.Add(1, "C");
Model[typeof(FooC)]
.AddSubType(201, typeof(FooD));
Model[typeof(FooD)]
.Add(1, "D");
我将它们序列化如下
FooA a = new FooA() {A = 10, B = 20};
FooB b = new FooB() {A = 10, B = 20, C = 30};
FooA1 b1 = new FooA1() {A = 100, B = 200};
FooC c = new FooC() {A = 10, B = 20, C = 30};
FooD d = new FooD() {A = 10, B = 20, C = 30, D = 40};
using (var stream = File.Open(fileName, FileMode.Create, FileAccess.Write))
{
SerializeWithLengthPrefix(stream, a);
SerializeWithLengthPrefix(stream, b);
SerializeWithLengthPrefix(stream, b1);
SerializeWithLengthPrefix(stream, c);
SerializeWithLengthPrefix(stream, d);
}
辅助方法
public void SerializeWithLengthPrefix<T>(Stream stream, T obj)
{
var serializationContext = new ProtoBuf.SerializationContext() { Context = this };
Model.SerializeWithLengthPrefix(stream, obj, typeof(T), PrefixStyle.Base128, 1, serializationContext);
}
public T DeserializeWithLengthPrefix<T>(Stream stream, out long bytesRead, out bool haveObject)
{
var serializationContext = new ProtoBuf.SerializationContext() { Context = this };
return (T)Model.DeserializeWithLengthPrefix(stream, null, typeof(T), PrefixStyle.Base128, 1, null, out bytesRead, out haveObject, serializationContext);
}
现在,我需要以这种方式更改继承层次结构(将其标记为版本2):
public class FooA
{
public double A { get; set; }
public double B { get; set; }
}
public class FooB : FooA
{
public double C { get; set; }
}
public class FooC : FooA1/*FooB*/
{
public double C { get; set; }
}
public class FooD : FooC
{
public double D { get; set; }
}
public class FooA1 /*: FooA*/
{
public double A { get; set; }
public double B { get; set; }
}
还有模型定义,试图为先前定义的每个类保留相同的ID。
Model = RuntimeTypeModel.Create();
Model.Add(typeof(FooA), false)
.AddSubType(201, typeof(FooB))
//.AddSubType(202, typeof(FooA1))
.Add(1, "A")
.Add(2, "B");
Model.Add(typeof(FooA1), false)
.Add(1, "A")
.Add(2, "B");
Model[typeof(FooB)]
//.AddSubType(201, typeof(FooC))
.Add(1, "C");
Model[typeof(FooA1)]
.AddSubType(201, typeof(FooC));
Model[typeof(FooC)]
.Add(1, "C")
.AddSubType(201, typeof(FooD));
Model[typeof(FooD)]
.Add(1, "D");
现在,我反序列化版本1中存储的文件,并检查模型中定义的类型:它们与版本1序列化期间的类型相同。
但是当我检查对象值时,我看到FooC已反序列化为FooD,并且D值始终等于0。
我在做什么错?有办法解决吗?
更新
使用版本2反序列化 FooC 时尝试调试protobuf-net源代码,方法 RuntimeTypeModel。GetKey()从基类开始(getBaseKey = true),可以正确获取 FooA1 (key = 2),但最终它将获得 FooD 对象,而不是 FooC 。也许有一种方法可以以不同的方式处理这种方法,以允许出现这种情况?
答案 0 :(得分:0)
我想不出一种改变而不破坏兼容性的方法。当我们陷入困境时,我的建议是:将“真实”类型和序列化类型拆分为单独的类型模型,这些模型不必彼此成对1:1映射。然后,您可以对“实际”类型(域模型)执行任何操作,然后将这些值投影到序列化模型中,该模型可能具有不同的规则,并且对于序列化程序更方便。在这种情况下,就继承树而言,序列化类型可能不是1:1映射。
另一个选择是强制迁移旧数据,因此:用旧布局反序列化它,并用新布局重新序列化它。这是在接受继承性更改,而不是假装没有发生。
以下内容显示了最终的.proto布局,以解释为什么FooC
在v2中最终以FooD
的形式出现:
从FooA
-FooC
开始的v1是201,201(FooD
是201,201,201)
syntax = "proto2";
message FooA {
optional double A = 1;
optional double B = 2;
oneof subtype {
FooB FooB = 201;
FooA1 FooA1 = 202;
}
}
message FooA1 {
}
message FooB {
optional double C = 1;
oneof subtype {
FooC FooC = 201;
}
}
message FooC {
oneof subtype {
FooD FooD = 201;
}
}
message FooD {
optional double D = 1;
}
从FooA1
-FooD
开始的v2是201,201:
syntax = "proto2";
message FooA1 {
optional double A = 1;
optional double B = 2;
oneof subtype {
FooC FooC = 201;
}
}
message FooC {
optional double C = 1;
oneof subtype {
FooD FooD = 201;
}
}
message FooD {
optional double D = 1;
}
答案 1 :(得分:0)
由于@Marc的解释,我找到了一种通过使用一些帮助器类来处理此特定用例的方法。
我将解决方案发布到这里对其他人有帮助。
版本2可以定义如下:
public class FooA
{
public double A { get; set; }
public double B { get; set; }
}
public class FooAFake
{
public double A { get; set; }
public double B { get; set; }
}
public class FooB : FooA
{
public double C { get; set; }
}
public class FooBFake : FooAFake
{
public double C { get; set; }
}
public class FooC : FooBFake
{
}
public class FooD : FooC
{
public double D { get; set; }
}
public class FooA1 : FooAFake
{
}
并以这种方式建模:
Model = RuntimeTypeModel.Create();
Model.Add(typeof(FooA), false)
.AddSubType(201, typeof(FooB))
//.AddSubType(202, typeof(FooA1))
.Add(1, "A")
.Add(2, "B");
Model[typeof(FooB)]
//.AddSubType(201, typeof(FooC))
.Add(1, "C");
Model[typeof(FooC)]
.AddSubType(201, typeof(FooD));
Model[typeof(FooD)]
.Add(1, "D");
Model.Add(typeof(FooAFake), false)
.AddSubType(201, typeof(FooBFake))
.AddSubType(202, typeof(FooA1))
.Add(1, "A")
.Add(2, "B");
Model[typeof(FooBFake)]
.AddSubType(201, typeof(FooC))
.Add(1, "C");