Преобразование несериализуемых классов в байтовый массив - PullRequest
5 голосов
/ 28 октября 2011

У меня есть сценарий, в котором я синхронизирую данные между несколькими ОЧЕНЬ разными системами. (Сами данные похожи, но таблицы в разных системах имеют совершенно разные форматы.) Чтобы помочь с этой синхронизацией, у меня есть таблица базы данных, в которой хранятся хеши объектов из каждой системы, а также ключи элементов и другая соответствующая информация. Когда хэш объекта из одной системы изменяется, я обновляю другой.

Моя таблица базы данных выглядит примерно так.

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 хеш (что я и использую)) для объекта, вы должны иметь возможность преобразовать его в байтовый массив .

И ...

Похоже, что для преобразования объекта в байтовый массив он должен быть serializable . (По крайней мере, это то, что я прочитал, и ошибки, которые я получаю от .NET, похоже, указывают на то, что это правда.)

Для одной из систем у меня есть возможность сделать все мои объекты базы данных сериализуемыми, так что это здорово. Генерируются хеши, все синхронизируется, и мир прекрасен!

Для другой системы все не так здорово . Мне передали контекст базы данных из модели сущностей 4 (сначала код) и объекты НЕ сериализованы .

Когда я пытаюсь преобразовать как байт, используя что-то вроде следующего, .NET жалуется и бросает небольшую истерику - все время отказываясь давать мне симпатичный маленький байтовый массив, который я так вежливо просил.

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

Ok. Нет проблем.

У меня есть хороший метод расширения, который, я думал, может сработать.

public static byte[] ObjectToByteArray<T>(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<T>(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<T>(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());
}

Но ...

В конце концов, то, что я все еще действительно хотел бы иметь, - это эффективно и правильно создавать байтовый массив из объекта, класс которого не помечен как сериализуемый . Каков наилучший способ сделать это?

Заранее спасибо!

1 Ответ

7 голосов
/ 28 октября 2011

создать байтовый массив из объекта, класс которого не помечен как сериализуемый

Вы можете использовать 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();
}

Сериализатор protobuf-net будет сериализировать намного быстрее и производить меньший массив байтов [], чем BinaryFormatter

caveat 1 Это только (в его текущей форме) сериализует общедоступные свойства вашего класса, что выглядит нормально для вашего использования.
caveat 2 Это считается хрупким, поскольку добавление нового свойства в Person может означать, что вы не можете десериализовать объект Person, который был сериализован с предыдущим TypeModel.

...