非直列化可能クラスのバイト配列への変換

私は複数の非常に異なるシステム間でデータを同期させるシナリオを持っています。このデータの同期は、各システムのオブジェクトハッシュとアイテムキーやその他の関連情報を格納するデータベーステーブルを持っています。いずれかのシステムからのオブジェクトのハッシュが変わると、私はもう一方を更新します。

私のデータベーステーブルは次のようになります。

CREATE TABLE [dbo].[SyncHashes](
    [SyncHashId] [int] IDENTITY(1,1) NOT NULL,
    [ObjectName] [nvarchar](50) NULL,
    [MappingTypeValue] [nvarchar](25) NULL,
    [MappingDirectionValue] [nvarchar](25) NULL,
    [SourceSystem] [nvarchar](50) NULL,
    [SourceKey] [nvarchar](200) NULL,
    [SourceHash] [nvarchar](50) NULL,
    [TargetSystem] [nvarchar](50) NULL,
    [TargetKey] [nvarchar](200) NULL,
    [TargetHash] [nvarchar](50) NULL,
    [UpdateNeededValue] [nvarchar](max) NULL,
    [CreatedOn] [datetime] NULL,
    [ModifiedOn] [datetime] NULL,
    [Version] [timestamp] NOT NULL, 
    [IsActive] [bit] NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [SyncHashId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

ここまでは順調ですね。しかし...

オブジェクトの MD5ハッシュ(これは私が使用しているものです)などのハッシュを効果的に計算するには、それをバイト配列に変換できる必要があります。 em>。

そして...

オブジェクトをバイト配列に変換するには、直列化可能でなければならないようです。 (少なくとも、これは私が読んだものであり、.NETから得られるエラーはそれが真実であると思われるようです。)

いずれかのシステムでは、すべてのデータベースオブジェクトをシリアライズ可能にする機能があります。ハッシュが生成され、すべてが同期され、世界は美しいです!

別のシステムでは、あまり大きくないです。 エンティティフレームワーク4 (コードファースト)モデルからデータベースコンテキストを渡され、エンティティは直列化されていません

私が次のようなものを使ってバイトとしてキャストしようとすると、.NETは文句を叫び、小さな悩みを吹き飛ばします。

foreach(var dataItem in context.TableName)
{
    var byteArray = (byte[]) dataItem;
}

OK。問題ない。

私は自分自身に、トリックをするかもしれないと思っていたちょっとした拡張メソッドを持っています。

public static byte[] ObjectToByteArray(this T obj)
{
    if (obj == null)
        return null;
    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();

    bf.Serialize(ms, obj);
    return ms.ToArray();
}

しかし、ああ、いいえ!オブジェクト(Entity)がシリアライズ可能でない場合、このルーチンはもう少し良い(そして完全に予想される)例外を投げます。

だから...私はルーチンを修正し、where節をメソッド定義に追加します。

public static byte[] ObjectToByteArray(this T obj) where T : ISerializable
{
    if (obj == null)
        return null;
    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();

    bf.Serialize(ms, obj);
    return ms.ToArray();
}

唯一の問題は、バイト配列を取得するために、すべてのオブジェクトを直列化可能にする必要がある正方形に戻っていることです。

うーん。良くない。

そこで私は、オブジェクトのすべてのプロパティを繰り返し処理し、バイト配列を構築できる文字列表現を生成するためのハックをまとめました。それは醜いと不合理でしたが、それはある種のトリックでした。

public static string ComputeMD5Hash(this T input)
{
    StringBuilder sb = new StringBuilder();

    Type t = input.GetType();
    PropertyInfo[] properties = t.GetProperties();

    foreach (var property in properties)
    {
        sb.Append(property.Name);
        sb.Append("|");
        object value = property.GetValue(input, null);
        if (value != null)
        {
            sb.Append(value);
        }
        sb.Append("|");
    }

    return MD5HashGenerator.GenerateKey(sb.ToString());
}

しかし...

結局のところ、まだ本当にできたいと思っているのは、クラスが直列化可能とマークされていないオブジェクトからバイト配列を効率的かつ適切に作成するです。これを達成する最良の方法は何ですか?

前もって感謝します!

5
私は、MD5HashGenerator.GenerateKey(byte [] byteArray)関数がバイト配列を引数としてとることを忘れていました。パラメータ。
追加された 著者 Anthony Gatlin,

1 答え

クラスが直列化可能とマークされていないオブジェクトからバイト配列を作成する

protobuf-net v2を使用してこれを達成できます。 zipをダウンロードし、 protobuf-net アセンブリを参照してください。

シリアライズしたい単純なクラス定義を考えてみましょう:

public class Person
{
    public string Firstname { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
}

これをバイト配列としてシリアル化することができます:

var person = new Person {Firstname = "John", Surname = "Smith", Age = 30};
var model = ProtoBuf.Meta.TypeModel.Create();
//add all properties you want to serialize. 
//in this case we just loop over all the public properties of the class
//Order by name so the properties are in a predictable order
var properties = typeof (Person).GetProperties().Select(p => p.Name).OrderBy(name => name).ToArray();
model.Add(typeof(Person), true).Add(properties);

byte[] bytes;

using (var memoryStream = new MemoryStream())
{
    model.Serialize(memoryStream, person);
    bytes = memoryStream.GetBuffer();
}

The protobuf-net serializer will serialize much faster and produce a smaller byte[] array than BinaryFormatter

caveat 1 This will only (in its current form) serialize the public properties of your class, which looks ok for your usage.
caveat 2 This is considered brittle because adding a new property to Person may mean you are unable to deserialize a Person object that was serialized with the prior TypeModel.

7
追加された
@Anthony Gatlin残念なことに、直列化したいモデルの各タイプを指定する必要があります。私はプロジェクトクリエーター(Marc Gravell)に電子メールを送信して、既存の方法があるかどうか、または他の人がそれを行っているかどうかを調べました。一方、プロパティをループして、それぞれのモデルを原始的なサウンドではないモデルに追加することで、あなたが提案しているのは達成可能なサウンドです。
追加された 著者 wal,
はい、Protobuf-netは非常に高速です。フードの下でこのウェブサイトで使用されています。
追加された 著者 wal,
@Marc Gravell、ありがとう、答えが更新されました。
追加された 著者 wal,
Wal、これは素晴らしいソリューションに見えます。しかし、私は現在、1つの問題を解決しています。私が直列化しているオブジェクトはネストされた階層にあり、Protobufはその子オブジェクトにいくつかの問題を抱えているようです。 (親を追加するときにモデルに自動的にモデルを追加しているようではありません)次に、子オブジェクトのリフレクションを使用してモデルに自動的に追加してから報告します。私はこのProtobufライブラリが大好きです。それは驚くほど速く見える。それを共有してくれてありがとう。
追加された 著者 Anthony Gatlin,
@Marc、ちょうど好奇心から、物件の注文はなぜ問題なのですか?シリアライザは、変換を実行するときに名前ではなくプロパティの位置を使用しますか?
追加された 著者 Anthony Gatlin,
重要: GetProperties()は順序を保証しないので、プロパティが反復可能な順序を表すようにする必要があります。最も簡単なアプローチは、 .Select(p => p.Name).OrderBy(x => x).ToArray()または Array.Sort(properties); (LINQの後)。
追加された 著者 Marc Gravell,
@Anthonyプロトコルバッファは本質的に数字ベースです。 MetaTypeにメンバーを伝えるときに、それを明示的な番号を付けることも、暗黙の昇順番号を割り当てることもできます。名前は決して関与しません。
追加された 著者 Marc Gravell,