使用 GZip 压缩 DataTable 序列化





3.00/5 (3投票s)
使用这个FastDataTable,您可以快速且良好地压缩序列化您的数据。
引言
在我们的项目工作中,这是一个三层业务解决方案,其中包含大量数据,在服务器和客户端之间流动,我们(指的是我们的开发人员部门)面临着压缩从服务器传输到客户端应用程序的数据的问题。 主要问题是序列化和反序列化的过程,因为它隐藏在 ADO.NET 的核心深处,并且很难从您的代码中更改它。 有一段时间我们使用了 FastDataSet 实现,其中序列化过程被完全重写。 长期以来,这是我们解决问题的最佳方案,但突然我们注意到,在某些情况下,当通过局域网将数据传递给报表生成器时,数据会损坏。 我们唯一的办法是理解如何使这种序列化对我们的数据简单、快速且无害。 看来我们做到了,所以我想和您分享一下这方面的知识。
背景
此代码的主要重点是使用内部 DataTable 方法,例如 DeserializeTableSchema
、DeserializeTableData
和 ResetIndexes
。 DataTable 类在反序列化中使用这三个函数。 我们也是如此,但就在使用这些函数之前,我们需要解压缩保存在 SerializationInfo 类中的数据。 序列化数据时,首先使用默认机制,然后在压缩它 - 仅此而已。 由于某些限制
Using the Code
所以,代码的主要部分是:方法 GetObjectData
,这是格式化程序用来将对象数据序列化到 SerializationInfo
对象中的方法。 此对象仅包含原始对象的所有字段、这些字段的类型及其名称。 我们在那里做了一些技巧 - 将真实数据获取到临时信息中,然后使用反射进入其私有字段,对其进行压缩,然后将这些数据放入真实的序列化信息中。
public override void GetObjectData(SerializationInfo info, StreamingContext context) {
SerializationInfo zipInfo = new SerializationInfo(typeof(FastDataTable),
new FormatterConverter());
base.GetObjectData(zipInfo, context);
FieldInfo fiData = typeof(SerializationInfo).GetField("m_data",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo fiMembers = typeof(SerializationInfo).GetField("m_members",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo fiTypes = typeof(SerializationInfo).GetField("m_types",
BindingFlags.NonPublic | BindingFlags.Instance);
object[] data = (object[])fiData.GetValue(zipInfo);
string[] members = (string[])fiMembers.GetValue(zipInfo);
Type[] types = (Type[])fiTypes.GetValue(zipInfo);
IFormatter formatter = new BinaryFormatter();
using(MemoryStream stream = new MemoryStream()) {
formatter.Serialize(stream, data);
formatter.Serialize(stream, members);
formatter.Serialize(stream, types);
formatter.Serialize(stream, zipInfo.MemberCount);
using(MemoryStream streamZip = new MemoryStream()) {
stream.Position = 0;
byte[] arr = null;
if(useCompression && stream.Length > compressThreshold) {
Compress(stream, streamZip);
arr = streamZip.ToArray();
} else {
arr = stream.ToArray();
}
info.AddValue("chunk", arr);
info.AddValue("compressed",
useCompression && stream.Length > compressThreshold);
}
}
}
第二个重要的方法是反序列化的重写构造函数。 在那里,我们解压缩原始的序列化信息,将真实数据放入信息对象中,然后再次使用反射,调用 DataTable 类用来反序列化表的方法。
protected FastDataTable(SerializationInfo info, StreamingContext context) {
MethodInfo miDeTS = typeof(FastDataTable).GetMethod(
"DeserializeTableSchema",
BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo miDeTD = typeof(FastDataTable).GetMethod("DeserializeTableData",
BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo miResIn = typeof(FastDataTable).GetMethod("ResetIndexes",
BindingFlags.NonPublic | BindingFlags.Instance);
using(MemoryStream stream = new MemoryStream()) {
byte[] bytes = (byte[])info.GetValue("chunk", typeof(byte[]));
useCompression = (bool)info.GetValue("compressed", typeof(bool));
compressedSize = bytes.Length;
if(useCompression) {
using(MemoryStream streamUnzip = new MemoryStream(bytes)) {
Decompress(streamUnzip, stream);
}
} else {
stream.Write(bytes, 0, bytes.Length);
}
stream.Position = 0;
originalSize = (int)stream.Length;
IFormatter formatter = new BinaryFormatter();
FieldInfo fiData = typeof(SerializationInfo).GetField(
"m_data", BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo fiMembers = typeof(SerializationInfo).GetField(
"m_members", BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo fiTypes = typeof(SerializationInfo).GetField("m_types",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo fiCurrMember = typeof(SerializationInfo).GetField(
"m_currMember", BindingFlags.NonPublic | BindingFlags.Instance);
object[] data = (object[])formatter.Deserialize(stream);
string[] members = (string[])formatter.Deserialize(stream);
Type[] types = (Type[])formatter.Deserialize(stream);
int curMember = (int)formatter.Deserialize(stream);
fiData.SetValue(info, data);
fiMembers.SetValue(info, members);
fiTypes.SetValue(info, types);
fiCurrMember.SetValue(info, curMember);
}
miDeTS.Invoke(this, new object[] { info, context, true });
miDeTD.Invoke(this, new object[] { info, context, 0 });
miResIn.Invoke(this, new object[] { });
}
所以,这就是我们使用的主要功能。 另外,不要忘记将 RemotingFormat
设置为 SerializationFormat.Binary
。
关注点
我们在编写此代码时面临的最大问题是,在 C# 中,我们无法在构造函数的任何地方调用基类构造函数 - 只能在开头调用。 因此,我们被迫使用所有这些反射东西。 .NET Reflector 在理解框架代码结构方面提供了很大的帮助 - 这是一个非常酷的工具!
历史
还没有 :)