必须安装NPOI和NPOI.OOXML(读.xlsx)或NPOI.SS(读.xls),FileStream需用FileMode.Open且支持Seek,读单元格前须判空并指定MissingCellPolicy,数字/日期需按CellType判断后取值。

用 NPOI 读取 Excel 前必须装对 NuGet 包
很多人卡在第一步:装了 NPOI 却报 Could not load file or assembly 'NPOI',或者 HSSFWorkbook 找不到。根本原因是包名和用途不匹配。
-
NPOI是主包,但只含核心类型,不包含 Excel 解析器实现 —— 它本身不直接读文件 - 必须额外安装
NPOI.OOXML(读.xlsx)或NPOI.OpenXml4Net(旧版依赖),否则XSSFWorkbook类型会找不到 - 如果只读
.xls(97–2003 格式),得装NPOI+NPOI.SS,且要用HSSFWorkbook,不是XSSFWorkbook - 推荐统一用
NPOI.OOXML(支持.xlsx)+NPOI,别碰已废弃的NPOI.Core或拼凑多个旧包
FileStream 必须用 FileMode.Open,且不能被其他进程占用
常见错误是代码跑着报 IOException: The process cannot access the file,或读出来全是空行 —— 大概率是文件正被 Excel 程序开着,或 FileStream 构造时用了 FileMode.Create 覆盖了内容。
- 打开文件必须用
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read) - 千万别用
File.OpenRead(path)返回的流,它默认不支持Seek,而NPOI内部需要回溯读取 - 如果文件来自上传(
IFormFile),先CopyTo到MemoryStream,再用new XSSFWorkbook(stream),别直接传原始流 - 读完记得
stream.Close()或用using,否则下次读同一文件会因句柄未释放而失败
读单元格前先判空,GetCell 返回 null 不代表没数据
Excel 表格里看着有值,但 row.GetCell(0) 返回 null,接着调 cell.StringCellValue 就崩 —— 这是最常踩的空引用坑。
-
GetCell(int)只返回“显式设置过值”的单元格,合并单元格、样式单元格、甚至带公式的空单元格都可能返回null - 安全写法是:
var cell = row.GetCell(colIndex) ?? row.GetCell(colIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK) - 读字符串建议统一走
cell?.ToString(),而不是cell.StringCellValue,因为后者对数字/日期单元格会抛异常 - 数字类型单元格(如 “123” 实际存为
Numeric类型)需先判断cell.CellType == CellType.Numeric,再用cell.NumericCellValue,否则转字符串会丢精度
日期单元格默认按 Excel 序列号读,CellStyle 不影响 GetCell 结果
明明 Excel 里显示 “2024/5/20”,但 cell.NumericCellValue 打出来是 45432 —— 这不是 bug,是 Excel 的存储机制:日期本质就是从 1900-1-1 开始的天数。
-
NPOI不自动解析日期格式,cell.DateCellValue才返回DateTime,但它依赖单元格是否被标记为日期类型(cell.CellStyle.DataFormatString含 “yyyy” 才可靠) - 更稳妥的做法是:先用
cell.CellType == CellType.Numeric && DateUtil.IsCellDateFormatted(cell)判断是否为日期,再取cell.DateCellValue - 注意
DateUtil在NPOI.SS.UserModel命名空间下,别漏 using - 如果 Excel 里日期是文本格式(比如手动输入带斜杠的 “2024/5/20”),它会被当
CellType.String,这时就得自己DateTime.TryParse
真正麻烦的不是读不出来,而是读出来的类型和你预期不一致 —— 比如把数字当字符串截断、把日期当普通数字算、把空合并单元格当真空。这些都得在循环每行前加一层类型适配逻辑,没法靠一个 GetCell 搞定。











