基于.NET 2.0无外部库解压Zip获取XML,适配SQL Server 2008 CLR
好消息是,在.NET 2.0环境下不用外部库解压ZIP是可行的——虽然没有后来.NET版本里的ZipArchive那么方便,但我们可以手动解析ZIP文件的底层格式,配合.NET 2.0自带的DeflateStream来完成解压。下面针对你的SQL Server 2008 CLR场景给出具体方案:
方案一:纯.NET 2.0内存解压(推荐)
这个方案完全依赖.NET 2.0自带的类库,不需要外部依赖,也能避免文件系统操作(更适合SQL CLR的权限限制)。核心思路是手动解析ZIP文件的Local File Header结构,提取每个文件的压缩数据后用DeflateStream解压(ZIP最常用的压缩方式就是Deflate)。
实现代码
首先定义一个简单的类来存储ZIP内的文件信息:
public class ZipEntry { public string FileName { get; set; } public byte[] Data { get; set; } }
然后实现解压方法,直接从ZIP的字节数组中提取所有文件:
using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; public static class ZipExtractor { public static List<ZipEntry> ExtractZip(byte[] zipData) { var entries = new List<ZipEntry>(); using (var zipStream = new MemoryStream(zipData)) using (var reader = new BinaryReader(zipStream)) { while (zipStream.Position < zipStream.Length) { // 读取ZIP文件的Local File Header签名(固定为0x04034B50) uint headerSignature = reader.ReadUInt32(); if (headerSignature != 0x04034B50) { // 遇到非Local File Header(比如Central Directory或结束记录),终止循环 break; } // 跳过不需要的头字段:版本、标志等 reader.ReadUInt16(); // 解压所需最低版本 reader.ReadUInt16(); // 通用标志位 ushort compressionMethod = reader.ReadUInt16(); // 压缩方法 reader.ReadUInt16(); // 文件修改时间 reader.ReadUInt16(); // 文件修改日期 reader.ReadUInt32(); // CRC32校验值 uint compressedSize = reader.ReadUInt32(); // 压缩后大小 uint uncompressedSize = reader.ReadUInt32(); // 解压后大小 ushort fileNameLength = reader.ReadUInt16(); // 文件名长度 ushort extraFieldLength = reader.ReadUInt16(); // 额外字段长度 // 读取文件名 string fileName = Encoding.UTF8.GetString(reader.ReadBytes(fileNameLength)); // 跳过额外字段 reader.ReadBytes(extraFieldLength); // 读取压缩数据并解压 byte[] compressedBytes = reader.ReadBytes((int)compressedSize); byte[] uncompressedBytes; if (compressionMethod == 0) { // 文件未压缩,直接使用原数据 uncompressedBytes = compressedBytes; } else if (compressionMethod == 8) { // Deflate压缩:注意跳过ZIP格式额外的2字节头 using (var compressedStream = new MemoryStream(compressedBytes, 2, compressedBytes.Length - 2)) using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress)) using (var outputStream = new MemoryStream()) { deflateStream.CopyTo(outputStream); uncompressedBytes = outputStream.ToArray(); } } else { throw new NotSupportedException($"不支持的压缩方式:{compressionMethod}"); } entries.Add(new ZipEntry { FileName = fileName, Data = uncompressedBytes }); } } return entries; } }
在你的CLR代码中使用
你可以去掉原有的临时文件写入逻辑,直接处理下载到的byte[]:
byte[] zipBinary = GetStreamFileResult(..); var zipEntries = ZipExtractor.ExtractZip(zipBinary); foreach (var entry in zipEntries) { // 筛选XML文件 if (entry.FileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) { // 将字节数组转为XML字符串 string xmlContent = Encoding.UTF8.GetString(entry.Data); // 接下来可以将XML插入SQL表,或进行其他处理 // ... } }
注意事项
- 权限适配:如果全程在内存中处理(不读写文件系统),你的CLR程序集设置为
SAFE权限即可;如果需要写入文件,需设置为EXTERNAL_ACCESS。 - ZIP格式支持:这个实现处理的是标准ZIP格式,不支持加密ZIP、分卷ZIP或ZIP64(大于4GB的文件),但对于XML文件场景足够使用。
- 文件名编码:如果遇到非UTF8编码的文件名乱码,可以将
Encoding.UTF8替换为Encoding.GetEncoding(437)(ZIP默认的DOS编码)。
方案二:依赖Windows系统工具(备选)
如果你的SQL Server服务器允许执行外部进程,也可以调用Windows自带的expand.exe工具来解压ZIP。但这个方案需要更高的权限,且依赖系统环境,稳定性不如方案一。
示例代码
string tempZipPath = Path.Combine(Path.GetTempPath(), "temp.zip"); string extractDir = Path.Combine(Path.GetTempPath(), "extracted_xmls"); Directory.CreateDirectory(extractDir); // 写入临时ZIP文件 File.WriteAllBytes(tempZipPath, varbinary); // 调用expand.exe解压 string expandExePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "expand.exe"); System.Diagnostics.Process.Start(expandExePath, $"\"{tempZipPath}\" \"{extractDir}\\*\"")?.WaitForExit(); // 读取解压后的XML文件 foreach (string xmlFile in Directory.GetFiles(extractDir, "*.xml")) { string xmlContent = File.ReadAllText(xmlFile); // 处理XML... } // 清理临时文件 File.Delete(tempZipPath); Directory.Delete(extractDir, true);
注意事项
- 你的CLR程序集必须设置为
UNSAFE权限才能执行外部进程。 - 确保
expand.exe存在于服务器的System目录中(Windows系统默认自带)。 - 临时文件路径需要确保SQL Server服务账户有读写权限。
内容的提问来源于stack exchange,提问作者Manos
相关产品推荐
相关产品推荐

