#c# #.net #serialization #bytearray #hashcode
#c# #.net #сериализация #bytearray #хэш-код
Вопрос:
У меня есть сценарий, в котором я синхронизирую данные между несколькими ОЧЕНЬ разными системами. (Сами данные похожи, но таблицы в разных системах имеют совершенно разные форматы.) Для облегчения этой синхронизации у меня есть таблица базы данных, в которой хранятся хэши объектов из каждой из систем вместе с ключами элементов и другой соответствующей информацией. Когда хэш объекта из любой системы изменяется, я обновляю другую.
Моя таблица базы данных выглядит примерно так.
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 (который я использую)) для объекта, вы должны иметь возможность преобразовать его в массив байтов.
И…
Кажется, что для преобразования объекта в байтовый массив он должен быть сериализуемым. (По крайней мере, это то, что я прочитал, и ошибки, которые я получаю.Похоже, NET указывает, что это правда.)
Для одной из систем у меня есть возможность сделать все мои объекты базы данных сериализуемыми, так что это здорово. Генерируются хэши, все синхронизируется, и мир прекрасен!
Для другой системы все не так здорово. Мне передается контекст базы данных из модели entity framework 4 (code first), и объекты НЕ сериализуются.
Когда я пытаюсь выполнить преобразование в байт, используя что-то вроде следующего, .NET жалуется и устраивает небольшую истерику — все время отказываясь предоставить мне симпатичный маленький байтовый массив, о котором я так вежливо просил.
foreach(var dataItem in context.TableName)
{
var byteArray = (byte[]) dataItem;
}
ОК. Нет проблем.
У меня есть хороший маленький метод расширения, который, как я думал, может помочь.
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();
}
Но нет! Если объект (сущность) не сериализуем, эта процедура выдает мне еще одно приятное маленькое (и вполне ожидаемое) исключение.
Итак… Я изменяю процедуру и добавляю предложение 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. Я забыл упомянуть, что MD5HashGenerator . Функция GenerateKey(byte[] ByteArray) принимает массив байтов в качестве своего. параметр.
Ответ №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();
}
Сериализатор protobuf-net сериализует намного быстрее и создает меньший массив байтов [], чем BinaryFormatter
предостережение 1 Это приведет только (в его текущей форме) к сериализации общедоступных свойств вашего класса, что выглядит нормально для вашего использования.
предостережение 2 Это считается хрупким, поскольку добавление нового свойства в Person
может означать, что вы не сможете десериализовать Person
объект, который был сериализован с предыдущим TypeModel
.
Комментарии:
1. Wal, это выглядит ФАНТАСТИЧЕСКИМ решением. Однако в настоящее время я работаю над одной проблемой. Объекты, которые я сериализую, находятся во вложенной иерархии, и у Protobuf, похоже, возникают некоторые трудности с дочерними объектами. (Похоже, он не добавляет их автоматически в модель при добавлении родительского элемента.) Далее я буду использовать отражение дочерних объектов, чтобы автоматически добавлять их в модель, а затем отчитываться. В целом, мне действительно нравится эта библиотека Protobuf. Это кажется невероятно быстрым. Большое спасибо, что поделились этим.
2. @Anthony Gatlin К сожалению, вам придется указать каждый тип в модели, которую вы хотите сериализовать. Я отправил электронное письмо создателю проекта (Марку Гравеллу), чтобы узнать, существует ли существующий способ или кто-то другой это сделал. Между тем, то, что вы предлагаете, перебирая свойства и добавляя каждое из них в модель, которая не является примитивной, звучит достижимо.
3. и да, Protobuf-net работает очень быстро. Он используется этим сайтом под капотом.
4. Важно: поскольку
GetProperties()
это не гарантирует порядок, вы должны убедиться, чтоproperties
он представляет собой повторяющийся порядок. Самым простым подходом было бы просто использовать либо.Select(p => p.Name).OrderBy(x => x).ToArray()
, либоArray.Sort(properties);
(после LINQ).5. @Marc, просто из любопытства, почему порядок свойств имеет значение? Использует ли сериализатор позицию свойства, а не имя при выполнении преобразования?