基于C# OpenXML合并多Excel工作表,保留透视表等元素
使用OpenXML合并含筛选、透视表的Excel工作表
核心思路
要完整保留筛选排序列、透视表等复杂元素,不能仅复制工作表的表层节点,必须同步复制所有关联的部件(如透视表缓存、样式定义),并处理ID冲突问题。以下是简化的实现方案:
步骤与代码实现
- 以其中一个Excel文件为基础目标文档,逐个导入另一个文件的工作表及关联资源
- 处理透视表缓存ID、工作表名称等冲突问题
- 保存目标文档
using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using System.Linq; public static void MergeExcelWorkbooks(string targetFilePath, string sourceFilePath) { // 打开目标文档(可写)和源文档(只读) using var targetDoc = SpreadsheetDocument.Open(targetFilePath, true); using var sourceDoc = SpreadsheetDocument.Open(sourceFilePath, false); var targetWorkbookPart = targetDoc.WorkbookPart; var sourceWorkbookPart = sourceDoc.WorkbookPart; foreach (var sourceSheet in sourceWorkbookPart.Workbook.Sheets.OfType<Sheet>()) { // 获取源工作表部件 var sourceWorksheetPart = (WorksheetPart)sourceWorkbookPart.GetPartById(sourceSheet.Id); // 将源工作表部件复制到目标文档 var newWorksheetPart = targetWorkbookPart.AddPart<WorksheetPart>(sourceWorksheetPart); // 复制透视表及关联缓存部件 foreach (var sourcePivotPart in sourceWorksheetPart.PivotTableParts) { var newPivotPart = newWorksheetPart.AddPart<PivotTablePart>(sourcePivotPart); // 复制透视表缓存定义并更新缓存ID var sourceCacheDef = sourcePivotPart.PivotCacheDefinitionPart; var newCacheDef = newPivotPart.AddPart<PivotCacheDefinitionPart>(sourceCacheDef); newCacheDef.PivotCacheDefinition.CacheId = GetNextCacheId(targetWorkbookPart); // 复制透视表缓存记录(如果存在) var sourceCacheRecords = sourceCacheDef.PivotCacheRecordsPart; if (sourceCacheRecords != null) { newCacheDef.AddPart<PivotCacheRecordsPart>(sourceCacheRecords); } } // 生成不重复的工作表名称 var uniqueSheetName = GetUniqueSheetName(targetWorkbookPart, sourceSheet.Name.Value); // 向目标工作簿添加新工作表节点 var newSheet = new Sheet { Id = targetWorkbookPart.GetIdOfPart(newWorksheetPart), SheetId = (uint)(targetWorkbookPart.Workbook.Sheets.Count() + 1), Name = uniqueSheetName }; targetWorkbookPart.Workbook.Sheets.Append(newSheet); } // 保存修改 targetWorkbookPart.Workbook.Save(); } // 获取不重复的工作表名称 private static string GetUniqueSheetName(WorkbookPart workbookPart, string originalName) { var existingNames = workbookPart.Workbook.Sheets.OfType<Sheet>().Select(s => s.Name.Value); var candidateName = originalName; int counter = 1; while (existingNames.Contains(candidateName)) { candidateName = $"{originalName}_{counter}"; counter++; } return candidateName; } // 获取下一个可用的透视表缓存ID private static uint GetNextCacheId(WorkbookPart workbookPart) { uint maxId = 0; foreach (var cacheDefPart in workbookPart.PivotCacheDefinitionParts) { if (cacheDefPart.PivotCacheDefinition.CacheId > maxId) { maxId = cacheDefPart.PivotCacheDefinition.CacheId; } } return maxId + 1; }
注意事项
- 运行前确保目标文件未被其他程序占用
- 若源文档包含自定义样式,需额外合并
StylesPart节点,避免格式丢失 - 筛选、排序信息属于工作表节点的子元素,复制工作表部件时会自动保留
- 如需处理命名范围冲突,可扩展代码合并
DefinedNames节点
内容的提问来源于stack exchange,提问作者andrey1567
相关产品推荐
相关产品推荐

