使用OpenXML/EPPlus合并Excel工作表遇数据丢失等问题求助
解决EPPlus合并后原工作表数据丢失的问题
你的EPPlus代码中,原工作表数据丢失大概率是因为没有将内存中修改后的工作簿内容正确同步到流中,或者GetAsByteArray()没有正确导出完整内容。可以修改代码如下:
private byte[] MergeExcelWorksheets(byte[] array1, byte[] array2) { using (MemoryStream ms1 = new MemoryStream(array1)) using (ExcelPackage package1 = new ExcelPackage(ms1)) using (MemoryStream ms2 = new MemoryStream(array2)) using (ExcelPackage package2 = new ExcelPackage(ms2)) { var workbook1 = package1.Workbook; var workbook2 = package2.Workbook; foreach (var sourceSheet in workbook2.Worksheets) { if (!workbook1.Worksheets.Any(sheet => sheet.Name.Equals(sourceSheet.Name, StringComparison.OrdinalIgnoreCase))) { // 复制工作表,包含数据和样式 workbook1.Worksheets.Add(sourceSheet.Name, sourceSheet); } } // 显式保存工作簿到内存流 package1.Save(); // 重置流位置到起始处 ms1.Position = 0; // 返回更新后的字节数组 return ms1.ToArray(); } }
关键修改点:
- 替换
GetAsByteArray()为先保存到原内存流,再读取流内容:GetAsByteArray()内部会创建新内存流导出内容,但部分场景下可能因工作簿加载异常导致原内容丢失;直接保存到初始流并读取更可靠。 - 工作表名称判断加入
StringComparison.OrdinalIgnoreCase,避免大小写差异导致重复添加。
修复OpenXML SDK的内存流与类型匹配问题
针对你最初用OpenXML遇到的问题,可做如下调整:
public byte[] GetBytes() { var array1 = ....ToArray(); var array2 = ....ToArray(); using (MemoryStream firstStream = new MemoryStream(array1)) using (SpreadsheetDocument firstDocument = SpreadsheetDocument.Open(firstStream, true)) using (MemoryStream secondStream = new MemoryStream(array2)) using (SpreadsheetDocument secondDocument = SpreadsheetDocument.Open(secondStream, false)) { var firstSheets = firstDocument.WorkbookPart.Workbook.Sheets; uint nextSheetId = (uint)(firstSheets.Elements<Sheet>().Count() + 1); foreach (Sheet secondSheet in secondDocument.WorkbookPart.Workbook.Sheets.Elements<Sheet>()) { // 避免强制转换导致类型不匹配,先判断部件类型 var sourcePart = secondDocument.WorkbookPart.GetPartById(secondSheet.Id); if (sourcePart is WorksheetPart secondWorksheetPart) { // 克隆工作表部件,同时复制关联的子部件(如样式、注释等) WorksheetPart clonedWorksheetPart = firstDocument.WorkbookPart.AddPart(secondWorksheetPart); Sheet clonedSheet = new Sheet { Id = firstDocument.WorkbookPart.GetIdOfPart(clonedWorksheetPart), SheetId = nextSheetId++, Name = secondSheet.Name }; firstSheets.Append(clonedSheet); } // 可选:处理图表表等其他类型的工作表 else if (sourcePart is ChartsheetPart chartPart) { ChartsheetPart clonedChartPart = firstDocument.WorkbookPart.AddPart(chartPart); Sheet clonedSheet = new Sheet { Id = firstDocument.WorkbookPart.GetIdOfPart(clonedChartPart), SheetId = nextSheetId++, Name = secondSheet.Name }; firstSheets.Append(clonedSheet); } } // 保存工作簿,无需手动调整内存流容量(MemoryStream会自动扩容) firstDocument.WorkbookPart.Workbook.Save(); firstStream.Position = 0; return firstStream.ToArray(); } }
关键修复点:
- 类型安全处理:不再强制转换
GetPartById的结果,通过类型判断处理不同类型的工作表部件,避免类型不匹配异常。 - 移除手动内存流容量调整:
MemoryStream会自动根据内容大小扩容,手动设置反而可能引发异常。 - 统一SheetId生成:提前计算初始SheetId并递增,避免重复计算导致的ID冲突。
- 重置流位置:返回前将流位置设为起始处,确保读取完整内容。
内容的提问来源于stack exchange,提问作者byteram
相关产品推荐
相关产品推荐

