C# 将不可序列化的类转换为字节数组
我有一个场景,在多个非常不同的系统之间同步数据。(数据本身是相似的,但不同系统上的表具有完全不同的格式。)为了帮助进行同步,我有一个数据库表,它存储来自每个系统的对象哈希以及项键和其他相关信息。当来自任一系统的对象的哈希值发生变化时,我会更新另一个系统 我的数据库表看起来像这样C# 将不可序列化的类转换为字节数组,c#,.net,serialization,bytearray,hashcode,C#,.net,Serialization,Bytearray,Hashcode,我有一个场景,在多个非常不同的系统之间同步数据。(数据本身是相似的,但不同系统上的表具有完全不同的格式。)为了帮助进行同步,我有一个数据库表,它存储来自每个系统的对象哈希以及项键和其他相关信息。当来自任一系统的对象的哈希值发生变化时,我会更新另一个系统 我的数据库表看起来像这样 CREATE TABLE [dbo].[SyncHashes]( [SyncHashId] [int] IDENTITY(1,1) NOT NULL, [ObjectName] [nvarchar](50
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中得到的错误似乎表明这是真的。)
对于其中一个系统,我能够使我的所有数据库对象都可序列化,所以这很好。哈希得到生成,一切都得到同步,世界是美好的
对于另一个系统来说,情况并不是那么好。从EntityFramework4(代码优先)模型向我传递了一个数据库上下文,并且这些实体没有序列化
当我尝试使用如下内容转换为字节时,.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();
}
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());
}
公共静态字节[]对象字节数组(此T obj)
{
if(obj==null)
返回null;
BinaryFormatter bf=新的BinaryFormatter();
MemoryStream ms=新的MemoryStream();
bf.序列化(ms,obj);
返回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();
}
publicstaticbyte[]ObjectToByteArray(此T obj),其中T:ISerializable
{
if(obj==null)
返回null;
BinaryFormatter bf=新的BinaryFormatter();
MemoryStream ms=新的MemoryStream();
bf.序列化(ms,obj);
返回ToArray女士();
}
唯一的问题是,现在我又回到了原点,我的所有对象都需要序列化才能获得字节数组
嗯,不太好
因此,我编写了一个hack来迭代对象的所有属性,并生成一个字符串表示,从中我可以构建一个字节数组。这是丑陋和低效的,但它有点做的把戏
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();
}
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());
}
公共静态字符串ComputeMD5Hash(此T输入)
{
StringBuilder sb=新的StringBuilder();
类型t=input.GetType();
PropertyInfo[]properties=t.GetProperties();
foreach(属性中的var属性)
{
某人追加(财产名称);
某人加上(“|”);
对象值=property.GetValue(输入,null);
if(值!=null)
{
附加(价值);
}
某人加上(“|”);
}
返回MD5HashGenerator.GenerateKey(sb.ToString());
}
但是
在所有这些之后,我仍然希望能够高效、正确地从一个类未标记为可序列化的对象创建一个字节数组。实现这一目标的最佳方式是什么
提前谢谢你
从类未标记为可序列化的对象创建字节数组
您可以使用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网络序列化程序的序列化速度比BinaryFormatter快得多,生成的字节[]数组比BinaryFormatter小得多
注意事项1这将仅(以其当前形式)序列化类的公共属性,看起来适合您的使用。注意事项2这被认为是脆弱的,因为向
Person
添加新属性可能意味着您无法对使用先前的TypeModel
序列化的Person
对象进行反序列化。我忘了提到MD5HashGenerator.GenerateKey(byte[]byteArray)函数将字节数组作为其属性。Wal,这看起来是一个非常好的解决方案。然而,我目前正在处理一个问题。我序列化的对象位于嵌套的层次结构中,Protobuf似乎在处理子对象时遇到了一些困难。(在添加父对象时,似乎不会自动将它们添加到模型中。)接下来,我将使用子对象上的反射自动将它们添加到模型中,然后返回报告。总之,我真的很喜欢这个Protobuf库。它似乎快得惊人。非常感谢您的分享。@Anthony Gatlin您必须在模型中指定要序列化的每种类型。我已经给项目创建者(Marc Gravell)发了一封电子邮件,询问是否有现成的方法,或者是否有其他人已经这样做了。同时,通过循环属性并将每个属性添加到模型中,您的建议听起来是不可能实现的。是的,Protobuf网络非常快。此网站暗中使用它。重要提示:由于GetProperties()
不能保证订单,因此您应该确保properties
代表可重复的订单。最简单的方法是只使用.Select(p=>p.Name).OrderBy(x=>x).ToArray()
,或Array.Sort(属性)代码>(在LINQ之后)。@Marc,出于好奇,为什么财产顺序很重要?序列化程序在执行转换时是否使用属性位置而不是名称?