开源 - Ideal库 - Excel帮助类,ExcelHelper实现(五)

书接上回,我们继续来聊聊ExcelHelper的具体实现。

在这里插入图片描述

01、读取Excel到DataSet单元测试

在上一章我们主要讲解了读取Excel到DataSet的三个重载方法具体实现,还没来得及做单元测试,因此我们首先对这三个方法做个单元测试。具体代码如下:

[Fact]
public void Read_FileName_DataSet()
{//读取所有工作簿var dataSet = ExcelHelper.Read("Read.xlsx");Assert.Equal(3, dataSet.Tables.Count);var table1 = dataSet.Tables[0];Assert.Equal("Sheet1", table1.TableName);Assert.Equal("A", table1.Rows[0][0]);Assert.Equal("B", table1.Rows[0][1]);Assert.Equal("1", table1.Rows[0][2]);Assert.Equal("C", table1.Rows[1][0]);Assert.Equal("D", table1.Rows[1][1]);Assert.Equal("2", table1.Rows[1][2]);//读取所有工作簿,并且首行数据作为表头dataSet = ExcelHelper.Read("Read.xlsx", true);Assert.Equal(3, dataSet.Tables.Count);table1 = dataSet.Tables[1];var columus = table1.Columns;Assert.Equal("Sheet2", table1.TableName);Assert.Equal("E", columus[0].ColumnName);Assert.Equal("F", columus[1].ColumnName);Assert.Equal("3", columus[2].ColumnName);Assert.Equal("G", table1.Rows[0][0]);Assert.Equal("H", table1.Rows[0][1]);Assert.Equal("4", table1.Rows[0][2]);//根据工作簿名称sheetName读取指定工作簿dataSet = ExcelHelper.Read("Read.xlsx", true, "Sheet2");Assert.Single(dataSet.Tables);Assert.Equal("Sheet2", dataSet.Tables[0].TableName);//通过工作簿名称sheetName读取不存在的工作簿dataSet = ExcelHelper.Read("Read.xlsx", true, "Sheet99");Assert.Empty(dataSet.Tables);//同时指定sheetName和sheetNumber优先使用sheetNamedataSet = ExcelHelper.Read("Read.xlsx", true, "Sheet1", 2);Assert.Single(dataSet.Tables);Assert.Equal("Sheet1", dataSet.Tables[0].TableName);//通过工作簿编号sheetNumber读取不存在的工作簿dataSet = ExcelHelper.Read("Read.xlsx", true, null, 99);Assert.Empty(dataSet.Tables);//通过工作簿编号sheetNumber读取指定工作簿dataSet = ExcelHelper.Read("Read.xlsx", true, null, 1);Assert.Single(dataSet.Tables);Assert.Equal("Sheet1", dataSet.Tables[0].TableName);
}

