应优先使用NPOI而非Microsoft.Office.Interop.Excel,因其不依赖Office安装、无COM交互、线程安全、支持.NET Core+,且避免“CLSID {00024500-...} failed”等典型报错。

为什么直接用 NPOI 而不是 Microsoft.Office.Interop.Excel
因为后者必须在目标机器上安装 Office,且多线程下极不稳定,容易卡死或抛出 COMException。而 NPOI 是纯 .NET 实现,不依赖 Office、无 COM 交互、支持 .NET Core/.NET 5+,适合服务端导出场景。
常见错误现象:Retrieving the COM class factory for component with CLSID {00024500-0000-0000-C000-000000000046} failed —— 这是 Interop 在无 Office 环境下的典型报错,NPOI 完全避开这个问题。
- 仅需引用
NPOINuGet 包(推荐NPOI 2.7.1或更高,注意区分net48和net6.0版本) - 不需注册 COM 组件,也不需要设置 Excel 进程权限
- 生成的是标准
.xlsx(XSSFWorkbook)或旧版.xls(HSSFWorkbook),但建议统一用XSSFWorkbook
XSSFWorkbook 创建空工作簿并写入数据的最小可行代码
别一上来就套模板,先跑通最简流程:新建工作簿 → 添加 Sheet → 写入几行数据 → 保存为文件。
using NPOI.XSSF.UserModel;
using NPOI.SS.UserModel;
using System.IO;
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("报表");
// 写入表头
var headerRow = sheet.CreateRow(0);
headerRow.CreateCell(0).SetCellValue("ID");
headerRow.CreateCell(1).SetCellValue("姓名");
headerRow.CreateCell(2).SetCellValue("金额");
// 写入数据行
var dataRow = sheet.CreateRow(1);
dataRow.CreateCell(0).SetCellValue(1001);
dataRow.CreateCell(1).SetCellValue("张三");
dataRow.CreateCell(2).SetCellValue(999.5);
// 保存到文件
using var fs = new FileStream("report.xlsx", FileMode.Create);
workbook.Write(fs);
关键点:workbook.Write() 必须在 FileStream 打开状态下调用;若提前关闭流,会抛 ObjectDisposedException。
- 不要用
File.WriteAllText或StreamWriter处理 Excel 文件 —— 它们只适用于文本 - 如果要写入大量数据(如万行以上),避免反复调用
CreateRow+CreateCell,可缓存IRow和ICell引用减少对象创建开销 -
XSSFWorkbook默认使用 OOXML 格式,兼容 Excel 2007+,不支持 Excel 97–2003 的.xls
如何设置单元格样式(字体、边框、数字格式)
没有样式的数据报表很难被业务方接受。NPOI 的样式必须通过 ICellStyle 设置,且样式对象不能跨 Sheet 复用(除非显式克隆)。
清理、标准化、验证并汇总杂乱的 Excel 工作簿。当 Codex 需要处理 `.xlsx`、`.xls`、`.xlsm` 或 `.csv` 格式的数据时使用,包括:统一表头格式、删除空白行与空白列、修剪文本内容、去重记录、检测数据质量问题、拆分或合并工作表、生成清洗后的工作簿,以及为业务电子表格生成审计摘要。
var font = workbook.CreateFont(); font.FontHeightInPoints = 10; font.Boldweight = (short)FontBoldWeight.Bold; var style = workbook.CreateCellStyle(); style.SetFont(font); style.BorderTop = BorderStyle.Thin; style.BorderBottom = BorderStyle.Thin; style.BorderLeft = BorderStyle.Thin; style.BorderRight = BorderStyle.Thin; // 应用到某单元格 headerRow.GetCell(0).CellStyle = style;
常见坑:CellStyle 是 workbook 级别资源,最多支持 64000 种唯一样式;若循环中每次都 workbook.CreateCellStyle(),会快速耗尽并抛 InvalidOperationException。
- 复用已有样式:先用
workbook.FindCellFormat(...)尝试查找匹配样式 - 数字格式如金额,用
style.DataFormat = workbook.CreateDataFormat().GetFormat("#,##0.00") - 中文乱码?确保字体设为
font.FontName = "微软雅黑"(Windows)或"SimSun",否则默认 Arial 不支持中文
导出 DataTable 到 Excel 的实用封装要点
实际项目里基本不会手写每行数据,而是从 DataTable 导出。但要注意列类型映射、NULL 值处理、日期格式兼容性。
核心逻辑就是遍历 DataTable.Columns 写表头,再遍历 Rows 写数据行,但必须区分类型:
-
DBNull.Value或null→ 跳过SetCellValue,或设为空字符串(cell.SetCellValue("")) -
DateTime→ 用cell.SetCellValue((DateTime)value)(NPOI 会自动识别并序列化),别转成字符串,否则 Excel 无法识别为日期 -
double/decimal→ 直接SetCellValue(double),避免 ToString() 后丢失精度或格式错乱 - 布尔值 →
cell.SetCellValue(bool),Excel 中显示为 TRUE/FALSE,且可参与筛选
性能提示:DataTable 行数超 5000 时,建议关闭自动列宽(sheet.AutoSizeColumn(i) 放在最后批量调用),否则每写一列就重算一次,速度骤降。
样式和格式细节最容易被忽略——比如金额列没加千分位、日期列导出后变成数字串、中文标题显示为方块,这些问题上线后才暴露,排查成本远高于写代码时多加两行格式设置。

















