最近在想.net core NPOI 導入導出Excel,一開始感覺挺簡單的,后來真的遇到很多坑。所以還是寫一篇博客讓其他人少走一些彎路,也方便忘記了再重溫一遍。好了,多的不說,直接開始吧。
在.Net core 使用NPOI首先必須先安裝DotNetCore.NPOI,第一種方法可以在管理Nuget包中安裝,如圖:
直接搜索DotNet.NPOI 安裝就可以了。
第二種方法就是在Nuget程序包控制台中輸入 Install-Package DotNetCore.NPOI 然后回車就可以了。
首先導出,和網上很多例子一樣,我的想法一開始是用datatable導出到Excel中,但是感覺datatable的性能不夠好,而且局限比較大,不過一開始我還是用datatable導出來了。
代碼如下:
public static byte[] Output(DataTable dataTable, string[] tableTitle) { NPOI.SS.UserModel.IWorkbook workbook = new NPOI.XSSF.UserModel.XSSFWorkbook(); NPOI.SS.UserModel.ISheet sheet = workbook.CreateSheet("sheet"); IRow Title = null; IRow rows = null; for (int i = 1; i <= dataTable.Rows.Count; i++) { //創建表頭
if (i - 1 == 0) { Title = sheet.CreateRow(0); for (int k = 1; k < tableTitle.Length + 1; k++) { Title.CreateCell(0).SetCellValue("序號"); Title.CreateCell(k).SetCellValue(tableTitle[k - 1]); } continue; } else { rows = sheet.CreateRow(i - 1); for (int j = 1; j <= dataTable.Columns.Count; j++) { rows.CreateCell(0).SetCellValue(i - 1); rows.CreateCell(j).SetCellValue(dataTable.Rows[i - 1][j - 1].ToString()); } } } byte[] buffer = new byte[1024 * 5]; using (MemoryStream ms = new MemoryStream()) { workbook.Write(ms); buffer = ms.ToArray(); ms.Close(); } return buffer; }
這里返回的是byte數組是為了方便在控制器調用方法后中返回一個文件給客戶端。
return File(buffer, "application/ms-excel", "list.xlsx");
這樣客戶端直接會有一個文件下載。
當然別忘了引用
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
這里XSSF是適用於xlsx,如果是xls的話引用using NPOI.HSSF.UserModel;
可以發現這里的數據源是datatable類型的,對於使用EF Core 操作數據庫不是特別方便,所以還是改用list泛型去導出會方便很多。
代碼如下:
public static byte[] OutputExcel(List<T> entitys, string[] title) { IWorkbook workbook = new XSSFWorkbook(); ISheet sheet = workbook.CreateSheet("sheet"); IRow Title = null; IRow rows = null; Type entityType = entitys[0].GetType(); PropertyInfo[] entityProperties = entityType.GetProperties(); for (int i = 0; i <= entitys.Count; i++) { if (i == 0) { Title = sheet.CreateRow(0); for (int k = 1; k < title.Length + 1; k++) { Title.CreateCell(0).SetCellValue("序號"); Title.CreateCell(k).SetCellValue(title[k - 1]); } continue; } else { rows = sheet.CreateRow(i); object entity = entitys[i - 1]; for (int j = 1; j <= entityProperties.Length; j++) { object[] entityValues = new object[entityProperties.Length]; entityValues[j - 1] = entityProperties[j - 1].GetValue(entity); rows.CreateCell(0).SetCellValue(i); rows.CreateCell(j).SetCellValue(entityValues[j - 1].ToString()); } } } byte[] buffer = new byte[1024 * 2]; using (MemoryStream ms = new MemoryStream()) { workbook.Write(ms); buffer = ms.ToArray(); ms.Close(); } return buffer; }
因為list<T>可以直接得出行數,所以得出行數很方便,但是不知道list<T>有多少列,這個時候需要用到反射,記得引用System.Reflection 。通過獲取list<T>類型去獲取list<T>的屬性。
然后通過 entityProperties.Length 獲取列數。當然這里要留意第一列是表頭,第二列才是數據,導出的時候從第二列開始。
通用引用類型去獲取一行的值object entity = entitys[i - 1];然后通過屬性數組去獲取每一列的值,這里的邏輯有點亂,細心一點就一目了然了。object[] entityValues = new object[entityProperties.Length]; entityValues[j - 1] = entityProperties[j - 1].GetValue(entity);
這里是先寫到內存中再導出,可能數據多了的時候會慢,不過一般是不會的。導出在這里就結束了。
然后是導入:
還是因為EF Core的原因,感覺還是導入成List<T>最方便,因為是公共方法,所以根據實體類型不同,要兼容所有實體類型,泛型似乎是最好的選擇。
在網上找了很多資料,也看到了很多方法實現,不過不是特別適合我現在所在的項目,所以還是自己結合網上的重寫,忘了借鑒了的文章的作者,若有侵權,馬上刪除。
代碼如下:
/// <summary> /// 導入Excel /// </summary> /// <param name="file">導入文件</param> /// <returns>List<T></returns> public static List<T> InputExcel(IFormFile file) { List<T> list = new List<T> { }; MemoryStream ms = new MemoryStream(); file.CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); IWorkbook workbook = new XSSFWorkbook(ms); ISheet sheet = workbook.GetSheetAt(0); IRow cellNum = sheet.GetRow(0); var propertys = typeof(T).GetProperties(); string value = null; int num = cellNum.LastCellNum; for (int i = 1; i <= sheet.LastRowNum; i++) { IRow row = sheet.GetRow(i); var obj = new T(); for (int j = 0; j < num; j++) { value = row.GetCell(j).ToString(); string str = (propertys[j].PropertyType).FullName; if (str == "System.String") { propertys[j].SetValue(obj, value, null); } else if (str == "System.DateTime") { DateTime pdt = Convert.ToDateTime(value, CultureInfo.InvariantCulture); propertys[j].SetValue(obj, pdt, null); } else if (str == "System.Boolean") { bool pb = Convert.ToBoolean(value); propertys[j].SetValue(obj, pb, null); } else if (str == "System.Int16") { short pi16 = Convert.ToInt16(value); propertys[j].SetValue(obj, pi16, null); } else if (str == "System.Int32") { int pi32 = Convert.ToInt32(value); propertys[j].SetValue(obj, pi32, null); } else if (str == "System.Int64") { long pi64 = Convert.ToInt64(value); propertys[j].SetValue(obj, pi64, null); } else if (str == "System.Byte") { byte pb = Convert.ToByte(value); propertys[j].SetValue(obj, pb, null); } else { propertys[j].SetValue(obj, null, null); } } list.Add(obj); } return list; }
數據源是IFormFile類型的,因為前端導入文件的方法是用layui的。所以是IFormFile。
同樣我們也不知道list<T>是什么類型的,所以我們還是得用反射去獲取類型,而且這里還是寫到內存里面,就不會在服務器上留下文件,不過服務器內存小的要注意一下。
然后我們也不知道屬性是什么類型的,所以要 string str = (propertys[j].PropertyType).FullName;
判斷屬性的數據類型,然后在從Excel中遍歷每一個單元格,輸出到list中就可以了,記得要留意下數據在第二行開始導入進去。
大致的問題就是這些,重點就是反射去獲取list<T>的類型。關於Excel的操作,這里就不詳細說明了。
總的來說,NPOI可以脫離微軟Excel操作Excel實在是非常方便,oledb真的非常不好用。
最后,我還需要學習的很多,有什么錯誤請指出。謝謝大家!