最近抽空研究了一下 基於DocumentFormat.OpenXml操做Excel,也把本身的理解記錄下來,便於往後能夠查閱。api
各類系統中,導出Excel是一種很常見的功能,在C#/.Net 環境下, 市面上存在不少相關的類庫,開源免費的有基於JAVA的POI移植過來的 NPOI、 EPPlus、OpenXML 等等;也有收費的 如 Aspose.Cells、Spire.XLS 等。性能優化
收費的商業組件在性能,功能上會通常來講都會比較好,例如Aspose.Cells,在性能上是比較卓越的,價格最便宜也要1000美金左右。 可是大部分公司,特別是初創公司,考慮到成本問題,用得比較多的,可能仍是會使用開源免費的 NPOI 和 EPPlus,而OpenXML(DocumentFormat.OpenXml) 是微軟官方推出的一個操做Excel, Word, PPT文件的組件,並且操做的是更爲底層的部分,可以作到靈活和精確的控制,可是自己操做起來會比較複雜,有些順序的限制,會感受沒有 NPOI 和 EPPlus 方便,並且生成出來的excel搞很差還會提示錯誤(有可能WPS打開不會,office打開會),並且性能方面雖然操做底層,可是也要看使用者自己,不必定性能能夠很高。因此自己是比較少人會考慮使用它。工具
這個組件爲啥叫 OpenXML SDK,由於從07版本以後,office系列的文檔,包括Excel, Word, PPT這些文檔,都是使用XML方式存儲的。 而 Office 的 Open XML 文件格式規範是一個開放的國際性標準 ,是 ECMA 376 標準, 能夠查閱這個網址:https://www.ecma-international.org/publications/standards/Ecma-376.htm 。 因此若是要學習這個SDK,則須要瞭解Excel裏面 XML的組成元素,這個能夠查閱 ECMA 376 標準的定義,可是會比較難啃。 可是瞭解Excel的內部組成,也對於你後期操做Excel的準確度,以及性能優化也是有幫助的。另外,因爲是操做XML, 因此 OpenXML SDK只能適用與07版本後的office, 像2003版本的Excel 文件(.xls)是不支持的。性能
在一個文檔的內部,都是由多份XML的東西來組成的。 咱們能夠本身新建一個Excel文檔(.xlsx),而後修改其後綴名 xlsx 爲 rar/zip, 能夠直接解壓。學習
以下圖所示:開發工具
從以上的圖中解壓出來後的文件,能夠看出,一個 Excel (.xlsx)文件裏面包含不少個XML文件,分別表明一個Excel不一樣模塊的數據組成。優化
從名字上,咱們大概初步能夠猜想出幾點: spa
(1)workbook.xml 一個工做簿(Excel文件)的總覽3d
(2)styles.xml 樣式相關excel
(3)worksheets文件夾,包含多份xml, 每一份表明一個工做簿(Excel文件)裏面其中的一個工做表(WorkSheet)
(4)theme 文件夾,Excel主題相關
(5)printerSettings文件夾, 打印設置相關
那麼再來看看,初步經過代碼應該怎麼實現。
微軟官方提供的文檔說明,能夠查閱:https://docs.microsoft.com/zh-cn/office/open-xml/structure-of-a-spreadsheetml-document
OpenXML SDK 對應的Nuget包叫DocumentFormat.OpenXml,目前最新版本是 2.11.3,地址是: https://www.nuget.org/packages/DocumentFormat.OpenXml/
OpenXML SDK 的相關類型說明,能夠查閱微軟官方提供的API文檔:https://docs.microsoft.com/zh-cn/dotnet/api/documentformat.openxml.spreadsheet.workbook?view=openxml-2.8.1
在C#.Net 項目中,經過VS開發工具的Nuget管理工具安裝,也能夠直接在終端經過nuget包管理命令安裝: Install-Package DocumentFormat.OpenXml -Version 2.11.3
根據微軟官方的文檔,先來經過C#代碼,簡單生成一個excel文件,而且保存到磁盤。 經過Nuget安裝好SDK。
根據微軟官方的介紹,一份最小(空白)工做簿必須包含如下內容,要:
一、有一個工做表(WorkSheet)
二、工做表的Id
三、指向工做表定義位置的關係 Id
可能聽起來有點難理解,經過這個來看下面的代碼示例(能夠運行的)
1 using System; 2 using System.IO; 3 using DocumentFormat.OpenXml; 4 using DocumentFormat.OpenXml.Packaging; 5 using DocumentFormat.OpenXml.Spreadsheet; 6 7 namespace PracticePart1 8 { 9 public class Program 10 { 11 public static void Main(string[] args) 12 { 13 //當前運行時路徑 14 var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); 15 var fileName = $@"PracticePart1-{DateTime.Now:yyyyMMddHHmmss}.xlsx"; 16 17 //文件路徑,保存在運行時路徑下 18 var filepath = Path.Combine(directoryInfo.ToString(), fileName); 19 Console.WriteLine($"FilePath: {filepath}"); 20 21 //建立SpreadsheetDocument對象,xlsx類型,經過路徑 22 var spreadsheetDocument = SpreadsheetDocument.Create(filepath, SpreadsheetDocumentType.Workbook); 23 24 //經過Stream對象 25 //MemoryStream ms = new MemoryStream(); 26 //SpreadsheetDocument.Create(ms, SpreadsheetDocumentType.Workbook); 27 28 //調用AddWorkbookPart, 建立WorkbookPart對象, 建立Workbook對象(至關於XML根元素)關聯到WorkbookPart 29 var workbookPart = spreadsheetDocument.AddWorkbookPart(); 30 workbookPart.Workbook = new Workbook(); 31 32 //經過上面的WorkbookPart,建立WorksheetPart對象,建立Worksheet對象(至關於XML根元素)關聯到 WorksheetPart 33 var worksheetPart = workbookPart.AddNewPart<WorksheetPart>(); 34 worksheetPart.Worksheet = new Worksheet(new SheetData()); 35 36 // 建立Sheets 到 Workbook 37 var sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets()); 38 39 // 建立添加Sheet對象, Id關聯 Worksheet, 從而命名工做表的名稱 40 var sheet = new Sheet() 41 { 42 Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart), 43 SheetId = 1, 44 Name = "myFirstSheet" 45 }; 46 47 //追加到 Sheets 48 sheets.Append(sheet); 49 50 //保存到磁盤 51 workbookPart.Workbook.Save(); 52 53 // Close the document. 54 spreadsheetDocument.Close(); 55 } 56 } 57 }
以上是能夠直接運行,而後生成Excel文件。上述涉及到的幾個類型,依據我我的的理解,下面簡單說明下。
SpreadsheetDocument 表格文檔類, 從字面意思,它就表示一個Excel相關的文檔包。 經過它的Create方法, 經過枚舉來指定文檔類型(*.xlsx, 能夠指定其它如*.xltx, *.xlsm等),同時指定文件路徑(重載方法中,是能夠支持傳入一個Stream),建立一個document對象。而後建立表示 工做簿 的WorkbookPart, 建立表示 工做表的 WorksheetPart, 這個時候符合上訴的 第一個要求【有一個工做表(WorkSheet)】。
接着經過工做簿對象Workbook建立一個Sheets,表示工做簿中的全部表。 這裏要先明白Sheet 和 Worksheet 的這2個的不一樣:
(1)Worksheet : 表示的是工做簿的一份工做表的內容定義,就是咱們打開Excel文件,下方顯示sheet1, sheet2,sheet3....的每一份工做表。
(2)Sheet : 表明的是工做簿的一個表,它能夠是工做表(Worksheet), 也能夠圖表(ChartSheet),也能夠是其它類型的表。它自己不涉及具體表怎麼定義,可是會經過Id關聯具體表的定義; 因此 Sheets 是表示工做簿中關聯的全部表的集合(圖表,工做表,宏表等)。 好比一個Sheets,它其中包含2個工做表,1個圖表,而具體的定義放在其它的地方,通常一個表的定義有一個xml。
Sheet 對象, 有一個 Id 和 SheetId, 這2個字段。 按照上訴的 第二個要求 【工做表的Id】,就是指 SheetId, 本身賦值。 第三個要求【指向工做表定義位置的關係 Id 】, 就是指這個Id字段, 這個Id 指向工做表定義位置(就是指Sheet 關聯到具體哪一個 WorksheetPart ), 經過 WorkbookPart.GetIdOfPart 的方法來獲取這個Id 。
以上就是DocumentFormat.OpenXml 建立一個最小化空白文檔的方式。