6

protobuf-net (Marc Gravell の実装) を使用して、このユース ケースに対処する方法を決定しようとしています。

  • バージョン1と見なされるクラスAがあります
  • クラス A のインスタンスがディスクにシリアル化されました
  • これで、クラス A のバージョン 2 と見なされるクラス B ができました (クラス 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 は、オブジェクトの最初の反復であり、積極的に使用されています。データは、シリアル化にクラス A を使用して v1 形式で存在します。

私たちのやり方の誤りに気づいた後、クラス B と呼ばれる新しい構造を作成します。A と B の間には非常に多くの変更があるため、元のクラス A を適応させるのではなく、B を作成する方がよいと感じています。

しかし、私たちのアプリケーションはすでに存在し、クラス A がデータのシリアル化に使用されています。変更を世界に公開する準備ができましたが、最初にバージョン 1 で作成されたデータを (クラス A を使用して) デシリアライズし、クラス B としてインスタンス化する必要があります。データが欠落している場合は B を使用しますが、クラス A インスタンスからクラス B にデータを移行する必要があります。クラス B インスタンスが作成されると、アプリケーションはそのデータをクラス B 形式 (バージョン 2) で再度シリアル化します。

将来、クラス B に変更を加えると想定しており、おそらく新しいクラス「C」で、バージョン 3 に反復できるようにしたいと考えています。2 つの目標があります。既存のデータに対処することと、将来の移行に備えてオブジェクトを準備することです。

既存の「遷移」属性 (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 のうち null でない方を選択するだけで、おそらくそれらの間に変換演算子を追加できます。ただし、古いデータに裸の A しかない場合、これは難しくなります。私が考えることができる2つのアプローチがあります:

  • 互換性のために多くの 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 を介して、またはType2 つの非ジェネリック API からのインスタンスを介して (Serializer.NonGeneric.DeserializeまたはRuntimeTypeModel.Default.Deserialize- いずれにせよ、同じ場所に到達します。実際には、ジェネリックと非ジェネリックのどちらが最も便利であるかのケースです)。

A次に、 / B/間の変換コードが必要になりCます。独自のカスタム オペレータ / メソッドを使用するか、自動マッパーなどを使用します。

コードを動かしたくない場合はProtoReader、次のこともできます。

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

および runDeserialize<VersionStub>にアクセスできますVersion。これにより、タイプ固有の逆シリアル化を実行できます。ここでの主な違いは、ProtoReaderコードがバージョン番号を取得するとすぐに短絡できることです。

于 2013-01-28T22:47:16.543 に答える
2

期待される練習はありませんが、これは私がすることです。

V1 クラスに引き続きアクセスできる場合は、V2 インスタンスを提供する V1 クラスにプロパティを追加します。

あなた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 に答える