小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

開(kāi)源 - Ideal庫(kù) - Excel幫助類(lèi),ExcelHelper實(shí)現(xiàn)(五)

 IT規(guī)劃師 2024-12-06

書(shū)接上回,我們繼續(xù)來(lái)聊聊ExcelHelper的具體實(shí)現(xiàn)。

01、讀取Excel到DataSet單元測(cè)試

在上一章我們主要講解了讀取Excel到DataSet的三個(gè)重載方法具體實(shí)現(xiàn),還沒(méi)來(lái)得及做單元測(cè)試,因此我們首先對(duì)這三個(gè)方法做個(gè)單元測(cè)試。具體代碼如下:

[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]);
 
    //讀取所有工作簿,并且首行數(shù)據(jù)作為表頭
    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]);
 
    //根據(jù)工作簿名稱(chēng)sheetName讀取指定工作簿
    dataSet = ExcelHelper.Read("Read.xlsx", true, "Sheet2");
    Assert.Single(dataSet.Tables);
    Assert.Equal("Sheet2", dataSet.Tables[0].TableName);
 
    //通過(guò)工作簿名稱(chēng)sheetName讀取不存在的工作簿
    dataSet = ExcelHelper.Read("Read.xlsx", true, "Sheet99");
    Assert.Empty(dataSet.Tables);
 
    //同時(shí)指定sheetName和sheetNumber優(yōu)先使用sheetName
    dataSet = ExcelHelper.Read("Read.xlsx", true, "Sheet1", 2);
    Assert.Single(dataSet.Tables);
    Assert.Equal("Sheet1", dataSet.Tables[0].TableName);
 
    //通過(guò)工作簿編號(hào)sheetNumber讀取不存在的工作簿
    dataSet = ExcelHelper.Read("Read.xlsx", true, null, 99);
    Assert.Empty(dataSet.Tables);
 
    //通過(guò)工作簿編號(hào)sheetNumber讀取指定工作簿
    dataSet = ExcelHelper.Read("Read.xlsx", true, null, 1);
    Assert.Single(dataSet.Tables);
    Assert.Equal("Sheet1", dataSet.Tables[0].TableName);
}

02、根據(jù)文件路徑讀取Excel到對(duì)象集合

在上一章中我們實(shí)現(xiàn)了Excel與DataSet相互轉(zhuǎn)換,而在前面TableHelper實(shí)現(xiàn)章節(jié)中我們已經(jīng)實(shí)現(xiàn)了對(duì)象集合與表格DataTable的相互轉(zhuǎn)換,因此我們只要把這兩者結(jié)合起來(lái)就可以實(shí)現(xiàn)Excel與對(duì)象集合的相互轉(zhuǎn)換。

因?yàn)镋xcel中有多個(gè)工作簿Sheet,而每一個(gè)工作簿Sheet代表一個(gè)表格DataTable,一個(gè)表格DataTable關(guān)聯(lián)一個(gè)對(duì)象集合,因此我們約定本方法必須指定一個(gè)工作簿Sheet用來(lái)轉(zhuǎn)換對(duì)象集合,如果沒(méi)有指定則默認(rèn)讀取第一個(gè)工作簿Sheet。

而該方法通過(guò)文件完全路徑讀取到Excel文件流后,調(diào)用具體實(shí)現(xiàn)文件流處理重載方法,具體代碼如下:

//根據(jù)文件路徑讀取Excel到對(duì)象集合
//指定sheetName,sheetNumber則讀取相應(yīng)工作簿Sheet
//如果不指定則默認(rèn)讀取第一個(gè)工作簿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、根據(jù)文件流、文件名讀取Excel到對(duì)象集合

在有些場(chǎng)景下,我們直接得到的就是Excel文件流,因此更通用的處理方式就是處理ExceL文件流,因?yàn)闊o(wú)論如何最終我們都是要拿到Excel文件流的。

該方法也是一個(gè)重載方法,為了方便哪些上傳文件后,有文件流,有文件名,但是不想自己處理文件后綴格式的,提供一個(gè)便捷方法,因此該方法會(huì)通過(guò)文件名識(shí)別出文件具體后綴格式,再調(diào)用下一個(gè)重載方法,具體實(shí)現(xiàn)如下:

//根據(jù)文件流讀取Excel到對(duì)象集合
//指定sheetName,sheetNumber則讀取相應(yīng)工作簿Sheet
//如果不指定則默認(rèn)讀取第一個(gè)工作簿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、根據(jù)文件流、文件后綴讀取Excel到對(duì)象集合

該方法是上面兩個(gè)方法的最終實(shí)現(xiàn),具體實(shí)現(xiàn)分為兩步:

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

(2)把DataSet中第一個(gè)表格DataTable轉(zhuǎn)換為對(duì)象集合;