02、根据文件路径读取Excel到对象集合```

在上一章中我们实现了Excel与DataSet相互转换,而在前面TableHelper实现章节中我们已经实现了对象集合与表格DataTable的相互转换,因此我们只要把这两者结合起来就可以实现Excel与对象集合的相互转换。

因为Excel中有多个工作簿Sheet,而每一个工作簿Sheet代表一个表格DataTable,一个表格DataTable关联一个对象集合,因此我们约定本方法必须指定一个工作簿Sheet用来转换对象集合,如果没有指定则默认读取第一个工作簿Sheet。

而该方法通过文件完全路径读取到Excel文件流后,调用具体实现文件流处理重载方法,具体代码如下:

//根据文件路径读取Excel到对象集合
//指定sheetName,sheetNumber则读取相应工作簿Sheet
//如果不指定则默认读取第一个工作簿Sheet
public static IEnumerable<T> Read<T>(string path, bool isFirstRowAsColumnName = false, string? sheetName = null, int? sheetNumber = null)
{using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);return Read<T>(stream, IsXlsxFile(path), isFirstRowAsColumnName, sheetName, sheetNumber);
}

03、根据文件流、文件名读取Excel到对象集合

在有些场景下,我们直接得到的就是Excel文件流,因此更通用的处理方式就是处理ExceL文件流,因为无论如何最终我们都是要拿到Excel文件流的。

该方法也是一个重载方法,为了方便哪些上传文件后,有文件流,有文件名,但是不想自己处理文件后缀格式的,提供一个便捷方法,因此该方法会通过文件名识别出文件具体后缀格式,再调用下一个重载方法,具体实现如下:

//根据文件流读取Excel到对象集合
//指定sheetName,sheetNumber则读取相应工作簿Sheet
//如果不指定则默认读取第一个工作簿Sheet
public static IEnumerable<T> Read<T>(Stream stream, string fileName, bool isFirstRowAsColumnName = false, string? sheetName = null, int? sheetNumber = null)
{return Read<T>(stream, IsXlsxFile(fileName), isFirstRowAsColumnName, sheetName, sheetNumber);
}

04、根据文件流、文件后缀读取Excel到对象集合

该方法是上面两个方法的最终实现,具体实现分为两步:

(1)读取指定工作簿Sheet到DataSet中;

(2)把DataSet中第一个表格DataTable转换为对象集合;

而这两步都是调用之前实现好的方法,具体代码如下:

//根据文件流读取Excel到对象集合
//指定sheetName,sheetNumber则读取相应工作簿Sheet
//如果不指定则默认读取第一个工作簿Sheet
public static IEnumerable<T> Read<T>(Stream stream, bool isXlsx, bool isFirstRowAsColumnName = false, string? sheetName = null, int? sheetNumber = null)
{//读取指定工作簿Sheet至DataSetvar dataSet = CreateDataSetWithStreamOfSheet(stream, isXlsx, isFirstRowAsColumnName, sheetName, sheetNumber ?? 1);if (dataSet == null || dataSet.Tables.Count == 0){return [];}//DataTable转对象集合return TableHelper.ToModels<T>(dataSet.Tables[0]);
}

下面我们针对上面三个方法做个简单的单元测试,代码如下:

public class Student
{public string A { get; set; }[Description("B")]public string Name { get; set; }[Description("1")]public DateTime Age { get; set; }
}
[Fact]
public void Read_FileName_T()
{//表格数据格式无法转为对象数据类型,则抛异常Assert.Throws<FormatException>(() => ExcelHelper.Read<Student>("Read.xlsx", true, "Sheet1"));//表格成功转为对象集合var models = ExcelHelper.Read<Student>("Read.xlsx", true, "Sheet3");Assert.Single(models);var model = models.First();Assert.Equal("C", model.A);Assert.Equal("D", model.Name);Assert.Equal(new DateTime(2024, 11, 29), model.Age);
}

05、把表格数组写入Excel文件流

该方法是先把表格数组生成Excel的IWorkbook,然后再写入内存流MemoryStream。

而表格数组转换为IWorkbook也很简单,在IWorkbook中创建工作簿Sheet,然后把每个表格数据填充至相应的工作簿Sheet中即可,具体代码如下:

//把表格数组写入Excel文件流
public static MemoryStream Write(DataTable[] dataTables, bool isXlsx, bool isColumnNameAsData)
{//表格数组写入Excel对象using var workbook = CreateWorkbook(dataTables, isXlsx, isColumnNameAsData);var stream = new MemoryStream();workbook.Write(stream, true);stream.Flush();return stream;
}//表格数组转为IWorkbook
private static IWorkbook CreateWorkbook(DataTable[] dataTables, bool isXlsx, bool isColumnNameAsData)
{//根据Excel文件后缀创建IWorkbookvar workbook = CreateWorkbook(isXlsx);foreach (var dt in dataTables){//根据表格填充SheetFillSheetByDataTable(workbook, dt, isColumnNameAsData);}return workbook;
}

而根据表格填充工作簿Sheet实现也非常简单,只需遍历表格中每个单元格,把其值填充至对应工作簿Sheet中相同的位置即可,当然其中表格列名是否要作为数据,需要单独处理,具体代码如下:

//根据表格填充工作簿Sheet
private static void FillSheetByDataTable(IWorkbook workbook, DataTable dataTable, bool isColumnNameAsData)
{var sheet = string.IsNullOrWhiteSpace(dataTable.TableName) ? workbook.CreateSheet() : workbook.CreateSheet(dataTable.TableName);if (isColumnNameAsData){//把列名加入数据第一行var dataRow = sheet.CreateRow(0);foreach (DataColumn column in dataTable.Columns){dataRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);}}//循环处理表格的所有行数据for (var i = 0; i < dataTable.Rows.Count; i++){var dataRow = sheet.CreateRow(i + (isColumnNameAsData ? 1 : 0));for (var j = 0; j < dataTable.Columns.Count; j++){dataRow.CreateCell(j).SetCellValue(dataTable.Rows[i][j].ToString());}}
}

06、把表格数组写入Excel文件

该方法需要注意的是对于Excel文件路径的处理,如果给定的Excel文件路径不存在,则本方法会自动创建相应的文件夹,如果给定的Excel文件路径中不包括文件名称,则本方法会自动根据当前时间+4位随机数的方式+.xlsx的命名方式自动生成文件名。

处理好这些则只需要调用根据表格数组生成Excel对象方法,最后写入Excel文件中,具体代码如下:

//把表格数组写入Excel文件
public static void Write(DataTable[] dataTables, string path, bool isColumnNameAsData)
{//检查文件夹是否存在,不存在则创建var directoryName = Path.GetDirectoryName(path);if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)){Directory.CreateDirectory(directoryName);}//检查是否指定文件名,没有则默认以“时间+随机数.xlsx”作为文件名var fileName = Path.GetFileName(path);if (string.IsNullOrEmpty(fileName)){directoryName = Path.GetFullPath(path);fileName = DateTime.Now.ToString("yyyyMMdd-hhmmss-") + new Random().Next(0000, 9999).ToString("D4") + ".xlsx";path = Path.Combine(directoryName, fileName);}//表格数组写入Excel对象using var workbook = CreateWorkbook(dataTables, IsXlsxFile(path), isColumnNameAsData);using var fs = new FileStream(path, FileMode.Create, FileAccess.Write);workbook.Write(fs, true);
}

下面我们对上面两个写入方法进行详细的单元测试,具体如下:

[Fact]
public void Write_Table()
{var table = TableHelper.Create<Student>();var row1 = table.NewRow();row1[0] = "Id-11";row1[1] = "名称-12";row1[2] = new DateTime(2024, 11, 28);table.Rows.Add(row1);var row2 = table.NewRow();row2[0] = "Id-21";row2[1] = "名称-22";row2[2] = new DateTime(2024, 11, 29);table.Rows.Add(row2);var message = "The column name of the table cannot be mapped to an object property, and the conversion cannot be completed.";//把表格写入Excel,并且列名不作为数据行,结果重新读取Excel无法和对象完成转换ExcelHelper.Write([table], "Write.xls", false);var exception1 = Assert.Throws<NotSupportedException>(() => ExcelHelper.Read<Student>("Write.xls", true, "Sheet0"));Assert.Equal(message, exception1.Message);//把表格写入Excel,并且列名作为数据行,但是重新读取Excel时第一行没有作为列名,结果还是无法和对象完成转换ExcelHelper.Write([table], "Write.xls", true);var exception2 = Assert.Throws<NotSupportedException>(() => ExcelHelper.Read<Student>("Write.xls", false, "Sheet0"));Assert.Equal(message, exception2.Message);//重新读取Excel时第一行作为列名var models = ExcelHelper.Read<Student>("Write.xls", true, "Sheet0");Assert.Equal(2, models.Count());var model = models.First();Assert.Equal("Id-11", model.A);Assert.Equal("名称-12", model.Name);Assert.Equal(new DateTime(2024, 11, 28), model.Age);File.Delete("Write.xls");
}

07、把对象集合写入Excel文件流或Excel文件

到这里这两个方法就很好实现了,因为这两个方法需要的所有基础方法都已经实现,核心思路就是先把对象集合转换为表格DataTable,然后再通过调用相关把表格数组写入Excel的扩展方法实现即可,具体代码如下:

//把对象集合写入Excel文件流
public static MemoryStream Write<T>(IEnumerable<T> models, bool isXlsx, bool isColumnNameAsData, string? sheetName = null)
{//对象集合转为表格var table = TableHelper.ToDataTable<T>(models, sheetName);//表格数组写入Excel文件流return Write([table], isXlsx, isColumnNameAsData);
}//把对象集合写入Excel文件
public static void Write<T>(IEnumerable<T> models, string path, bool isColumnNameAsData, string? sheetName = null)
{//对象集合转为表格var table = TableHelper.ToDataTable<T>(models, sheetName);//表格数组写入Excel文件Write([table], path, isColumnNameAsData);
}

最后我们再进行一次详细的单元测试,代码如下:

[Fact]
public void Write_T()
{//验证正常情况var students = new List<Student>();var student1 = new Student{A = "Id-11",Name = "名称-12",Age = new DateTime(2024, 11, 28)};students.Add(student1);var student2 = new Student{A = "Id-21",Name = "名称-22",Age = new DateTime(2024, 11, 29)};students.Add(student2);var message = "The column name of the table cannot be mapped to an object property, and the conversion cannot be completed.";//把对象集合写入Excel,并且列名不作为数据行,结果重新读取Excel无法和对象完成转换ExcelHelper.Write<Student>(students, "Write_T.xls", false);var exception1 = Assert.Throws<NotSupportedException>(() => ExcelHelper.Read<Student>("Write_T.xls", true, "Sheet0"));Assert.Equal(message, exception1.Message);//把对象集合写入Excel,并且列名作为数据行,但是重新读取Excel时第一行没有作为列名,结果还是无法和对象完成转换ExcelHelper.Write<Student>(students, "Write_T.xls", true);var exception2 = Assert.Throws<NotSupportedException>(() => ExcelHelper.Read<Student>("Write_T.xls", false, "Sheet0"));Assert.Equal(message, exception2.Message);//重新读取Excel时第一行作为列名var models = ExcelHelper.Read<Student>("Write_T.xls", true, "Sheet0");Assert.Equal(2, models.Count());var model = models.First();Assert.Equal("Id-11", model.A);Assert.Equal("名称-12", model.Name);Assert.Equal(new DateTime(2024, 11, 28), model.Age);File.Delete("Write_T.xls");
}

到这里我们整个Excel封装就完成了,相信通过对象集合完成Excel导入导出能满足大多数业务开发需求。当然如果有更复杂的业务需求,还需要我们自己去研究相应的第三方库。

*:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Ideal

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35263.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

CCF-GESP 编程能力认证 C++ 七级 2024年9月份选择题详细解析

第 1 题 已知小写字母 b 的 ASCII 码为 98 &#xff0c;下列 C 代码的输出结果是&#xff08;B&#xff09;。 #include <iostream> using namespace std; int main() {char a b;a;cout << a;return 0; } A. b B. c C. 98 D. 99 【这题很简单&#xff0c;我们只…

Oceanbase离线集群部署

准备工作 两台服务器 服务器的配置参照官网要求来 服务器名配置服务器IPoceanbase116g8h192.168.10.239oceanbase216g8h192.168.10.239 这里选oceanbase1作为 obd机器 oceanbase安装包 选择社区版本的时候自己系统的安装包 ntp时间同步rpm包 联网机器下载所需的软件包 …

动手学深度学习d2l包M4芯片 gpu加速

conda创建环境 CONDA_SUBDIRosx-arm64 conda create -n ml python3.9 -c conda-forge conda env config vars set CONDA_SUBDIRosx-arm64 conda activate mlpip安装包 pip install --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/n…

Y20030046 Java+JSP+SpringBoot+MYSQL+LW+实验室管理系统的设计与实现 源码

实验室管理系统 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 摘 要 伴随互联网的快速发展&#xff0c;国家对当前教育行业实行了新的改革&#xff0c;科学教育要培养更多的技术型人才&#xff0c;2020年全球爆发的新冠疫情&#xff0c;更是推动了科…

AG32 MCU与STM32 等MCU有哪些不同

STM32 MCU的特点 STM32一般是M0,M3, M4等内核的ARM Cortex内核的MCU&#xff0c;仅仅作为MCU使用&#xff0c;没有内置CPLD/FPGA的功能。 2. AG32的特点 AG32是AGM近2年来推出的差异化设计的SOC。以下我们了解以下AG32的特点。 &#xff08;1&#xff09;. 芯片结构&#xff…

【vscode】如何在项目中分享插件?

文章目录 前言一、如何获取插件名称呢&#xff1f;二、项目应用 前言 分享插件&#xff0c;除了将插件名字告诉你的小伙伴&#xff0c;当然还有其他的办法 项目根目录下创建.vscode 文件夹添加extensions.json文件 如下图 一、如何获取插件名称呢&#xff1f; 二、项目应用 …

【css】基础(一)

本专栏内容为&#xff1a;前端专栏 记录学习前端&#xff0c;分为若干个子专栏&#xff0c;html js css vue等 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;css专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &a…

Qt学习笔记第51到60讲

第51讲 记事本实现打开功能 回到第24个功能文件Notepad&#xff0c;给UI中的各个控件添加槽函数。 ①开始按钮 void Widget::on_btnOpen_clicked() {QString fileNameQFileDialog::getOpenFileName(this,tr("Open File"),"E:\\6_Qt Projects\\24_Notepad\\fi…

WPS解决Word文件引入excel对象文件无法打开提示“不能启动此对象...”的问题

一、问题现象 接收到了一份 Word文件&#xff0c;里面引入了一个Excel对象文件&#xff0c;双击时候&#xff0c;wps出现卡顿&#xff0c;过一会之后弹出错误提示&#xff1a;不能启动此对象... 二、解决方法 1.点击WPS左上角图标&#xff0c;并打开右上角设置&#xff0c;萱蕚…

# issue 8 TCP内部原理和UDP编程

TCP 通信三大步骤&#xff1a; 1 三次握手建立连接; 2 开始通信&#xff0c;进行数据交换; 3 四次挥手断开连接&#xff1b; 一、TCP内部原理--三次握手 【第一次握手】套接字A∶"你好&#xff0c;套接字B。我这儿有数据要传给你&#xff0c;建立连接吧。" 【第二次…

什么是人工智能?(Chapter 2)

Chapter 2&#xff1a; 大家到底在争论什么&#xff1f; 2022 年末&#xff0c;OpenAI 发布 ChatGPT 后不久&#xff0c;网上开始流传一个新的备忘录&#xff0c;它比其他任何东西都更能捕捉到这项技术的诡异之处。在大多数版本中&#xff0c;一个名为 "食人魔"&am…

OpenCV-平滑图像

二维卷积(图像滤波) 与一维信号一样&#xff0c;图像也可以通过各种低通滤波器&#xff08;LPF&#xff09;、高通滤波器&#xff08;HPF&#xff09;等进行过滤。LPF 有助于消除噪音、模糊图像等。HPF 滤波器有助于在图像中找到边缘。 opencv 提供了函数 **cv.filter2D()**&…

如何使用 Docker Compose 安装 Memos 自托管笔记应用

简介 Memos是一个自托管的开源笔记应用程序&#xff0c;专为个人组织和信息管理而设计。它允许用户高效地创建、存储和管理笔记&#xff0c;提供如Markdown支持、用户友好的界面和注重隐私的设计等功能。Memos可以在各种平台上运行&#xff0c;但使用Docker Compose可以简化部…

西部地区生活物资保供与城郊大仓基地高质量建设运营论坛西安市成功举办

2024 年 12 月 5日&#xff0c;由中国商业联合会、陕西省商务厅指导&#xff0c;中国商业联合会商贸物流与供应链分会、西安市发改委、西安市商务局主办&#xff0c;中企盟&#xff08;北京&#xff09;电商物流技术研究院、西安商业联合会、陕西省商贸物流供应链协会承办的“西…

[笔记] Windows 上 Git 安装详细教程:从零开始,附带每个选项解析

Git 是目前最流行的分布式版本控制系统之一&#xff0c;广泛应用于软件开发和项目管理中。对于 Windows 用户来说&#xff0c;正确安装和配置 Git 是开始使用 Git 的第一步。本文提供一份详细的指南&#xff0c;帮助你在 Windows 系统上顺利安装 Git&#xff0c;并解释每个安装…

五天SpringCloud计划——DAY3之服务治理(Nacos+OpenFeign+OKHttp)

一、引言 在微服务架构中&#xff0c;一个项目通常会被分为多个模块来降低耦合&#xff0c;但是通常情况下&#xff0c;一个项目中总会出现一种情况——一个模块内的方法需要调用另一个模块内的方法。本文就来使用NacosOpenFeignOKHttp帮助大家解决这个问题。 二、Nacos的使用…

go-blueprint create exit status 1

1. 异常信息 2024/12/06 10:59:19 Could not initialize go.mod in new project exit status 1 2024/12/06 10:59:19 Problem creating files for project. exit status 1 Error: exit status 12. 排查思路 手动进行go mod init查看手动的报错解决报错 3. 解决问题 发现是GO11…

Socket编程-tcp

1. 前言 在tcp套接字编程这里&#xff0c;我们将完成两份代码&#xff0c;一份是基于tcp实现普通的对话&#xff0c;另一份加上业务&#xff0c;client输入要执行的命令&#xff0c;server将执行结果返回给client 2. tcp_echo_server 与udp类似&#xff0c;前两步&#xff1…

Python使用Selenium自动实现表单填写之蛇年纪念币蛇钞预约(附源码,源码有注释解析,已测试可用

Python实现纪念币预约自动填写表单 声明:本文只做技术交流,不可用代码为商业用途,文末有源码下载,已测试可用。 Part 1 配置文件改写(源码 有详细的注释说明 读取配置文件,自己组数据库,录入信息 配置文件 Part 2 主函数 每一期的xpath路径都不一样 所以需要提前去网站…

【计算机网络】期末速成(2)

部分内容来源于网络&#xff0c;侵删~ 第五章 传输层 概述 传输层提供进程和进程之间的逻辑通信&#xff0c;靠**套接字Socket(主机IP地址&#xff0c;端口号)**找到应用进程。 传输层会对收到的报文进行差错检测。 比特流(物理层)-> 数据帧(数据链路层) -> 分组 / I…