将不可序列化类转换为字节数组

本文关键字:字节 字节数 数组 转换 序列化 | 更新日期: 2023-09-27 18:12:41

我有一个场景,我在多个非常不同的系统之间同步数据。(数据本身是相似的,但不同系统上的表具有完全不同的格式。)为了帮助实现这种同步,我有一个数据库表,其中存储来自每个系统的对象散列以及项键和其他相关信息。当一个系统中对象的哈希值发生变化时,我更新另一个系统。

我的数据库表是这样的

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得到的错误似乎表明这是真的。)

对于其中一个系统,我有能力使我所有的数据库对象序列化,这很好。哈希值生成了,一切都同步了,世界变得美丽了!

对于另一个系统,事情不是那么好。我从实体框架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();
}

但是哦不!如果对象(实体)是不可序列化的,这个例程会抛出另一个很好的(完全预期的)小异常。

所以…我修改了例程,并在方法定义中添加了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();
}

唯一的问题是,现在我又回到了原点,我所有的对象都需要序列化才能得到一个字节数组。

嗯。不好。

因此,我编写了一个hack来遍历对象的所有属性,并生成一个字符串表示,从中我可以构建一个字节数组。这是丑陋的和低效的,但它在某种程度上做到了。

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());
}

但是…

在所有这些之后,我仍然真正希望能够有效和正确地从一个类未标记为serializable的对象创建字节数组。实现这一目标的最佳方式是什么?

提前感谢!

将不可序列化类转换为字节数组

从类未标记为serializable的对象创建一个字节数组

您可以使用protobuf-net v2来完成此操作。下载压缩包,然后引用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 快得多,并且生成一个更小的byte[]数组

警告1这只会(以其当前形式)序列化类的公共属性,这看起来很适合您的使用。
警告2这被认为是脆弱的,因为向Person添加新属性可能意味着您无法反序列化与先前的TypeModel序列化的Person对象。