EPPlus(v6+)是读取.xlsx最稳选择:纯托管、无需Office、线程安全、性能好;注意LicenseContext设置、用.Text而非.Value、判空Dimension、避免文件格式混淆。

用 EPPlus 读取 .xlsx 是目前最稳的选择
直接说结论:别用 Microsoft.Office.Interop.Excel,它依赖 Office 安装、线程不安全、服务器上基本跑不起来;也别碰已停更的 NPOI 旧版(0.9x),对 .xlsx 支持弱且容易抛 Invalid header signature。推荐用现代、纯托管、NuGet 开箱即用的 EPPlus(v6+,注意需 .NET 5+ 或 .NET Framework 4.6.1+)。
安装命令:
dotnet add package EPPlus(若用 .NET Core/.NET 5+)或通过 NuGet 包管理器安装
EPPlus v6.x。
关键点:
-
EPPlus不需要 Excel 软件,也不调 COM,纯内存解析,适合后台服务 - v6+ 默认启用「商业用途需授权」提示,若仅内网/非分发场景,可加一行代码关闭:
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
- 读取时默认不加载样式、公式结果(只读值),性能好;如需公式求值,得手动调用
worksheet.Calculate()
读取第一个 Sheet 的数据:跳过空行、处理标题行
常见需求是把 Excel 表格当二维数据源用,比如首行为列名,后面为记录。用 EPPlus 可这样写:
using (var package = new ExcelPackage(new FileInfo("data.xlsx")))
{
var worksheet = package.Workbook.Worksheets[0]; // 第一个 sheet
var rowCount = worksheet.Dimension?.Rows ?? 0;
var colCount = worksheet.Dimension?.Columns ?? 0;
var data = new List>();
if (rowCount zuojiankuohaophpcn 2) return data;
// 假设第1行是 header
var headers = Enumerable.Range(1, colCount)
.Select(c =youjiankuohaophpcn worksheet.Cells[1, c].Text.Trim())
.ToArray();
for (int row = 2; row zuojiankuohaophpcn= rowCount; row++)
{
var values = new Dictionary();
bool allEmpty = true;
for (int col = 1; col zuojiankuohaophpcn= colCount; col++)
{
var cell = worksheet.Cells[row, col];
var val = cell.Text; // 用 .Text 更安全(避免 null 引用),.Value 是 object 类型需判空转型
values[headers[col - 1]] = val;
if (!string.IsNullOrWhiteSpace(val)) allEmpty = false;
}
if (!allEmpty) data.Add(values);
}
}
注意:
-
worksheet.Dimension可能为null(空表),务必判空 - 别直接用
cell.Value——它可能是double、DateTime、null,类型不统一;cell.Text总是字符串,语义更接近「用户看到的内容」 - Excel 行号/列号从 1 开始,不是 0,
Cells[row, col]别写反
遇到 System.IO.FileFormatException: File contains corrupted data
这通常不是文件真坏了,而是你用了错误的库或打开方式:
- 用
FileStream打开后没设FileShare.Read,被其他进程占用 → 改用new FileInfo(path)构造ExcelPackage,它内部会正确处理流 - 文件其实是
.xls(OLE 复合文档格式),但扩展名错写成.xlsx→ 用十六进制编辑器看前 8 字节:真正的.xlsx应以50 4B 03 04(PK..)开头;.xls是D0 CF 11 E0(DOCFILE) - 用低版本
EPPlus(v4/v5)打开由新版 Excel(如 Microsoft 365)保存的强加密或含新特性(如动态数组公式)的文件 → 升级到 v6.2+ 并确认LicenseContext设置正确
读取指定列、跳过隐藏行、识别合并单元格
业务中常要按列名取值,或过滤掉人工隐藏的行:
- 按列名查值:先用
headers数组找到列索引,再取对应cell.Text,比硬编码列号更健壮 - 跳过隐藏行:
worksheet.Row(row).Hidden返回true时continue - 合并单元格:用
worksheet.MergedCells获取所有合并区域(如"A1:C1"),再用worksheet.Cells[row, col].Merged判断当前单元格是否属于合并区;若属合并区,只取左上角单元格的值即可,其余为空 - 日期列读出来是数字?那是 Excel 序列号(如 44197 = 2021-01-01),用
DateTime.FromOADate(cell.Value as double?)转换,但前提是cell.Value确实是double;更稳妥仍是先读cell.Text再DateTime.TryParse
合并单元格和隐藏行逻辑容易漏,尤其在报表类 Excel 中,不检查就会导致数据错位或重复。










