资讯动态

C#利用EPPlus写入、读取、修改Excel实例:从Interop迁移实战

发布时间:2026/10/15 17:13:47 来源:尧图企业网站定制
简介这份资源面向需要在.NET环境中处理Excel的C#开发者尤其是从事数据导入导出、报表生成等任务的程序员。内容围绕EPPlus这一开源库展开覆盖XLSX格式文件的写入、读取与修改三大核心操作并延伸至样式设置、公式计算、图表创建等进阶用法适合具备一定C#基础、希望提升办公自动化效率的中级开发者参考实践。压缩包共41个文件约2.55MB以dll动态库、cs源码、xml与config配置、resx资源文件及xlsx示例表格为主另含sln解决方案与exe可执行程序构成一套可直接运行的完整工程。目前已有3725人学习下载说明其在同类教程中具备一定认可度。通过该实例读者可掌握ExcelPackage与FileStream的配合方式、工作表与单元格的读写技巧、批量数据加载及保存流程并了解资源管理与内存泄漏的规避思路便于快速迁移到实际项目中。1. 报表导出又崩了为什么我最终把 Excel 操作从 Interop 换成了 EPPlus做过企业后台的同行大概都遇到过这种场景运营要一份带格式的月度对账表财务要能直接在上面改数字再回传导出量一上来服务器上还装着 Office 的机器就开始抽风进程残留、句柄泄漏、偶发 COM 异常排查起来像开盲盒。这类需求的核心其实就三件事——写入、读取、修改 Excel而 C# 里能干净利落把这三件事做完的库EPPlus 是我这些年用得最顺手的之一。它不依赖本机安装 Office纯托管代码跑在 .NET 里服务端、桌面端、批处理任务都能用。这篇笔记就围绕「c#利用EPPlus写入、读取、修改excel实例」这个主题把选型理由、最小可跑代码、参数怎么调、坑在哪一层层拆开。适合正在做报表导出、数据导入、模板回填的 .NET 开发者新手能照着复现熟手能对一下边界。2. 先把 EPPlus 跑起来环境、授权与最小写入实例2.1 为什么是 EPPlus而不是 Interop 或别的库选型这件事先看约束再看功能。Interop 的问题在于它本质是去驱动本机 Excel 进程服务端部署要装 Office并发一高就容易出「服务器意外关闭连接」这类玄学问题而且资源释放稍有不慎就留下僵尸进程。NPOI 是另一个常见选择功能也全但 API 偏底层写个带样式的表要堆不少代码。EPPlus 的定位刚好卡在中间API 直观、样式和公式支持完整、不依赖 Office对「写入 读取 修改」这种典型报表场景覆盖得很到位。代价是授权。EPPlus 从 5.0 起改成了 Polyform Noncommercial 许可商业闭源项目需要购买商业授权个人学习、开源、非商业用途可以免费。这一点必须先确认清楚别代码写完上线了才发现合规问题。选型时我一般会问三个问题部署环境有没有 Office、并发量多大、是否需要读写复杂样式。前两个决定要不要用 Interop第三个决定用 EPPlus 还是 NPOI。2.2 装包与最小写入五行代码落一个文件先通过 NuGet 引入。命令行方式# 引入 EPPlus 包版本按项目实际需要锁定 dotnet add package EPPlus最小写入实例把一张两列的表写进新文件using OfficeOpenXml; // EPPlus 命名空间 using System.IO; // 5.0 之后需要显式声明许可上下文非商业用途用 NonCommercial ExcelPackage.LicenseContext LicenseContext.NonCommercial; // 用 using 保证 package 被正确释放这是避免文件被占用的关键 using (var package new ExcelPackage()) { // 新增一个工作表名字会显示在底部标签上 var sheet package.Workbook.Worksheets.Add(对账明细); // 表头Cells[行, 列]行列都从 1 开始不是 0 sheet.Cells[1, 1].Value 订单号; sheet.Cells[1, 2].Value 金额; // 数据行 sheet.Cells[2, 1].Value A1001; sheet.Cells[2, 2].Value 1280.50m; // 保存到指定路径文件不存在会新建存在会覆盖 package.SaveAs(new FileInfo(D:\reports\demo.xlsx)); }逻辑说明ExcelPackage是整个工作簿的内存模型Worksheets.Add建表Cells[行,列]定位单元格SaveAs落盘。参数上要注意两点一是LicenseContext必须在创建 package 之前设置否则会抛异常二是SaveAs接收的是FileInfo路径目录必须已存在EPPlus 不会帮你建目录目录不存在会直接报错。2.3 批量写入与样式别一行一行赋值真实报表动辄几千行逐格赋值性能很差。正确姿势是用LoadFromCollection或LoadFromArrays一次性灌数据再统一设样式// 假设有一个订单列表 var orders new[] { new { OrderNo A1001, Amount 1280.50m, Date new DateTime(2024, 5, 1) }, new { OrderNo A1002, Amount 860.00m, Date new DateTime(2024, 5, 2) } }; var sheet package.Workbook.Worksheets.Add(批量); // 从 A1 开始写入true 表示首行作为表头 sheet.Cells[A1].LoadFromCollection(orders, true); // 表头加粗、加背景色 using (var header sheet.Cells[A1:C1]) { header.Style.Font.Bold true; header.Style.Fill.PatternType OfficeOpenXml.Style.ExcelFillStyle.Solid; header.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray); } // 金额列格式化为两位小数 sheet.Column(2).Style.Numberformat.Format #,##0.00; // 按内容自动撑开列宽 sheet.Cells[sheet.Dimension.Address].AutoFitColumns();LoadFromCollection的第二个参数控制是否输出表头匿名类型的属性名会直接变成列名。AutoFitColumns在大数据量下会明显变慢几万行以上建议手动设Column(i).Width。Numberformat.Format用的是 Excel 自己的格式串和 C# 的格式化语法不是一回事别混用。3. 读取与修改把已有 Excel 当数据源和模板用3.1 读取已有文件定位、遍历与类型转换的坑读取比写入更容易翻车因为文件是别人给的格式不可控。基本读法var file new FileInfo(D:\reports\demo.xlsx); using (var package new ExcelPackage(file)) { // 按名字取表名字错了会返回 null必须判空 var sheet package.Workbook.Worksheets[对账明细]; if (sheet?.Dimension null) return; // 空表直接退出 int rows sheet.Dimension.End.Row; int cols sheet.Dimension.End.Column; // 从第 2 行开始读跳过表头 for (int r 2; r rows; r) { // GetValueT 泛型读取类型不匹配会抛异常 string orderNo sheet.Cells[r, 1].GetValuestring(); decimal amount sheet.Cells[r, 2].GetValuedecimal(); // 处理业务…… } }Dimension表示已使用区域空表时为 null这是最常见的空引用来源。GetValueT在类型对不上时会抛InvalidCastException比如单元格里是文本「待定」你却按 decimal 读。稳妥做法是先取sheet.Cells[r, c].Value返回 object再自己判断转换或者用GetValueobject()配合TryParse。3.2 修改已有文件模板回填与保留原格式「修改」的典型场景是拿一份带格式的模板往指定格子里填数再另存。关键是打开模板、改值、另存为新文件不要在原文件上直接覆盖避免模板被污染var template new FileInfo(D:\templates\monthly.xlsx); var output new FileInfo(D:\reports\2024-05.xlsx); using (var package new ExcelPackage(template)) { var sheet package.Workbook.Worksheets[0]; // 回填指定单元格原有样式、合并、边框都会保留 sheet.Cells[B3].Value DateTime.Now.ToString(yyyy-MM); sheet.Cells[C5].Value 1280.50m; // 在已有数据末尾追加一行 int nextRow (sheet.Dimension?.End.Row ?? 1) 1; sheet.Cells[nextRow, 1].Value A1003; sheet.Cells[nextRow, 2].Value 500m; // 另存为新文件模板本身不动 package.SaveAs(output); }用new ExcelPackage(template)打开时EPPlus 会把整个工作簿读进内存你改的是内存副本SaveAs到新路径不会影响原模板。如果确实要原地保存用package.Save()但要注意文件被其他进程占用时会抛IOException这是「文件正在被使用」报错的头号原因。3.3 公式、合并单元格与日期三个高频细节公式直接赋字符串即可EPPlus 会识别并计算sheet.Cells[D2].Formula B2*C2; // 写入公式 package.Workbook.Calculate(); // 触发重算否则读值时可能拿到旧结果合并单元格用Cells[A1:C1].Merge true合并后只有左上角那个格能写值往其他格写会被忽略这是很多人「写了没反应」的原因。日期要特别注意Excel 内部用序列号存日期EPPlus 读出来可能是 double建议写入时直接赋DateTime对象读取时用GetValueDateTime()并给单元格设Numberformat.Format yyyy-mm-dd保证显示正确。4. 避坑与排查EPPlus 实战里最容易翻车的五件事4.1 报「文件正在被使用」或保存后文件损坏现象SaveAs抛IOException或者保存出来的文件用 Excel 打开提示需要修复。原因通常是 package 没释放或者多个线程同时操作同一个文件。解决所有ExcelPackage一律用using包住确保Dispose被调用并发场景下每个线程用独立的 package 和独立文件不要共享实例。EPPlus 的ExcelPackage不是线程安全的这点没有后悔药只能从设计上避开。4.2 读到的数字变成科学计数法或精度丢失现象长订单号读出来变成1.23E15或者金额小数位对不上。原因单元格被 Excel 当成数值类型超过 15 位有效数字就丢精度。解决订单号这类「看起来是数字其实是文本」的字段写入时显式设成文本格式sheet.Cells[r,c].Style.Numberformat.Format 或者写入时前面加单引号语义EPPlus 里直接赋 string 并设文本格式即可。金额用decimal而不是double。4.3 大数据量导出内存暴涨甚至 OOM现象导出十万行时进程内存飙到几个 G。原因EPPlus 把整个工作簿放在内存里行数越多占用越大。解决控制单次导出行数超过阈值就分文件关闭不必要的AutoFitColumns及时Dispose。如果确实要处理超大表考虑流式方案或换更适合大数据的库EPPlus 的强项是中小规模带样式的报表不是海量数据管道。4.4 中文列名或表名读取失败现象Worksheets[对账明细]返回 null。原因表名里有空格、特殊字符或者实际名字和你以为的不一样。解决先用package.Workbook.Worksheets遍历打印所有表名确认或者用索引Worksheets[0]取第一张表。表名匹配是精确匹配大小写和空格都敏感。4.5 授权上下文没设导致启动即崩现象程序一创建ExcelPackage就抛LicenseException。原因5.0 之后必须设置ExcelPackage.LicenseContext。解决在程序入口或首次使用前设置一次即可全局生效。商业项目记得确认授权类型别用NonCommercial蒙混商业用途合规风险自己扛。5. 进阶技巧用 EPPlus 做一份能直接交付的带样式报表前面讲的都是零件这一章把它们拼成一份能直接发给业务方的成品。我平时做月度报表的习惯是模板固定、数据动态、样式统一、公式自算最后加一层校验确保导出的文件打开就能用不用业务方再手动调格式。先看一个完整的导出方法把表头样式、斑马纹、冻结首行、条件格式一次配齐public void ExportReport(string path, ListOrderRow data) { ExcelPackage.LicenseContext LicenseContext.NonCommercial; using (var package new ExcelPackage()) { var sheet package.Workbook.Worksheets.Add(月报); // 1. 写表头 sheet.Cells[1, 1].Value 订单号; sheet.Cells[1, 2].Value 数量; sheet.Cells[1, 3].Value 单价; sheet.Cells[1, 4].Value 金额; // 2. 写数据金额列用公式而不是算好再写方便业务方改数量后自动重算 for (int i 0; i data.Count; i) { int r i 2; sheet.Cells[r, 1].Value data[i].OrderNo; sheet.Cells[r, 2].Value data[i].Qty; sheet.Cells[r, 3].Value data[i].Price; sheet.Cells[r, 4].Formula $B{r}*C{r}; } int lastRow data.Count 1; // 3. 表头样式 using (var head sheet.Cells[1, 1, 1, 4]) { head.Style.Font.Bold true; head.Style.Font.Color.SetColor(System.Drawing.Color.White); head.Style.Fill.PatternType OfficeOpenXml.Style.ExcelFillStyle.Solid; head.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.SteelBlue); head.Style.HorizontalAlignment OfficeOpenXml.Style.ExcelHorizontalAlignment.Center; } // 4. 斑马纹偶数行浅灰底 for (int r 2; r lastRow; r 2) { sheet.Cells[r, 1, r, 4].Style.Fill.PatternType OfficeOpenXml.Style.ExcelFillStyle.Solid; sheet.Cells[r, 1, r, 4].Style.Fill.BackgroundColor.SetColor( System.Drawing.Color.FromArgb(245, 245, 245)); } // 5. 金额列格式 合计行 sheet.Column(3).Style.Numberformat.Format #,##0.00; sheet.Column(4).Style.Numberformat.Format #,##0.00; sheet.Cells[lastRow 1, 3].Value 合计; sheet.Cells[lastRow 1, 4].Formula $SUM(D2:D{lastRow}); // 6. 冻结首行滚动时表头不动 sheet.View.FreezePanes(2, 1); // 7. 自动筛选 sheet.Cells[1, 1, lastRow, 4].AutoFilter true; // 8. 触发公式计算保证打开文件时合计已算好 package.Workbook.Calculate(); package.SaveAs(new FileInfo(path)); } }这段代码里有几个值得单独说的点。金额列用公式B{r}*C{r}而不是在 C# 里算好再写好处是业务方改了数量或单价金额会自动重算报表从「死数据」变成「活表格」。FreezePanes(2, 1)表示冻结第 1 行、第 1 列左侧不动参数是「冻结到哪个单元格的左上角」。AutoFilter让业务方能自己筛选排序交付体验直接上一个档次。Workbook.Calculate()必须在保存前调用否则某些阅读器打开时公式列是空的。再补一个校验环节。导出后我一般会立刻用 EPPlus 把文件读回来核对行数和合计避免「导出成功但内容是空的」这种低级事故using (var check new ExcelPackage(new FileInfo(path))) { var s check.Workbook.Worksheets[0]; int dataRows (s.Dimension?.End.Row ?? 1) - 1; if (dataRows ! data.Count) throw new Exception($导出行数不符期望 {data.Count}实际 {dataRows}); }这个「写完读回来验一遍」的习惯帮我拦下过好几次因为数据源为空、路径写错、权限不足导致的静默失败。EPPlus 本身不会告诉你「你导了个空表」它只会老老实实保存一个只有表头的文件校验得自己加。最后说一个性能上的取舍。带样式的报表样式操作本身比写数据还耗时尤其是逐格设样式。我的做法是能批量就批量Cells[起始行,起始列,结束行,结束列]一次性拿到区域再设样式比循环里一格一格设快得多。几万行以内这套写法在普通服务器上跑完全没问题真到了几十万行就该考虑分页导出或者换数据管道了别硬刚 EPPlus 的内存模型。这些年踩下来我最大的教训是Excel 操作的坑八成不在库本身而在文件的生命周期管理——谁打开、谁释放、谁在并发。把using写扎实把授权设对把类型转换判空剩下的就是调样式和格式的体力活。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