优先EPPlus(仅.xlsx)或NPOI(兼容.xls/.xlsx);EPPlus需校验文件、用FileStream、预访问Worksheets;NPOI读.xls须防OLE2异常、日期用NumericCellValue+25569;转List推荐DataTable中间层。

读取 Excel 时选 NPOI 还是 EPPlus?
EPPlus 更轻、API 更现代,但只支持 .xlsx(Office 2007+);NPOI 同时支持 .xls 和 .xlsx,但 API 繁琐、文档少、容易踩空引用。如果你的业务明确只要处理新格式 Excel,优先用 EPPlus;如果要兼容老系统导出的 .xls 文件,只能上 NPOI。
注意:EPPlus 5.x 及以前免费,6.x 起需商业授权(MIT 协议仅限个人/非商业项目);NPOI 始终完全开源(Apache 2.0)。
用 EPPlus 读取 Excel 的最小可靠写法
别直接 new ExcelPackage,它默认不校验文件头,遇到损坏或非 Excel 文件会抛奇怪异常。必须先检查 MIME 或扩展名,再用 FileStream 构造,并捕获 InvalidDataException 和 IOException。
-
FileStream必须以FileMode.Open+FileAccess.Read打开,不能用File.ReadAllBytes()加载到内存再构造 —— 大文件会 OOM - 读取前务必调用
package.Workbook.Worksheets.Count,否则首次访问Worksheets可能触发延迟加载失败,错误信息是模糊的NullReferenceException - 单元格值要用
cell.Value?.ToString(),不要直接cell.Text—— 后者依赖格式化,空单元格或数字类型可能返回空字符串
using (var stream = new FileStream("data.xlsx", FileMode.Open, FileAccess.Read))
using (var package = new ExcelPackage(stream))
{
var worksheet = package.Workbook.Worksheets[0];
var value = worksheet.Cells[1, 1].Value?.ToString();
}
NPOI 读 .xls 文件时的典型崩点
最常见的崩溃不是语法错,而是 HSSFWorkbook 构造时抛 InvalidOperationException:“The supplied file was not a valid OLE2 compound document”。这通常因为文件被 Excel 保存为“Strict Open XML”格式(.xlsx 的一种变体),或实际是 CSV 但后缀改成了 .xls。
- 必须用
FileStream+FileMode.Open,不能用MemoryStream包装字节数组 ——HSSFWorkbook内部依赖底层流的Seek能力 - 读取日期类型单元格,
cell.DateCellValue在老版本 NPOI(如 2.5.1)中对某些 Excel 日期偏移计算错误,建议统一转成cell.NumericCellValue后手动加 25569(Excel 基准日)再转DateTime - 别信
sheet.LastRowNum,它只返回物理最后一行索引,中间有空行就漏数据;老老实实用sheet.GetRow(i)循环判断是否为null
读取后怎么安全转成 List?
别手写 for 循环映射属性,容易错位、类型转换失败。用 DataTable 做中间层最稳:EPPlus 可调 worksheet.Cells["A1:Z1000"].ToDataTable()(需引用 EPPlus.DataExtractor),NPOI 可用社区封装的 Sheet.ToDataTable() 扩展方法。
- 列名必须和 Excel 表头严格一致(包括空格、大小写),否则映射失败且无提示
- 如果表头含特殊字符(如
/、#),DataTable 列名会自动替换为_,后续 LINQ 查询要用替换后的名字 - 空行或空列会导致
DataTable.Rows.Count少于预期,建议读完后用table.Rows.Cast过滤().Where(r => r.ItemArray.Any(x => x != DBNull.Value))
复杂结构(如合并单元格、多表头)没法靠通用方法解决,得按业务逻辑单独解析 —— 这部分没捷径,也别硬套 ORM 映射。











