使用ICSharpCode.SharpZipLib.Zip压缩SQL FileStream文件下载时异常解决
解决SqlFileStream读取文件生成Zip包时的CloseEntry异常问题
你遇到的这个异常"size was 845941, but I expected 16 at ICSharpCode.SharpZipLib.Zip.ZipOutputStream.CloseEntry()",核心原因一眼就能看出来:你给ZipEntry设置的Size值完全不是文件的实际大小。
看你代码里的这行:
zipEntry.Size = model123.serverTxn.Length;
这里的serverTxn是从GET_FILESTREAM_TRANSACTION_CONTEXT()获取的事务上下文二进制数据,它的长度只有16字节左右,但你实际从SqlFileStream读取的文件大小是845941字节。SharpZipLib会严格校验写入的字节数和预先设置的ZipEntry.Size是否一致,一旦不匹配就会抛出这个异常。
修复步骤
修改SQL查询,获取文件真实大小
在你的SELECT语句中添加LEN(FileData) AS FileSize,这样就能拿到FileStream中存储的文件实际字节数:SELECT FileData.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(), FileType, LEN(FileData) AS FileSize FROM MyFiles WHERE FileId = @FileId读取文件大小并赋值给ZipEntry
在读取SqlDataReader时,把FileSize字段取出来,再设置给zipEntry.Size:model123.fileSize = rdr.GetInt64(3); // 用Int64适配GB级大文件 // ... zipEntry.Size = model123.fileSize;优化代码细节(可选但推荐)
- 把TransactionScope移到外层,避免每个文件都创建一次事务,提升性能
- 去掉手动调用的
sfs.Close(),using语句会自动释放资源
修改后的完整代码
string zipFileName = "ZipName.zip"; Response.ContentType = "application/zip"; Response.AddHeader("content-disposition", $"attachment; fileName={zipFileName}"); // 加上attachment确保浏览器触发下载 byte[] buffer = new byte[4096]; ZipOutputStream zipOutputStream = new ZipOutputStream(Response.OutputStream); zipOutputStream.SetLevel(3); string cs = System.Configuration.ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; // 事务放到外层,减少重复创建开销 using (TransactionScope ts = new TransactionScope()) { using (System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(cs)) { conn.Open(); foreach (Filec file1 in Files) { StreamModel model123 = new StreamModel(); const string SelectTSql = @" SELECT FileData.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(), FileType, LEN(FileData) AS FileSize FROM MyFiles WHERE FileId = @FileId"; using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(SelectTSql, conn)) { cmd.Parameters.Add("@FileId", System.Data.SqlDbType.Int).Value = Convert.ToInt32(file1.FileId); using (System.Data.SqlClient.SqlDataReader rdr = cmd.ExecuteReader()) { rdr.Read(); model123.serverPath = rdr.GetSqlString(0).Value; model123.serverTxn = rdr.GetSqlBinary(1).Value; model123.filetype = rdr.GetSqlString(2).Value; model123.fileSize = rdr.GetInt64(3); // 获取真实文件大小 rdr.Close(); } } ZipEntry zipEntry = new ZipEntry(ZipEntry.CleanName(file1.FileName)); zipEntry.Size = model123.fileSize; // 设置正确的文件大小 zipOutputStream.PutNextEntry(zipEntry); byte[] buffer3 = new byte[4096]; using (System.Data.SqlTypes.SqlFileStream sfs = new System.Data.SqlTypes.SqlFileStream( model123.serverPath, model123.serverTxn, FileAccess.Read)) { int bytesRead; while ((bytesRead = sfs.Read(buffer3, 0, buffer3.Length)) > 0) { zipOutputStream.Write(buffer3, 0, bytesRead); } } zipOutputStream.CloseEntry(); // 现在不会再抛出异常了 } ts.Complete(); } } zipOutputStream.Close(); Response.Flush(); Response.End();
额外说明
如果你不想在SQL里查询文件大小,也可以在打开SqlFileStream后直接通过sfs.Length获取真实大小,再赋值给zipEntry.Size,两种方式都能解决问题。
内容的提问来源于stack exchange,提问作者Jilani pasha
相关产品推荐
相关产品推荐

