在 .NET 里操作 Excel,大多数人上手就是 NPOI、EPPlus、ClosedXML,或者我上篇刚聊过的 MiniExcel。但很多人不知道,这些库不管封装得多花哨,底层都在跟同一个东西打交道——微软官方的 OpenXML SDK。
这就很有意思了:官方出品、功能最强、什么都能干,但平时几乎没人提它。
原因很简单:它太底层了。没有封装,没有语法糖,直接让你面对 Excel 文件内部的 XML 结构。但也正因为它底层,它能做到很多封装库做不了的事。今天咱们就来看看,这把“手术刀”到底怎么用。
先认门
https://github.com/OfficeDev/Open-XML-SDK
dotnet add package DocumentFormat.OpenXml
不需要装 Office,不需要任何运行时。支持 .NET Framework 4.6.2 及以上,以及 .NET Core 3.1、.NET 5~10。
安装完就能直接用。
它到底在操作什么?
一个 .xlsx 文件本质上是个 ZIP 压缩包。你可以把后缀改成 .zip 解压看看,里面是一堆 XML 文件:xl/worksheets/sheet1.xml 存的是数据,xl/styles.xml 存的是样式,xl/sharedStrings.xml 存的是共享字符串,等等。
OpenXML SDK 做的事情,就是让你用 C# 直接读写这些 XML。它提供了两种方式:DOM 方式和 SAX 方式。
DOM 方式:全量加载,操作直观
DOM 方式把整个文档结构加载到内存,用强类型类一层层访问。适合数据量不大、需要对文档做复杂操作的场景。
using (var doc = SpreadsheetDocument.Open("test.xlsx", false)){ var sheet = doc.WorkbookPart.WorksheetParts.First(); var sheetData = sheet.Worksheet.Elements<SheetData>().First(); foreach (Row r in sheetData.Elements<Row>()) { foreach (Cell c in r.Elements<Cell>()) { Console.Write(c.CellValue?.Text + " "); } Console.WriteLine(); }}
缺点:文件一大,内存直接飙上去。100MB 的文件可能吃掉 1GB 内存。
SAX 方式:流式读取,内存省但更底层
SAX 方式用 OpenXmlReader 逐元素读取,不一次性加载整个文件。适合大文件读取或只需要提取部分数据的场景。
using (var doc = SpreadsheetDocument.Open("test.xlsx", false)){ var reader = OpenXmlReader.Create(doc.WorkbookPart.WorksheetParts.First()); while (reader.Read()) { if (reader.ElementType == typeof(Row)) { // 处理行 } if (reader.ElementType == typeof(CellValue)) { Console.Write(reader.GetText() + " "); } }}
优点:内存占用极低,文件多大都不怕。缺点:代码量大,得手动处理行的起止、共享字符串引用等细节。
它真正特别的地方:这些操作别的库做不到
数据读写不是 OpenXML SDK 的优势——MiniExcel 几行代码就搞定的事,它得写几十行。但下面这些操作,是只有它(或者底层调用了它的 ClosedXML)能做到的。
1. 精细控制单元格样式
字体、颜色、边框、对齐、背景色,每一个细节都能控制:
using (var doc = SpreadsheetDocument.Create("styled.xlsx", SpreadsheetDocumentType.Workbook)){ var workbookPart = doc.AddWorkbookPart(); var worksheetPart = workbookPart.AddNewPart<WorksheetPart>(); var stylesPart = workbookPart.AddNewPart<WorkbookStylesPart>(); // 定义样式 var font = new Font(new FontSize { Val = 12 }, new Bold(), new Color { Rgb = "FF0000" }); var fill = new Fill(new PatternFill(new ForegroundColor { Rgb = "FFFF00" }) { PatternType = PatternValues.Solid }); var alignment = new Alignment { Horizontal = HorizontalAlignmentValues.Center }; stylesPart.Stylesheet = new Stylesheet( new Fonts(font), new Fills(fill), new Borders(new Border()), new CellFormats(new CellFormat { FontId = 0, FillId = 0, Alignment = alignment, ApplyFont = true, ApplyFill = true, ApplyAlignment = true }) ); // 写入数据并应用样式 var sheetData = new SheetData(); var row = new Row(); var cell = new Cell { CellValue = new CellValue("样式测试"), DataType = CellValues.String, StyleIndex = 0 }; row.Append(cell); sheetData.Append(row); worksheetPart.Worksheet = new Worksheet(sheetData);}
这个能力 MiniExcel 做不到,EPPlus 能做但要花钱。
2. 合并单元格
var mergeCells = new MergeCells();mergeCells.Append(new MergeCell { Reference = "A1:C1" });worksheetPart.Worksheet.InsertAfter(mergeCells, sheetData);
3. 冻结窗格
var freezePane = new Pane{ VerticalSplit = 1, TopLeftCell = "A2", ActivePane = PaneValues.BottomLeft, State = PaneStateValues.Frozen};var selection = new Selection { Pane = PaneValues.BottomLeft };sheetData.Parent.InsertBefore(freezePane, sheetData);
4. 插入图表
OpenXML SDK 支持完整的图表 API:柱状图、折线图、饼图,都能用代码生成。
// 创建图表部件var chartPart = worksheetPart.AddNewPart<ChartPart>();// 添加柱状图var chart = new Chart();var barChart = new BarChart( new BarDirection { Val = BarDirectionValues.Column }, new BarGrouping { Val = BarGroupingValues.Clustered });chart.Append(barChart);chartPart.Chart = chart;
这是 OpenXML SDK 的独占领域,轻量库做不到。
5. 操作 Word 和 PPT
OpenXML SDK 不仅仅管 Excel。如果你的需求涉及 .docx 或 .pptx,它是统一的底层方案。用同一套 API 风格操作三种 Office 文档,这是封装库提供不了的。
OpenXML SDK vs MiniExcel:定位不同,可以共存
MiniExcel 底层也在操作 Open XML,但它把复杂度封起来了。一个让你快速干活,一个让你什么都能干。
总结
日常数据读写用 MiniExcel,几行代码完事。需要精细控制样式、插入图表、做正式企业报表的时候,再上 OpenXML SDK。两个工具可以混用,互不冲突。
知道工具箱里有这么一把“手术刀”,真碰到复杂需求的时候,心里就有底了。