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

7
我有一个情景,需要在多个非常不同的系统之间同步数据。(数据本身相似,但不同系统上的表格格式却很不同。)为了协助这种同步过程,我创建了一个数据库表格,它存储了来自每个系统的对象哈希值以及相关信息和项目键。当任何一个系统中的对象哈希值发生改变时,我会更新另外一个系统中的哈希值。
我的数据库表格看起来像这样:
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(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());
}

但是...

尽管如此,我仍然希望能够高效且正确地从一个未标记为可序列化的类的对象中创建字节数组。有什么最好的方法可以实现这一点吗?

提前感谢您!


我忘了提到 MD5HashGenerator.GenerateKey(byte[] byteArray) 函数需要一个字节数组作为参数。 - Anthony Gatlin
1个回答

9

从一个没有标记为可序列化的类创建一个字节数组

您可以使用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快得多,并且产生的byte[]数组更小。

注意1:目前(在当前形式下)仅序列化类的公共属性,但对于您的用途来说看起来还不错。
注意2:这被认为是脆弱的,因为向Person添加新属性可能意味着您无法反序列化使用先前TypeModel序列化的Person对象。


1
嗯,这似乎是一个非常棒的解决方案。然而,我目前正在处理一个问题。我要序列化的对象在一个嵌套层次结构中,Protobuf 似乎在处理子对象时遇到了一些困难。(它似乎不会在添加父对象时自动将子对象添加到模型中。)接下来,我将在子对象上使用反射自动将它们添加到模型中,然后再向你汇报。总的来说,我真的很喜欢这个 Protobuf 库。它似乎运行速度非常快。非常感谢你与我们分享。 - Anthony Gatlin
1
@Anthony Gatlin,很遗憾您需要在要序列化的模型中指定每种类型。我已经给项目创建者(Marc Gravell)发了电子邮件,想知道是否有现成的方法或其他人是否已经完成了这项工作。同时,您建议的通过循环属性并将每个属性添加到非基元模型中似乎是可行的。 - wal
1
是的,Protobuf-net非常快。在这个网站背后使用了它。 - wal
1
重要提示:由于GetProperties()不能保证顺序,您应确保properties表示可重复的顺序。最简单的方法是只使用.Select(p => p.Name).OrderBy(x => x).ToArray()或在LINQ之后使用Array.Sort(properties); - Marc Gravell
1
@Marc,只是出于好奇,为什么属性的顺序很重要?在执行转换时,序列化程序是否使用属性位置而不是名称? - Anthony Gatlin
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接