分析需求和场景:
1、一般我们的导出都是在列表页面上,列表是分页的,导出需要不分页导出所有数据和列表的所有列;
2、我们查询数据返回的结果还不是统一的对象,比如有时候是EFCore查询出来的List,有时候是sql直接查询的DataTable。
3、数据查询出来的列顺序并不是我们导出的顺序,比如查询出来ID列在第一个,导出模板是运营部门定的,他不关心ID,只关心Money,要把Money字段放在第一列。也就是说我们的导出列是动态灵活的。
首先说下我的思想,查询是一样的,都是用同一个方法返回数据的话,而且要保证想分页就分页,不想分页就不分页,那么:我将查询数据的PageSize页码参数做一个标识,如果PageSize=0,则代表不分页,底层获取数据的时候就跳过分页的sql。这样就解决了同一个方法供两个地方调用即可分页也可不分页的难题。
接下来就是解决另外两个问题,第三个问题我打算直接在导出生成Execl文件的时候处理,因为NPOI可以很方便的控制列和生成列,那么第二个问题底层方法返回的数据对象类型不一致,我打算采用重载稍微改下应该可行。
设计思路:
调用方直接将导出哪些列用一个字典的方式传过去,辅助类直接根据字典里面字段顺序依次生成列,将数据集合反射得到结果。
直接看代码
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; namespace C.Customization.Framework { /// <summary> /// Npoi辅助类 /// </summary> public class NpoiHepler { /// <summary> /// 实体类集合导出指定字段到EXCLE /// </summary> /// <param name="cellHeard">单元头的Key和Value:{ { "UserName", "姓名" }, { "Age", "年龄" } };</param> /// <param name="enList">数据源</param> /// <param name="sheetName">工作表名称</param> /// <param name="filePath">路径.xls</param> /// <returns> /// 文件的下载地址 /// </returns> public static MessageInfo EntitysToExcel(Dictionary<string, string> cellHeard, IList enList, string sheetName, string filePath) { try { // 1.检测是否存在文件夹,若不存在就建立个文件夹 string directoryName = Path.GetDirectoryName(filePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } // 2.解析单元格头部,设置单元头的中文名称 HSSFWorkbook workbook = new HSSFWorkbook(); // 工作簿 ISheet sheet = workbook.CreateSheet(sheetName); // 工作表 IRow row = sheet.CreateRow(0); List<string> keys = cellHeard.Keys.ToList(); for (int i = 0; i < keys.Count; i++) { row.CreateCell(i).SetCellValue(cellHeard[keys[i]]); // 列名为Key的值 sheet.SetColumnWidth(i, 30 * 256); } // 3.List对象的值赋值到Excel的单元格里 int rowIndex = 1; // 从第二行开始赋值(第一行已设置为单元头) foreach (var en in enList) { IRow rowTmp = sheet.CreateRow(rowIndex); for (int i = 0; i < keys.Count; i++) // 根据指定的属性名称,获取对象指定属性的值 { string cellValue = ""; // 单元格的值 object properotyValue = null; // 属性的值 System.Reflection.PropertyInfo properotyInfo = null; // 属性的信息 // 3.1 若属性头的名称包含'.',就表示是子类里的属性,那么就要遍历子类,eg:UserEn.UserName if (keys[i].IndexOf(".") >= 0) { // 3.1.1 解析子类属性(这里只解析1层子类,多层子类未处理) string[] properotyArray = keys[i].Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries); string subClassName = properotyArray[0]; // '.'前面的为子类的名称 string subClassProperotyName = properotyArray[1]; // '.'后面的为子类的属性名称 System.Reflection.PropertyInfo subClassInfo = en.GetType().GetProperty(subClassName); // 获取子类的类型 if (subClassInfo != null) { // 3.1.2 获取子类的实例 var subClassEn = en.GetType().GetProperty(subClassName).GetValue(en, null); // 3.1.3 根据属性名称获取子类里的属性类型 properotyInfo = subClassInfo.PropertyType.GetProperty(subClassProperotyName); if (properotyInfo != null) { properotyValue = properotyInfo.GetValue(subClassEn, null); // 获取子类属性的值 } } } else { // 3.2 若不是子类的属性,直接根据属性名称获取对象对应的属性 properotyInfo = en.GetType().GetProperty(keys[i]); if (properotyInfo != null) { properotyValue = properotyInfo.GetValue(en, null); } } // 3.3 属性值经过转换赋值给单元格值 if (properotyValue != null) { cellValue = properotyValue.ToString(); // 3.3.1 对时间初始值赋值为空 if (cellValue.Trim() == "0001/1/1 0:00:00" || cellValue.Trim() == "0001/1/1 23:59:59" || cellValue.Trim() == "1970-01-01 00:00:00") { cellValue = ""; } } // 3.4 填充到Excel的单元格里 rowTmp.CreateCell(i).SetCellValue(cellValue); } rowIndex++; } // 4.生成文件 FileStream file = new FileStream(filePath, FileMode.Create); workbook.Write(file); file.Close(); // 5.返回下载路径 return new MessageInfo() { IsSucceed = true, Message = filePath }; } catch (Exception ex) { return new MessageInfo() { IsSucceed = false, Message = ex.Message }; } } /// <summary> /// 实体类集合导出指定字段到EXCLE /// </summary> /// <param name="cellHeard">单元头的Key和Value:{ { "UserName", "姓名" }, { "Age", "年龄" } };</param> /// <param name="enList">数据源</param> /// <param name="sheetName">工作表名称</param> /// <param name="filePath">路径.xls</param> /// <returns> /// 文件的下载地址 /// </returns> public static MessageInfo DataTableToExcel(Dictionary<string, string> cellHeard, DataTable enList, string sheetName, string filePath) { try { // 1.检测是否存在文件夹,若不存在就建立个文件夹 string directoryName = Path.GetDirectoryName(filePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } // 2.解析单元格头部,设置单元头的中文名称 HSSFWorkbook workbook = new HSSFWorkbook(); // 工作簿 ISheet sheet = workbook.CreateSheet(sheetName); // 工作表 IRow row = sheet.CreateRow(0); List<string> keys = cellHeard.Keys.ToList(); for (int i = 0; i < keys.Count; i++) { row.CreateCell(i).SetCellValue(cellHeard[keys[i]]); // 列名为Key的值 sheet.SetColumnWidth(i, 30 * 256); } // 3.List对象的值赋值到Excel的单元格里 int rowIndex = 1; // 从第二行开始赋值(第一行已设置为单元头) for (int en=0;en<enList.Rows.Count;en++) { IRow rowTmp = sheet.CreateRow(rowIndex); for (int i = 0; i < keys.Count; i++) // 根据指定的属性名称,获取对象指定属性的值 { string cellValue = ""; // 单元格的值 object properotyValue = enList.Rows[en][keys[i]]; // 属性的值 // 3.3 属性值经过转换赋值给单元格值 if (properotyValue != null) { cellValue = properotyValue.ToString(); // 3.3.1 对时间初始值赋值为空 if (cellValue.Trim() == "0001/1/1 0:00:00" || cellValue.Trim() == "0001/1/1 23:59:59" || cellValue.Trim() == "1970-01-01 00:00:00") { cellValue = ""; } } // 3.4 填充到Excel的单元格里 rowTmp.CreateCell(i).SetCellValue(cellValue); } rowIndex++; } // 4.生成文件 FileStream file = new FileStream(filePath, FileMode.Create); workbook.Write(file); file.Close(); // 5.返回下载路径 return new MessageInfo() { IsSucceed = true, Message = filePath }; } catch (Exception ex) { return new MessageInfo() { IsSucceed = false, Message = ex.Message }; } } } }
接下来看调用方式:当返回的是List类型的数据时
/// <summary> /// 导出 /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void Tb1_Export_Click(object sender, EventArgs e) { //导出 List<Mem_MemberInfo> mems = Mem_MemberService.GetInstance().GetListAll(); Dictionary<string, string> cellHead = new Dictionary<string, string>(); cellHead[nameof(Mem_MemberInfo.UserName)] = "用户昵称"; cellHead[nameof(Mem_MemberInfo.Mobile)] = "手机号"; cellHead[nameof(Mem_MemberInfo.Balance)] = "余额"; cellHead[nameof(Mem_MemberInfo.RealName)] = "真实姓名"; cellHead[nameof(Mem_MemberInfo.IdCardNum)] = "身份证号码"; cellHead[nameof(Mem_MemberInfo.CreateTime)] = "注册时间"; cellHead[nameof(Mem_MemberInfo.Freeze)] = "冻结金额"; cellHead[nameof(Mem_MemberInfo.IdentityName)] = "等级"; string filename = $"用户数据{DateTime.Now:yyyyMMddHHmmss}.xls"; string filepath = Server.MapPath($"{PageParam.DocumentPath}{filename}"); MessageInfo msg = NpoiHepler.EntitysToExcel(cellHead, mems, "用户列表", filepath); if (msg.IsSucceed == false) { Alert.ShowInTop("导出失败" + msg.Message, MessageBoxIcon.Error); return; } FileInfo file = new FileInfo(msg.Message); Response.Clear(); Response.ClearContent(); Response.ClearHeaders(); Response.AddHeader("Content-Disposition", "attachment;filename=" + filename); Response.AddHeader("Content-Length", file.Length.ToString()); Response.AddHeader("Content-Transfer-Encoding", "gb2312"); Response.ContentType = "application/octet-stream"; Response.ContentEncoding = System.Text.Encoding.GetEncoding("gb2312"); Response.WriteFile(filepath); Response.Flush(); Response.End(); }
这段代码里面使用的将文件直接按文件流的方式返回,当然了,你都得到了导出文件的地址了,用什么方式给前端,都可以。
再看另一种导出DataTable类型的数据:
/// <summary> /// 导出 /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void Tb1_Export_Click(object sender, EventArgs e) { //导出 PageDataBaseInfo pagedata = FUHelper.GridPageData(Grid1, ttbSearch.Text); pagedata.PageSize = 0; DataTable dt = Record_WithdrawalService.GetInstance().FindDt(pagedata, "2", RblStatus.SelectedValue); Dictionary<string, string> cellHead = new Dictionary<string, string>(); cellHead["SysNo"] = "系统编号"; cellHead["Mobile"] = "提现账户"; cellHead["StateName"] = "状态"; cellHead["CreateTime"] = "申请时间"; cellHead["Money"] = "提现金额"; cellHead["Balance"] = "账户余额"; cellHead["Freeze"] = "冻结金额"; cellHead["Remark"] = "备注"; string filename = $"提现记录{DateTime.Now:yyyyMMddHHmmss}.xls"; string filepath = Server.MapPath($"{PageParam.DocumentPath}{filename}"); MessageInfo msg = NpoiHepler.DataTableToExcel(cellHead, dt, "提现记录", filepath); if (msg.IsSucceed == false) { Alert.ShowInTop("导出失败" + msg.Message, MessageBoxIcon.Error); return; } FileInfo file = new FileInfo(msg.Message); Response.Clear(); Response.ClearContent(); Response.ClearHeaders(); Response.AddHeader("Content-Disposition", "attachment;filename=" + filename); Response.AddHeader("Content-Length", file.Length.ToString()); Response.AddHeader("Content-Transfer-Encoding", "gb2312"); Response.ContentType = "application/octet-stream"; Response.ContentEncoding = System.Text.Encoding.GetEncoding("gb2312"); Response.WriteFile(filepath); Response.Flush(); Response.End(); }
注意其中的一句代码:pagedata.PageSize = 0;
这就是我开头所说的利用PageSize来区分是否需要分页。
到这里就结束了,如果有更好的方式,请通知我!
留下您的脚步
最近评论