【问题标题】:Converting Non-Serializable Classes to a Byte Array将不可序列化的类转换为字节数组
【发布时间】:2011-12-16 21:58:27
【问题描述】:

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

我的数据库表看起来像这样。

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 哈希值(这是我正在使用的)),您需要能够将其转换为 byte数组

还有……

似乎为了将对象转换为字节数组,它必须是可序列化的。 (至少这是我读到的内容,我从 .NET 得到的错误似乎表明这是真的。)

对于其中一个系统,我能够使我的所有数据库对象可序列化,这很棒。生成哈希,同步一切,世界很美!

对于另一个系统,事情没那么好。我从entity framework 4(代码优先)模型传递了一个数据库上下文,并且实体未序列化

当我尝试使用类似以下的内容将其转换为字节时,.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) 函数使用一个字节数组作为它的。参数。

标签: c# .net serialization bytearray hashcode


【解决方案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 更快地序列化 并生成更小的 byte[] 数组

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

【讨论】:

  • 哇,这看起来是一个很棒的解决方案。但是,我目前正在解决一个问题。我正在序列化的对象位于嵌套层次结构中,Protobuf 似乎对子对象有一些困难。 (添加父对象时似乎不会自动将它们添加到模型中。)接下来,我将使用子对象上的反射将它们自动添加到模型中,然后返回报告。总而言之,我真的很喜欢这个 Protobuf 库。它似乎快得惊人。非常感谢您的分享。
  • @Anthony Gatlin 不幸的是,您必须指定要序列化的模型中的每种类型。我已通过电子邮件向项目创建者 (Marc Gravell) 发送电子邮件,以了解是否存在现有方法或其他人是否已经完成。同时,您通过循环遍历属性并将每个属性添加到模型中的建议听起来是可以实现的。
  • 是的,Protobuf-net 非常快。本网站在后台使用它。
  • 重要:由于GetProperties()不保证顺序,您应该确保properties代表可重复的顺序。最简单的方法是使用 .Select(p =&gt; p.Name).OrderBy(x =&gt; x).ToArray()Array.Sort(properties);(在 LINQ 之后)。
  • @Marc,出于好奇,为什么属性顺序很重要?序列化程序在执行转换时是否使用属性位置而不是名称?
猜你喜欢
  • 2014-03-24
  • 2020-07-12
  • 1970-01-01
  • 2014-12-19
  • 2013-02-13
  • 1970-01-01
  • 2011-03-11
  • 2021-06-09
  • 1970-01-01
相关资源
最近更新 更多