而這兩步都是調(diào)用之前實(shí)現(xiàn)好的方法,具體代碼如下:

//根據(jù)文件流讀取Excel到對(duì)象集合
//指定sheetName,sheetNumber則讀取相應(yīng)工作簿Sheet
//如果不指定則默認(rèn)讀取第一個(gè)工作簿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轉(zhuǎn)對(duì)象集合
    return TableHelper.ToModels<T>(dataSet.Tables[0]);
}

下面我們針對(duì)上面三個(gè)方法做個(gè)簡(jiǎn)單的單元測(cè)試,代碼如下:

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()
{
    //表格數(shù)據(jù)格式無(wú)法轉(zhuǎn)為對(duì)象數(shù)據(jù)類(lèi)型,則拋異常
    Assert.Throws<FormatException>(() => ExcelHelper.Read<Student>("Read.xlsx", true, "Sheet1"));
 
    //表格成功轉(zhuǎn)為對(duì)象集合
    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、把表格數(shù)組寫(xiě)入Excel文件流

該方法是先把表格數(shù)組生成Excel的IWorkbook,然后再寫(xiě)入內(nèi)存流MemoryStream。

而表格數(shù)組轉(zhuǎn)換為IWorkbook也很簡(jiǎn)單,在IWorkbook中創(chuàng)建工作簿Sheet,然后把每個(gè)表格數(shù)據(jù)填充至相應(yīng)的工作簿Sheet中即可,具體代碼如下:

//把表格數(shù)組寫(xiě)入Excel文件流
public static MemoryStream Write(DataTable[] dataTables, bool isXlsx, bool isColumnNameAsData)
{
    //表格數(shù)組寫(xiě)入Excel對(duì)象
    using var workbook = CreateWorkbook(dataTables, isXlsx, isColumnNameAsData);
    var stream = new MemoryStream();
    workbook.Write(stream, true);
    stream.Flush();
    return stream;
}
 
//表格數(shù)組轉(zhuǎn)為IWorkbook
private static IWorkbook CreateWorkbook(DataTable[] dataTables, bool isXlsx, bool isColumnNameAsData)
{
    //根據(jù)Excel文件后綴創(chuàng)建IWorkbook
    var workbook = CreateWorkbook(isXlsx);
    foreach (var dt in dataTables)
    {
        //根據(jù)表格填充Sheet
        FillSheetByDataTable(workbook, dt, isColumnNameAsData);
    }
 
    return workbook;
}

而根據(jù)表格填充工作簿Sheet實(shí)現(xiàn)也非常簡(jiǎn)單,只需遍歷表格中每個(gè)單元格,把其值填充至對(duì)應(yīng)工作簿Sheet中相同的位置即可,當(dāng)然其中表格列名是否要作為數(shù)據(jù),需要單獨(dú)處理,具體代碼如下:

//根據(jù)表格填充工作簿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)
    {
        //把列名加入數(shù)據(jù)第一行
        var dataRow = sheet.CreateRow(0);
        foreach (DataColumn column in dataTable.Columns)
        {
            dataRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
        }
    }
 
    //循環(huán)處理表格的所有行數(shù)據(jù)
    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、把表格數(shù)組寫(xiě)入Excel文件

該方法需要注意的是對(duì)于Excel文件路徑的處理,如果給定的Excel文件路徑不存在,則本方法會(huì)自動(dòng)創(chuàng)建相應(yīng)的文件夾,如果給定的Excel文件路徑中不包括文件名稱(chēng),則本方法會(huì)自動(dòng)根據(jù)當(dāng)前時(shí)間+4位隨機(jī)數(shù)的方式+.xlsx的命名方式自動(dòng)生成文件名。

處理好這些則只需要調(diào)用根據(jù)表格數(shù)組生成Excel對(duì)象方法,最后寫(xiě)入Excel文件中,具體代碼如下:

//把表格數(shù)組寫(xiě)入Excel文件
public static void Write(DataTable[] dataTables, string path, bool isColumnNameAsData)
{
    //檢查文件夾是否存在,不存在則創(chuàng)建
    var directoryName = Path.GetDirectoryName(path);
    if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
    {
        Directory.CreateDirectory(directoryName);
    }
 
    //檢查是否指定文件名,沒(méi)有則默認(rèn)以“時(shí)間+隨機(jī)數(shù).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);
    }
 
    //表格數(shù)組寫(xiě)入Excel對(duì)象
    using var workbook = CreateWorkbook(dataTables, IsXlsxFile(path), isColumnNameAsData);
    using var fs = new FileStream(path, FileMode.Create, FileAccess.Write);
    workbook.Write(fs, true);
}

下面我們對(duì)上面兩個(gè)寫(xiě)入方法進(jìn)行詳細(xì)的單元測(cè)試,具體如下:

