开源 - 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优先运用sheetName
dataSet = 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文件流后,调用具体完结文件流处理重载办法,具体代码如下:
```csharp
//依据文件途径读取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至DataSet
var 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文件后缀创立IWorkbook
var workbook = CreateWorkbook(isXlsx);
foreach (var dt in dataTables)
{
//依据表格填充Sheet
FillSheetByDataTable(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