• .Net Core 如何数据导出 Excel?(EPPlus->OfficeOpenXml 实现固定列和动态列导出)



    〇、前言

    特别注意:本文涉及的包(OfficeOpenXml.Extension.AspNetCore)依赖于 EPPlus 5.0.3 等更高版本,属于限制商业用途版本,因此只能用作个人或公司小范围内部使用。

    对于将数据以 Excel 表格文件输出,还是比较常用的,也存在诸多情况,比如列固定或不固定、数据类型为 List或 Json 对象等。

    本文通过包 OfficeOpenXml.Extension.AspNetCore 实现两个方法导出列数固定和不固定的数据。

    注意:OfficeOpenXml.Extension.AspNetCore 是一个基于 OfficeOpenXml 拓展,它依赖于 EPPlus,用于根据模板输出 Excel。

    包控制台安装:

    NuGet> Install-Package OfficeOpenXml.Extension.AspNetCore -Version 1.0.0

    一、根据已知类型对象 List 下载

    本章节方法适用背景:

      数据列数固定,且可罗列。

    对于对象 List 的属性,一般不会命名为汉字,那么就需要将列名转换为汉字,以方便数据清晰显示。

    如下为一个基于 WebAPI 项目的固定列数,动态行数的下载实例:

    // 安装包:OfficeOpenXml.Extension.AspNetCore // 支持 Core 3.1 及以上,Standard 2.0 及以上
    using OfficeOpenXml;
    using OfficeOpenXml.Style;
    using Microsoft.AspNetCore.Mvc; // File 支持
    [HttpGet]
    public FileContentResult DownloadInfo()
    {
    try
    {
    string sql_datalist = "";
    var resultlist = dapperFactory.Query(sql_datalist).ToList();
    string sFileName = $"{Guid.NewGuid()}.xlsx";
    FileInfo fileinfo = new FileInfo(System.IO.Path.Combine("C:/Files", sFileName));
    ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
    using (ExcelPackage package = new ExcelPackage(fileinfo))
    {
    // 添加 worksheet
    ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("明细表");
    // 添加头
    worksheet.Cells[1, 1].Value = "序号";
    worksheet.Cells[1, 2].Value = "公司";
    worksheet.Cells[1, 3].Value = "日期";
    // 添加值
    for (int i = 0; i < resultlist.Count; i++)
    {
    worksheet.Cells["A" + (i + 2)].Value = resultlist[i].xuhao;
    worksheet.Cells["B" + (i + 2)].Value = resultlist[i].gongsimc;
    worksheet.Cells["C" + (i + 2)].Value = resultlist[i].riqi.Substring(0,10);
    }
    worksheet.Column(1).Width = 10; // 设置列宽,从第 1 列开始
    worksheet.Column(2).Width = 30;
    worksheet.Column(3).Width = 15;
    worksheet.Row(1).Style.Font.Bold = true; // 给第一行内容设置加粗
    worksheet.Cells.Style.WrapText = true; // 自动换行
    worksheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
    // 给有数据的区域添加边框
    using (ExcelRange excelRange = worksheet.Cells[1, 1, resultlist.Count + 1, 3]) // [从第一行开始,从第一列开始,到第几行,到第几列]
    {
    r.Style.Border.Top.Style = ExcelBorderStyle.Thin; // 实线
    r.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Left.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Right.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Top.Color.SetColor(System.Drawing.Color.Black); // 黑色
    r.Style.Border.Bottom.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Left.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Right.Color.SetColor(System.Drawing.Color.Black);
    }
    worksheet.Row(1).Style.Font.Bold = true;
    worksheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; // 第一行内容水平居中
    package.Save();
    }
    using (FileStream fs= fileinfo.OpenRead())
    {
    BinaryReader br = new BinaryReader(fs);
    br.BaseStream.Seek(0, SeekOrigin.Begin); // 将文件指针设置到文件开
    byte[] fileBytes = br.ReadBytes((int)br.BaseStream.Length);
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, sFileName); // 返回文件对象,前端可直接进行下载动作
    }
    }
    catch (Exception ex)
    {
    return null;
    }
    }

     二、动态列数据库下载

    本章节方法适用背景:

      数据列数不固定,且可循环取出,表头和对应的数据顺序相同。

    动态列就是,列总数不固定,程序根据传入的列数确定第一行表头。

    下面是一个根据 json 字符串,以 Excel 文件形式保存数据的实例。其中包含 “tableheader”节点,来指定动态表头;“tablebody”代表全部数据列表。

    // 安装包:OfficeOpenXml.Extension.AspNetCore // 支持 Core 3.1 及以上,Standard 2.0 及以上
    // 由于 Excel 2003 版本 和 2007 之后版本文件结构的差异性,当前扩展无法同时兼容两种模式,仅支持 *.xlsx 文件
    using OfficeOpenXml;
    using OfficeOpenXml.Style;
    public void DownloadByJsonstr(string xiazaisj)
    {
    // string jsonstr = "{\"tableheader\":[{\"mingcheng\":\"列名一\",\"daima\":\"bumenx1\",\"shifoutz\":true},{\"mingcheng\":\"列名二\",\"daima\":\"bumenx2\",\"shifoutz\":true}],\"tablebody\":[{\"kemumc\":\"科目一\",\"bumenx1\":0.12,\"bumenx2\":6.0,\"heji\":6.12,\"erjimx\":[{\"kemumc\":\"科目一明细科目1\",\"bumenx1\":0.0,\"bumenx2\":9.82,\"heji\":9.82},{\"kemumc\":\"科目一明细科目2\",\"bumenx1\":0.12,\"bumenx2\":6.18,\"heji\":6.3}]}]}";
    var baobiaosj_json = Json_Object.StrToJson(xiazaisj);
    var tableheader = xiazaisj_json["tableheader"];
    // 前两列表头固定
    List<string> headersname = new List<string>(){ "科目", "合计" }; // 用于显示
    List<string> headerscode = new List<string>(){ "kemumc", "heji" }; // 用于取值
    // 从第三列开始,按默认顺序加入后续表头
    foreach (var thj in tableheader)
    {
    headersname.Add(thj["mingcheng"].ToString());
    headerscode.Add(thj["daima"].ToString());
    }
    string sFileName = $"{Guid.NewGuid()}.xlsx";
    FileInfo fileinfo = new FileInfo(System.IO.Path.Combine("C:/Log", sFileName));
    ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
    List tableRowModels = new List();
    using (ExcelPackage package = new ExcelPackage(fileinfo))
    {
    ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("数据表");
    EpplusHelperPublic.AddHeader(worksheet, headersname.ToArray()); // 添加表头
    var tablebody = xiazaisj_json["tablebody"];
    TableRowModel tableRowModel = new TableRowModel();
    Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
    foreach (var tablebody_first in tablebody)
    {
    tableRowModel = new TableRowModel();
    keyValuePairs = new Dictionary<string, string>();
    tableRowModel.kemumc = tablebody_first["kemumc"].ToString(); // "kemumc": "成本"
    tableRowModel.heji = tablebody_first["heji"].ToString();
    for (int ii = 2; ii < headerscode.Count; ii++)
    {
    keyValuePairs.Add(headerscode[ii], tablebody_first[headerscode[ii]].ToString()); // "bumenx1": 0.0
    }
    tableRowModel.dict_lie = keyValuePairs;
    tableRowModels.Add(tableRowModel);
    if (tablebody_first["erjimx"] != null)
    {
    foreach(var tablebody_second in tablebody_first["erjimx"])
    {
    tableRowModel = new TableRowModel();
    keyValuePairs = new Dictionary<string, string>();
    tableRowModel.kemumc = tablebody_second["kemumc"].ToString(); // "kemumc": "成本"
    tableRowModel.heji = tablebody_second["heji"].ToString();
    for (int ii = 2; ii < headerscode.Count; ii++)
    {
    keyValuePairs.Add(headerscode[ii], tablebody_second[headerscode[ii]].ToString()); // "bumenx1": 0.0
    }
    tableRowModel.dict_lie = keyValuePairs;
    tableRowModels.Add(tableRowModel);
    }
    }
    }
    if (tableRowModels.Count > 0)
    {
    //添加动态数据
    EpplusHelperPublic.AddObjects(worksheet, 2, tableRowModels, headerscode);
    }
    worksheet.Column(1).Width = 20; // 设置列宽
    worksheet.Column(2).Width = 20;
    for (int ii = 3; ii <= headerscode.Count; ii++) // 为动态列设置统一列宽
    {
    worksheet.Column(ii).Width = 16;
    }
    //worksheet.Cells.Style.WrapText = true; // 自动换行
    worksheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
    using (ExcelRange r = worksheet.Cells[1, 1, tableRowModels.Count + 1, headersname.Count])
    {
    r.Style.Border.Top.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Left.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Right.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Top.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Bottom.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Left.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Right.Color.SetColor(System.Drawing.Color.Black);
    }
    worksheet.View.FreezePanes(2, 3); // 冻结第一行,以及前两列
    worksheet.Row(1).Style.Font.Bold = true; // 第一行加粗
    worksheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;// 第一行水平居中
    package.Save();
    }
    }

     EpplusHelperPublic.cs 类,用作循环添加表头和数据。

    using System;
    using System.Collections.Generic;
    using OfficeOpenXml;
    public static class EpplusHelperPublic
    {
    ///
    /// 通过 名称数组 添加表头
    ///
    ///
    ///
    public static void AddHeader(ExcelWorksheet sheet, params string[] headertexts)
    {
    for (var i = 0; i < headertexts.Length; i++)
    {
    AddHeader(sheet, i + 1, headertexts[i]);
    }
    }
    ///
    /// 直接添加表头
    ///
    ///
    ///
    ///
    public static void AddHeader(ExcelWorksheet sheet, int columnindex, string headertext)
    {
    sheet.Cells[1, columnindex].Value = headertext;
    sheet.Cells[1, columnindex].Style.Font.Bold = true;
    }
    ///
    /// 添加动态数据
    ///
    ///
    /// 从第几行开始
    /// 行数据列表
    /// 列名代码列表,用于取数据
    public static void AddObjects(ExcelWorksheet worksheet, int startrowindex, IList items, List<string> headerscode)
    {
    for (var i = 0; i < items.Count; i++)
    {
    worksheet.Cells[i + startrowindex, 1].Value = items[i].kemumc; // 注意此处为兼容前两列固定列
    worksheet.Cells[i + startrowindex, 2].Value = items[i].heji;
    for (var j = 2; j < headerscode.Count; j++) // headercode:kemumc,heji,bumenx1,bumenx2...
    {
    worksheet.Cells
    [i + startrowindex,
    j + 1]
    .Value
    = items[i].dict_lie[headerscode[j]];
    }
    }
    }
    }

    代码参考:C# 使用Epplus导出Excel [2]:导出动态列数据

    三、多目标对象的情况下统一下载入口 --20230717

    注意:本章节通过定义一个通用的对象,将不同表的不同字段对应到统一的对象,在对统一的对象进行下载操作。

    以下为实现代码,其中也用到了本文第二章节中的 EpplusHelperPublic.cs 类。

    代码中,将两个不同的对象,通过数据库 SQL 语句,让查询结果为同一对象,从而方便统一导出操作。

    ///
    /// 下载配置列表(共用)
    ///
    ///
    ///
    [HttpPost]
    public void DownloadConfigList([FromBody] InParametersModel inpara)
    {
    List<string> tableheaders = new List<string>();
    string tablename = string.Empty,selectparameters = string.Empty,condition =string.Empty;
    DynamicParameters dynamicParameters = new DynamicParameters();
    int lieshu = 0;
    switch (inpara.xiazailx) // 下载类型 1公司 2部门
    {
    case 1: // 1名称
    tableheaders = new List<string>() { "序号", "编码", "公司名称", "公司系" };
    tablename = "CompanyInfo";
    selectparameters = "ROW_NUMBER() OVER (ORDER BY gongsibm) xuhao,gongsibm lieming1,gongsimc lieming2,gongsix lieming3";
    if (inpara.mingcheng != null && inpara.mingcheng.Length > 0)
    {
    condition = "and (gongsibm like @gongsibm or gongsimc like @gongsibm) ";
    dynamicParameters.Add("@gongsibm", $"%{inpara.mingcheng}%");
    }
    lieshu = tableheaders.Count;
    break;
    case 2: // 2部门
    tableheaders = new List<string>() { "序号", "编码", "部门名称", "描述", "上级部门" };
    tablename = "DeptmentInfo";
    selectparameters = "ROW_NUMBER() OVER (ORDER BY bumenbm) xuhao,bumenbm lieming1,bumenmc lieming2,miaoshu lieming3,shangjibmbm lieming4";
    if (inpara.mingcheng != null && inpara.mingcheng.Length > 0)
    {
    condition = "and (bumenbm like @bumenbm or bumenmc like @bumenbm) ";
    dynamicParameters.Add("@bumenbm", $"%{inpara.mingcheng}%");
    }
    if (inpara.shifoujy != null && inpara.shifoujy.Length > 0)
    {
    dynamicParameters.Add("@shifoujy", inpara.shifoujy);
    condition += "and shifoujy = @shifoujy ";
    }
    lieshu = tableheaders.Count;
    break;
    default:
    return null;
    }
    // AnonymousModel 为通用 Model,字段为 xuhao、lieming1、lieming2、、、
    var resultlist = dapperFactory.Query($"select {selectparameters} from {tablename} where Flag>0 {condition}", dynamicParameters).ToList();
    string sFileName = $"{Guid.NewGuid()}.xlsx";
    FileInfo fileinfo = new FileInfo(System.IO.Path.Combine("C:/Log", sFileName));
    ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
    List tableRowModels = new List();
    using (ExcelPackage package = new ExcelPackage(fileinfo))
    {
    ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("配置信息表");
    EpplusHelperPublic.AddHeader(worksheet, tableheaders.ToArray()); // 添加表头
    List<string> headerscode = new List<string>();
    TableRowPublicModel tableRowModel = new TableRowPublicModel();
    Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
    foreach (var resultinfo in resultlist)
    {
    tableRowModel = new TableRowPublicModel();
    // 通过反射来取对象的属性代码和值 https://www.cnblogs.com/chenwolong/p/fanshe.html
    Type t = resultinfo.GetType();
    PropertyInfo[] PropertyList = t.GetProperties();
    keyValuePairs = new Dictionary<string, string>();
    foreach (PropertyInfo item in PropertyList)
    {
    object value = item.GetValue(resultinfo, null);
    keyValuePairs.Add(item.Name, value?.ToString());
    if(headerscode.Count< lieshu) // 取对应的有效列数
    headerscode.Add(item.Name);
    }
    tableRowModel.dict_lie = keyValuePairs;
    tableRowModels.Add(tableRowModel);
    }
    if (tableRowModels.Count > 0)
    {
    //添加动态数据
    EpplusHelperPublic.AddObjects(worksheet, 2, tableRowModels, headerscode);
    }
    worksheet.Column(1).Width = 8; // 设置列宽
    for (int ii = 2; ii <= headerscode.Count; ii++)
    {
    worksheet.Column(ii).Width = 20;
    }
    //worksheet.Cells.Style.WrapText = true; // 自动换行
    worksheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
    using (ExcelRange r = worksheet.Cells[1, 1, tableRowModels.Count + 1, tableheaders.Count])
    {
    r.Style.Border.Top.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Left.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Right.Style = ExcelBorderStyle.Thin;
    r.Style.Border.Top.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Bottom.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Left.Color.SetColor(System.Drawing.Color.Black);
    r.Style.Border.Right.Color.SetColor(System.Drawing.Color.Black);
    }
    worksheet.View.FreezePanes(2, 2); // 冻结第一行,以及第一列
    worksheet.Row(1).Style.Font.Bold = true; // 第一行加粗
    worksheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;// 第一行水平居中
    package.Save();
    }
    }
    public class TableRowPublicModel
    {
    public Dictionary<string,string> dict_lie { get; set; }
    }
  • 相关阅读:
    融合微平衡激活的小孔成像算术优化算法
    读取Excel的工具类——ExcelKit
    【C语言】深度剖析动态内存管理
    Netty学习(二)概述+EventLoop+Channel+ByteBuf
    Web安全漏洞解决方案
    psutils连接虚拟机
    ramdisk根文件系统
    数据科学与机器学习案例之WiFi定位系统的位置预测
    QCC51XX---GATT
    eclipse汉化方法
  • 原文地址:https://www.cnblogs.com/hnzhengfy/p/OfficeOpenXmlDownload.html