[Fact]
public void Write_Table()
{
    var table = TableHelper.Create<Student>();
    var row1 = table.NewRow();
    row1[0] = "Id-11";
    row1[1] = "名稱(chēng)-12";
    row1[2] = new DateTime(2024, 11, 28);
    table.Rows.Add(row1);
    var row2 = table.NewRow();
    row2[0] = "Id-21";
    row2[1] = "名稱(chēng)-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.";
 
    //把表格寫(xiě)入Excel,并且列名不作為數(shù)據(jù)行,結(jié)果重新讀取Excel無(wú)法和對(duì)象完成轉(zhuǎn)換
    ExcelHelper.Write([table], "Write.xls", false);
    var exception1 = Assert.Throws<NotSupportedException>(() => ExcelHelper.Read<Student>("Write.xls", true, "Sheet0"));
    Assert.Equal(message, exception1.Message);
 
    //把表格寫(xiě)入Excel,并且列名作為數(shù)據(jù)行,但是重新讀取Excel時(shí)第一行沒(méi)有作為列名,結(jié)果還是無(wú)法和對(duì)象完成轉(zhuǎn)換
    ExcelHelper.Write([table], "Write.xls", true);
    var exception2 = Assert.Throws<NotSupportedException>(() => ExcelHelper.Read<Student>("Write.xls", false, "Sheet0"));
    Assert.Equal(message, exception2.Message);
 
    //重新讀取Excel時(shí)第一行作為列名
    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("名稱(chēng)-12", model.Name);
    Assert.Equal(new DateTime(2024, 11, 28), model.Age);
 
    File.Delete("Write.xls");
}

07、把對(duì)象集合寫(xiě)入Excel文件流或Excel文件

到這里這兩個(gè)方法就很好實(shí)現(xiàn)了,因?yàn)檫@兩個(gè)方法需要的所有基礎(chǔ)方法都已經(jīng)實(shí)現(xiàn),核心思路就是先把對(duì)象集合轉(zhuǎn)換為表格DataTable,然后再通過(guò)調(diào)用相關(guān)把表格數(shù)組寫(xiě)入Excel的擴(kuò)展方法實(shí)現(xiàn)即可,具體代碼如下:

//把對(duì)象集合寫(xiě)入Excel文件流
public static MemoryStream Write<T>(IEnumerable<T> models, bool isXlsx, bool isColumnNameAsData, string? sheetName = null)
{
    //對(duì)象集合轉(zhuǎn)為表格
    var table = TableHelper.ToDataTable<T>(models, sheetName);
    //表格數(shù)組寫(xiě)入Excel文件流
    return Write([table], isXlsx, isColumnNameAsData);
}
 
//把對(duì)象集合寫(xiě)入Excel文件
public static void Write<T>(IEnumerable<T> models, string path, bool isColumnNameAsData, string? sheetName = null)
{
    //對(duì)象集合轉(zhuǎn)為表格
    var table = TableHelper.ToDataTable<T>(models, sheetName);
    //表格數(shù)組寫(xiě)入Excel文件
    Write([table], path, isColumnNameAsData);
}

最后我們?cè)龠M(jìn)行一次詳細(xì)的單元測(cè)試,代碼如下:

[Fact]
public void Write_T()
{
    //驗(yàn)證正常情況
    var students = new List<Student>();
    var student1 = new Student
    {
        A = "Id-11",
        Name = "名稱(chēng)-12",
        Age = new DateTime(2024, 11, 28)
    };
    students.Add(student1);
    var student2 = new Student
    {
        A = "Id-21",
        Name = "名稱(chēng)-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.";
 
    //把對(duì)象集合寫(xiě)入Excel,并且列名不作為數(shù)據(jù)行,結(jié)果重新讀取Excel無(wú)法和對(duì)象完成轉(zhuǎn)換
    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);
 
    //把對(duì)象集合寫(xiě)入Excel,并且列名作為數(shù)據(jù)行,但是重新讀取Excel時(shí)第一行沒(méi)有作為列名,結(jié)果還是無(wú)法和對(duì)象完成轉(zhuǎn)換
    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時(shí)第一行作為列名
    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("名稱(chēng)-12", model.Name);
    Assert.Equal(new DateTime(2024, 11, 28), model.Age);
 
    File.Delete("Write_T.xls");
}

到這里我們整個(gè)Excel封裝就完成了,相信通過(guò)對(duì)象集合完成Excel導(dǎo)入導(dǎo)出能滿足大多數(shù)業(yè)務(wù)開(kāi)發(fā)需求。當(dāng)然如果有更復(fù)雜的業(yè)務(wù)需求,還需要我們自己去研究相應(yīng)的第三方庫(kù)。

注:測(cè)試方法代碼以及示例源碼都已經(jīng)上傳至代碼庫(kù),有興趣的可以看看。https:///hugogoos/Ideal

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多