NET 5 Execl匯入資料處理(EppLus、NPOI)
阿新 • • 發佈:2020-12-08
先來簡單介紹下市面上最廣泛常見的三種操作excel庫的優缺點
1.NPOI
優點:免費開源,無需裝Office即可操作excel, 支援處理的檔案格式包括xls, xlsx, docx.格式
缺點:不支援大資料量以及多sheet的匯出
2.Aspose.Cells
優點:支援大資料量以及多sheet的匯出,提供了應有盡有的檔案格式支援,速度快效能佳
缺點:除了收費幾乎沒有缺點,試用版 限制開啟檔案數量100個,限制使用Aspose.Cells.GridWeb功能,生成的Excel會有水印
3.EPPlus
優點:開源免費,不需要安裝office,支援圖表的列印,匯入匯出速度快,支援高版本Excel格式,可以實現Excel上的各種基本功能
唯一缺點:僅支援xlsx格式,不支援古老的xlsx
基於業務需求和各大庫優缺點對比,儘量選擇合適業務需求的庫,個人比較推薦的是EPPlus
本文使用的是EPPlus包來實現資料的匯出,因為5.0以上的版本需要商業授權碼,所以使用的是4.5.3.3的的版本
專案也是基於最新版本的.net core 3.1 web api
右鍵管理NuGet包新增EPPlus 選擇版本新增專案引用
然後程式碼附上
建立excel匯入幫助類Export2Excel.cs,為了使所有的地方通用,通過list泛型引數 傳入資料來源以及需要匯出的欄位標題,返回byte[],
以便直接寫入檔案流,也提供了基於DataTable 的操作
1、Excel .xls 和 .xlsx 有什麼區別?#
區別如下: 1、檔案格式不同。.xls 是一個特有的二進位制格式,其核心結構是複合文件型別的結構,而.xlsx 的核心結構是 XML 型別的結構, 採用的是基於 XML 的壓縮方式,使其佔用的空間更小。.xlsx 中最後一個 x 的意義就在於此。 2、版本不同。.xls是excel2003及以前版本生成的檔案格式,而.xlsx是excel2007及以後版本生成的檔案格式。 3、相容性不同。.xlsx格式是向下相容的,可相容.xls格式。
2、一號種子選手(EppLus)#
EPPlus是一個使用Open Office XML(xlsx)檔案格式,能讀寫Excel 2007/2010 檔案的開源元件, 在匯出Excel的時候不需要電腦上安裝office,官網為:http://epplus.codeplex.com/。 基本上Excel上的各種功能(例如圖表、VBA、資料透視表、加密、資料驗證等)Epplus都能實現, 它的一個缺點就是不支援匯出2003版的Excel,也就是.XLS檔案。
2.1 EppLus實現#
(1)新增包 EPPlus (注意:EPPlus.Core已棄用) (2)Execl匯入資料使用EPPlus處理例項: /// <summary> /// 獲取Exel批量使用者資料(EppLus) /// </summary> /// <param name="context"></param> /// <param name="msg"></param> /// <returns></returns> public List<BatchUsersReq> GetBatchUsersData(HttpContext context,out string msg) { msg = "資料處理成功"; // 獲取上傳檔案字尾 var extension = Path.GetExtension(context.Request.Form.Files[0].FileName).ToUpper(); if(!extension.Contains("XLSX")) { msg = "檔案格式不正確,只支援XLSX檔案"; return null; } // 限制單次只能上傳5M float fileSize = context.Request.Form.Files[0].Length / 1024 / 1024; if(fileSize > 5) { msg = "檔案大小超過限制"; return null; } try { Stream stream = context.Request.Form.Files[0].OpenReadStream(); using (var package = new ExcelPackage(stream)) { // 獲取Exel指定工作簿,"Sheet1"也可以用索引代替 ExcelWorksheet worksheet = package.Workbook.Worksheets["Sheet1"]; // 獲取資料行數 int RowNum = worksheet.Dimension.Rows; // 待處理資料儲存列表 List<BatchUsersReq> usersData = new List<BatchUsersReq>(); // 獲取每行資料 for (int row = 1; row <= RowNum; row++) { usersData.Add(new BatchUsersReq { // 獲取每列資料 Account = worksheet.Cells[row, 1].Value.ToString(), Password = worksheet.Cells[row, 2].Value.ToString(), Name = worksheet.Cells[row, 3].Value.ToString(), Sex = worksheet.Cells[row, 4].Value.ToString(), UserRole = worksheet.Cells[row, 5].Value.ToString() }); } return usersData; } } catch(Exception e) { msg = "資料異常"; } return null; }
3、二號種子選手(NPOI)
NPOI是一個開源專案,可以讀/寫xls,doc,ppt檔案,有著廣泛的應用。NPIO官網地址:http://npoi.codeplex.com/ 使用NPOI能夠幫助開發者在沒有安裝微軟Office的情況下讀寫Office 97-2003的檔案,支援的檔案格式包括xls, doc, ppt等。 NPOI是構建在POI 3.x版本之上的,它可以在沒有安裝Office的情況 下對Word/Excel文件進行讀寫操作。
3.1 NPOI實現
(1)新增包 DotNetCore.NPOI (2)Execl匯入資料使用EPPlus處理例項: /// <summary> /// 獲取Execl批量使用者資料 NPOI /// </summary> /// <param name="file">execl</param> /// <param name="msg"></param> /// <returns></returns> public List<BatchUsersReq> GetBatchUsersData(IFormFile file,out string msg) { msg = "資料處理成功"; // 獲取上傳檔案字尾 string ext = Path.GetExtension(file.FileName).ToLower(); if(!ext.Contains("xls") && !ext.Contains("xlsx")) { msg = "檔案有誤,只支援上傳XLS、XLSX檔案"; return null; } // 限制單次只能上傳5M float fileSize = file.Length / 1024 / 1024; if (fileSize > 5) { msg = "檔案大小超過限制"; return null; } try { // 檔案流處理 MemoryStream ms = new MemoryStream(); file.CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); // 根據Excel版本進行處理 IWorkbook workbook = ext == ".xls" ? (IWorkbook)new HSSFWorkbook(ms) : new XSSFWorkbook(ms); // 獲取Excel第一張工作簿 ISheet sheet = workbook.GetSheetAt(0); // 獲取資料行數 int num = sheet.LastRowNum; // 待處理使用者資料 List<BatchUsersReq> users = new List<BatchUsersReq>(); for (int i = 1; i <= num; i++) { // 獲取指定行資料 IRow row = sheet.GetRow(i); BatchUsersReq user = new BatchUsersReq(); // 獲取指定列資料 user.Account = row.GetCell(0).ToString(); user.Password = row.GetCell(1).ToString(); user.Name = row.GetCell(2).ToString(); user.Sex = row.GetCell(3).ToString(); user.UserRole = row.GetCell(4).ToString(); users.Add(user); } return users; } catch(Exception e) { msg = "資料處理出錯"; } return null; }
4、踩坑心得
在使用一個庫之前一定要多瞭解全面,多幾個庫對比然後選擇符合自己需求的。
我剛開始參考的EppLus博文裡面並沒有說Epplus不支援.xls,IF判斷邏輯也是兩種都支援。
而我恰巧是上傳的.xls格式,導致程式碼在讀取工作簿的時候就報錯,我以為是檔案流的問題導致讀取不到所以折騰了很久。
後來百度知道了Epplus不支援.xls,於是機智的我的直接手動把.xls改成了.xlsx。(哭唧唧)
結果當然還是不行,於是我又幾番百度瞭解到了NPOI……
EppLus使用
using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Reflection; using Newtonsoft.Json; using OfficeOpenXml; using OfficeOpenXml.Style; namespace Common.Utils { public class Export2Excel { /// <summary> /// 生成excel /// </summary> /// <param name="dtSource">資料來源</param> /// <param name="title">標題(Sheet名)</param> /// <param name="showTitle">是否顯示</param> /// <returns></returns> public static MemoryStream Export(DataTable dtSource, string title, bool showTitle = true) { using (ExcelPackage package = new ExcelPackage()) { ExcelWorksheet workSheet = package.Workbook.Worksheets.Add(title); int maxColumnCount = dtSource.Columns.Count; int curRowIndex = 0; if (showTitle == true) { curRowIndex++; //主題 workSheet.Cells[curRowIndex, 1, 1, maxColumnCount].Merge = true; workSheet.Cells[curRowIndex, 1].Value = title; var headerStyle = workSheet.Workbook.Styles.CreateNamedStyle("headerStyle"); headerStyle.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; headerStyle.Style.Font.Bold = true; headerStyle.Style.Font.Size = 20; workSheet.Cells[curRowIndex, 1].StyleName = "headerStyle"; curRowIndex++; //匯出時間欄 workSheet.Cells[curRowIndex, 1, 2, maxColumnCount].Merge = true; workSheet.Cells[curRowIndex, 1].Value = "匯出時間:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm"); workSheet.Cells[curRowIndex, 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; } curRowIndex++; var titleStyle = workSheet.Workbook.Styles.CreateNamedStyle("titleStyle"); titleStyle.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; titleStyle.Style.Font.Bold = true; //標題 for (var i = 0; i < maxColumnCount; i++) { DataColumn column = dtSource.Columns[i]; workSheet.Cells[curRowIndex, i + 1].Value = column.ColumnName; workSheet.Cells[curRowIndex, i + 1].StyleName = "titleStyle"; } workSheet.View.FreezePanes(curRowIndex, 1);//凍結標題行 //內容 for (var i = 0; i < dtSource.Rows.Count; i++) { curRowIndex++; for (var j = 0; j < maxColumnCount; j++) { DataColumn column = dtSource.Columns[j]; var row = dtSource.Rows[i]; object value = row[column]; var cell = workSheet.Cells[curRowIndex, j + 1]; var pType = column.DataType; pType = pType.Name == "Nullable`1" ? Nullable.GetUnderlyingType(pType) : pType; if (pType == typeof(DateTime)) { cell.Style.Numberformat.Format = "yyyy-MM-dd hh:mm"; cell.Value = Convert.ToDateTime(value); } else if (pType == typeof(int)) { cell.Value = Convert.ToInt32(value); } else if (pType == typeof(double) || pType == typeof(decimal)) { cell.Value = Convert.ToDouble(value); } else { cell.Value = value == null ? "" : value.ToString(); } workSheet.Cells[curRowIndex, j + 1].Value = row[column].ToString(); } } workSheet.Cells[workSheet.Dimension.Address].Style.Font.Name = "宋體"; workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns();//自動填充 for (var i = 1; i <= workSheet.Dimension.End.Column; i++) { workSheet.Column(i).Width = workSheet.Column(i).Width + 2; }//在填充的基礎上再加2 MemoryStream ms = new MemoryStream(package.GetAsByteArray()); return ms; } } /// <summary> /// 生成excel /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dtSource">資料來源</param> /// <param name="columns">匯出欄位表頭合集</param> /// <param name="title">標題(Sheet名)</param> /// <param name="showTitle">是否顯示標題</param> /// <returns></returns> public static byte[] Export<T>(IList<T> dtSource, ExportColumnCollective columns, string title, bool showTitle = true) { using (ExcelPackage package = new ExcelPackage()) { ExcelWorksheet workSheet = package.Workbook.Worksheets.Add(title); int maxColumnCount = columns.ExportColumnList.Count; int curRowIndex = 0; //Excel標題 if (showTitle == true) { curRowIndex++; workSheet.Cells[curRowIndex, 1, 1, maxColumnCount].Merge = true; workSheet.Cells[curRowIndex, 1].Value = title; var headerStyle = workSheet.Workbook.Styles.CreateNamedStyle("headerStyle"); headerStyle.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; headerStyle.Style.Font.Bold = true; headerStyle.Style.Font.Size = 20; workSheet.Cells[curRowIndex, 1].StyleName = "headerStyle"; curRowIndex++; //匯出時間 workSheet.Cells[curRowIndex, 1, 2, maxColumnCount].Merge = true; workSheet.Cells[curRowIndex, 1].Value = "匯出時間:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm"); workSheet.Cells[curRowIndex, 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; } //資料表格標題(列名) for (int i = 0, rowCount = columns.HeaderExportColumnList.Count; i < rowCount; i++) { curRowIndex++; workSheet.Cells[curRowIndex, 1, curRowIndex, maxColumnCount].Style.Font.Bold = true; var curColSpan = 1; for (int j = 0, colCount = columns.HeaderExportColumnList[i].Count; j < colCount; j++) { var colColumn = columns.HeaderExportColumnList[i][j]; var colSpan = FindSpaceCol(workSheet, curRowIndex, curColSpan); if (j == 0) curColSpan = colSpan; var toColSpan = colSpan + colColumn.ColSpan; var cell = workSheet.Cells[curRowIndex, colSpan, colColumn.RowSpan + curRowIndex, toColSpan]; cell.Merge = true; cell.Style.VerticalAlignment = ExcelVerticalAlignment.Center; cell.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; workSheet.Cells[curRowIndex, colSpan].Value = colColumn.Title; curColSpan += colColumn.ColSpan; } } workSheet.View.FreezePanes(curRowIndex + 1, 1);//凍結標題行 Type type = typeof(T); PropertyInfo[] propertyInfos = type.GetProperties(); if (propertyInfos.Count() == 0 && dtSource.Count > 0) propertyInfos = dtSource[0].GetType().GetProperties(); //資料行 for (int i = 0, sourceCount = dtSource.Count(); i < sourceCount; i++) { curRowIndex++; for (var j = 0; j < maxColumnCount; j++) { var column = columns.ExportColumnList[j]; var cell = workSheet.Cells[curRowIndex, j + 1]; foreach (var propertyInfo in propertyInfos) { if (column.Field == propertyInfo.Name) { object value = propertyInfo.GetValue(dtSource[i]); var pType = propertyInfo.PropertyType; pType = pType.Name == "Nullable`1" ? Nullable.GetUnderlyingType(pType) : pType; if (pType == typeof(DateTime)) { cell.Style.Numberformat.Format = "yyyy-MM-dd hh:mm"; cell.Value = Convert.ToDateTime(value); } else if (pType == typeof(int)) { cell.Style.Numberformat.Format = "#0"; cell.Value = Convert.ToInt32(value); } else if (pType == typeof(double) || pType == typeof(decimal)) { if (column.Precision != null) cell.Style.Numberformat.Format = "#,##0.00";//保留兩位小數 cell.Value = Convert.ToDouble(value); } else { cell.Value = value == null ? "" : value.ToString(); } } } } } workSheet.Cells[workSheet.Dimension.Address].Style.Font.Name = "宋體"; workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns();//自動填充 for (var i = 1; i <= workSheet.Dimension.End.Column; i++) { workSheet.Column(i).Width = workSheet.Column(i).Width + 2; }//在填充的基礎上再加2 return package.GetAsByteArray(); } } private static int FindSpaceCol(ExcelWorksheet workSheet, int row, int col) { if (workSheet.Cells[row, col].Merge) { return FindSpaceCol(workSheet, row, col + 1); } return col; } } //匯出所需要對映的欄位和表頭集合 public class ExportColumnCollective { /// <summary> /// 欄位列集合 /// </summary> public List<ExportColumn> ExportColumnList { get; set; } /// <summary> /// 表頭或多表頭集合 /// </summary> public List<List<ExportColumn>> HeaderExportColumnList { get; set; } } //對映excel實體 public class ExportColumn { /// <summary> /// 標題 /// </summary> [JsonProperty("title")] public string Title { get; set; } /// <summary> /// 欄位 /// </summary> [JsonProperty("field")] public string Field { get; set; } /// <summary> /// 精度(只對double、decimal有效) /// </summary> [JsonProperty("precision")] public int? Precision { get; set; } /// <summary> /// 跨列 /// </summary> [JsonProperty("colSpan")] public int ColSpan { get; set; } /// <summary> /// 跨行 /// </summary> [JsonProperty("rowSpan")] public int RowSpan { get; set; } } }
OK,有了通用幫助類庫,剩下的就是針對具體業務所需而提供相應欄位和表頭的隱射,既可以實現檔案的匯出
別忘了新增引用名稱空間using Common.Utils;
我們來看一下API
[HttpGet("ExportExcel") public FileResult ExportExcel() { IList<Gogo> list = new List<Gogo> { new Gogo { Name = "張三", Age = 18, Card = "41234567890", CreateTime = DateTime.Now, }, new Gogo { Name = "李四", Age = 20, Card = "4254645461", CreateTime = DateTime.Now, }, }; //匯出表頭和欄位集合 ExportColumnCollective ecc = new ExportColumnCollective(); //匯出欄位集合 ecc.ExportColumnList = new List<ExportColumn> { new ExportColumn{Field = "Name"}, new ExportColumn{Field = "Card"}, new ExportColumn{Field = "Age"}, new ExportColumn{Field = "CreateTime"}, }; //匯出表頭集合 ecc.HeaderExportColumnList = new List<List<ExportColumn>> { //使用list是為了後續可能有多表頭合併列的需求,這裡只需要單個表頭所以一個list就ok了 new List<ExportColumn> { new ExportColumn{Title = "姓名"}, new ExportColumn{Title = "身份號"}, new ExportColumn{Title = "年齡"}, new ExportColumn{Title = "新增時間"} }, //new List<ExportColumn> //{ // new ExportColumn{Title = "子標題A",ColSpan = 1}, // new ExportColumn{Title = "子標題B",ColSpan = 1} //}, }; byte[] result = Export2Excel.Export<Gogo>(list, ecc, "測試匯出", false); return File(result, "application/vnd.ms-excel", "匯出報表.xlsx"); }