NET 5 Execl導入數據處理(EppLus、NPOI、Npoi.Mapper)


先來簡單介紹下市面上最廣泛常見的三種操作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");
}

 導入

[HttpPost]
public List<ExcelDemoDto> Import([FromForm] ImportExcelInput input)
{
    var list = new List<ExcelDemoDto>();

    using (var package = new ExcelPackage(input.ExcelFile.OpenReadStream()))
    {
        // 獲取到第一個Sheet,也可以通過 Worksheets["name"] 獲取指定的工作表
        var sheet = package.Workbook.Worksheets.First();

        #region 獲取開始和結束行列的個數,根據個數可以做各種校驗工作

        // +1 是因為第一行往往我們獲取到的都是Excel的標題
        int startRowNumber = sheet.Dimension.Start.Row + 1;
        int endRowNumber = sheet.Dimension.End.Row;
        int startColumn = sheet.Dimension.Start.Column;
        int endColumn = sheet.Dimension.End.Column;

        #endregion

        // 循環獲取整個Excel數據表數據
        for (int currentRow = startRowNumber; currentRow <= endRowNumber; currentRow++)
        {
            list.Add(new ExcelDemoDto
            {
                AAA = sheet.Cells[currentRow, 1].Text,
                BBB = sheet.Cells[currentRow, 2].Text,
                CCC = sheet.Cells[currentRow, 3].Text,
                DDD = sheet.Cells[currentRow, 4].Text,
                EEE = sheet.Cells[currentRow, 5].Text,
                FFF = sheet.Cells[currentRow, 6].Text
            });
        }
    }

    return list;
}

public class ExcelDemoDto
{
    public string AAA { get; set; }
    public string BBB { get; set; }
    public string CCC { get; set; }
    public string DDD { get; set; }
    public string EEE { get; set; }
    public string FFF { get; set; }
}

public class ImportExcelInput
{
    public IFormFile ExcelFile { get; set; }
}

導出

 

[HttpGet]
public async Task<string> Export()
{
    using var package = new ExcelPackage();
    var worksheet = package.Workbook.Worksheets.Add("sheet1");

    var headers = new string[] { "AAA", "BBB", "CCC", "DDD", "EEE", "FFF" };
    for (int i = 0; i < headers.Length; i++)
    {
        worksheet.Cells[1, i + 1].Value = headers[i];
        worksheet.Cells[1, i + 1].Style.Font.Bold = true;
    }

    // 模擬數據
    var list = new List<ExcelDemoDto>();
    for (int i = 1; i <= 10; i++)
    {
        list.Add(new ExcelDemoDto
        {
            AAA = $"A{i}",
            BBB = $"B{i}",
            CCC = $"C{i}",
            DDD = $"D{i}",
            EEE = $"E{i}",
            FFF = $"F{i}"
        });
    }

    // 支持各種直接獲取數據的方法
    // worksheet.Cells.Load*...

    int row = 2;
    foreach (var item in list)
    {
        worksheet.Cells[row, 1].Value = item.AAA;
        worksheet.Cells[row, 2].Value = item.BBB;
        worksheet.Cells[row, 3].Value = item.CCC;
        worksheet.Cells[row, 4].Value = item.DDD;
        worksheet.Cells[row, 5].Value = item.EEE;
        worksheet.Cells[row, 6].Value = item.FFF;

        row++;
    }

    // 通常做法是,將excel上傳至對象存儲,獲取到下載鏈接,這里將其輸出到項目根目錄。
    var path = Path.Combine(Directory.GetCurrentDirectory(), $"excel.xlsx");
    await package.GetAsByteArray().DownloadAsync(path);
    return path;
}

 Npoi.Mapper

 https://www.cnblogs.com/wucy/p/14125392.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM