C#使用Open XML读取Excel时字符串值显示为整数的问题求助
问题描述
在.NET应用中使用Open XML读取Excel文件,编写了根据行和列参数返回对应单元格值的方法,但遇到单元格字符串数据被读取为整数,整数值读取正常的问题。
实现代码
GetServicePorts方法
public List<ServicePortDto> GetServicePorts(IFormFile formFile, Dictionary<string, int> starRowForPorts) { using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(formFile.OpenReadStream(), false)) { WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart; List<int> portRows = new(); int counter = 0; IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>(); foreach (var sheet in sheets) { int startRowPort = starRowForPorts.Where(x=>x.Key == sheet.Name.Value).Select(x => x.Value).FirstOrDefault(); string relationshipId = sheet.Id.Value; WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId); Worksheet workSheet = worksheetPart.Worksheet; SheetData sheetData = workSheet.GetFirstChild<SheetData>(); IEnumerable<Row> rows = sheetData.Descendants<Row>(); rows = rows.Where(x=>x.RowIndex >= startRowPort); foreach (Row row in rows) { if(row.RowIndex >= startRowPort) { string portName = GetCell(workSheet, "B", startRowPort).InnerText; if (portName != null) { counter = counter + 1; } else { break; } } } } } return new List<ServicePortDto>(); }
GetCell方法
private static Cell GetCell(Worksheet worksheet, string columnName, int rowIndex) { Row row = worksheet.GetFirstChild<SheetData>().Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex); if (row != null) { return row.Elements<Cell>().FirstOrDefault(c => string.Compare(c.CellReference.Value, columnName + rowIndex, true) == 0); } return null; }
问题详情
读取单元格时,字符串内容返回的是整数(比如原本是"端口A",结果读出来是"1"),但整数类型的单元格内容读取正常。
解决方案
问题根源是Open XML的存储机制:Excel会把重复字符串存入共享字符串表(SharedStringTable),单元格的InnerText存储的是该字符串在表中的索引值,而非实际内容;整数类型则直接存储值,所以读取正常。
需要新增方法解析单元格实际值,步骤如下:
- 判断单元格数据类型,若为共享字符串类型,去共享字符串表查找对应索引的实际字符串;
- 其他类型直接返回
InnerText。
新增辅助方法
// 获取共享字符串表 private static SharedStringTablePart GetSharedStringTablePart(SpreadsheetDocument document) { return document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault(); } // 解析单元格的实际值 private static string GetCellValue(Cell cell, SharedStringTablePart sharedStringTablePart) { if (cell == null) return null; string cellValue = cell.InnerText; // 判断是否为共享字符串类型 if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString) { if (sharedStringTablePart != null && int.TryParse(cellValue, out int index)) { // 根据索引从共享字符串表中获取实际字符串 var sharedStringItem = sharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAtOrDefault(index); return sharedStringItem?.InnerText; } } // 非共享字符串类型直接返回值 return cellValue; }
修改GetServicePorts方法
加入共享字符串表的获取,并用GetCellValue替代直接读取InnerText,同时修复原代码中一直读取同一行的问题:
public List<ServicePortDto> GetServicePorts(IFormFile formFile, Dictionary<string, int> starRowForPorts) { var servicePorts = new List<ServicePortDto>(); using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(formFile.OpenReadStream(), false)) { WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart; // 获取共享字符串表 var sharedStringTablePart = GetSharedStringTablePart(spreadSheetDocument); IEnumerable<Sheet> sheets = workbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>(); foreach (var sheet in sheets) { // 简化startRowPort的获取逻辑 if (!starRowForPorts.TryGetValue(sheet.Name.Value, out int startRowPort)) { continue; } string relationshipId = sheet.Id.Value; WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(relationshipId); Worksheet workSheet = worksheetPart.Worksheet; SheetData sheetData = workSheet.GetFirstChild<SheetData>(); IEnumerable<Row> rows = sheetData.Descendants<Row>().Where(x => x.RowIndex >= startRowPort); foreach (Row row in rows) { // 读取当前循环行的B列单元格,而非固定startRowPort行 Cell portNameCell = GetCell(workSheet, "B", (int)row.RowIndex); string portName = GetCellValue(portNameCell, sharedStringTablePart); if (!string.IsNullOrEmpty(portName)) { // 按需实例化ServicePortDto并添加到列表 // servicePorts.Add(new ServicePortDto { PortName = portName }); } else { break; } } } } return servicePorts; }
额外优化点
- 原
GetCell方法每次遍历所有行找指定行,可缓存行索引与行的映射提升效率; - 用
TryGetValue替代LINQ查询获取startRowPort,减少不必要的计算; - 修复原代码中循环始终读取同一行单元格的错误,改为读取当前循环行的对应单元格。
内容的提问来源于stack exchange,提问作者Niranjan
相关产品推荐
相关产品推荐

