WeihanLi.Npoi 1.21.0 Released
Intro
WeihanLi.Npoi
是一个基于 netstandard2.0
的一个 NPOI 扩展库,主要用于导入导出 Excel 以及 CSV,支持通过 Fluent API 的方式来支持非常灵活的导入导出配置,详细使用可以参考文档介绍以及项目示例 https://github.com/WeihanLi/WeihanLi.Npoi
New Features
本次引入的新功能是针对 DataTable
的优化,如果导入的 Excel 出现了重复列,原来会直接抛出一个 System.Data.DuplicateNameException
,主要是因为原来是直接用 excel 列名称作为 DataColumn
的 Name
,而一个 DataTable
中是不允许有名字重复 Column
的,就像数据库同一个表中不允许出现重复列名一样。可以参考 Issue https://github.com/WeihanLi/WeihanLi.Npoi/issues/125
而导入 excel 时,很多时候可能并不根据列名称去读取对应的值,有时候会直接使用列索引来读取列的值,这个场景下,即使 excel 列名冲突了也关系不大,我们只需要按照索引读取就可以了,所以就考虑了支持冲突的读取,因为想再导出的时候 excel 还和之前导入的时候保持一致,所以也增加了导出的时候对 DataTable
的处理,实现效果可以参考单元测试:
// Csv
[Fact]
public void DuplicateColumnTest()
{var csvText = $@"A,B,C,A,B,C{Environment.NewLine}1,2,3,4,5,6";var dataTable = CsvHelper.ToDataTable(csvText.GetBytes());Assert.Equal(6, dataTable.Columns.Count);Assert.Equal(1, dataTable.Rows.Count);var newCsvText = CsvHelper.GetCsvText(dataTable);Assert.StartsWith("A,B,C,A,B,C", newCsvText);var newDataTable = CsvHelper.ToDataTable(newCsvText.GetBytes());Assert.Equal(dataTable.Columns.Count, newDataTable.Columns.Count);Assert.Equal(dataTable.Rows.Count, newDataTable.Rows.Count);
}
// Excel
[Theory]
[ExcelFormatData]
public void DuplicateColumnTest(ExcelFormat excelFormat)
{var workbook = ExcelHelper.PrepareWorkbook(excelFormat);var sheet = workbook.CreateSheet();var headerRow = sheet.CreateRow(0);headerRow.CreateCell(0).SetCellValue("A");headerRow.CreateCell(1).SetCellValue("B");headerRow.CreateCell(2).SetCellValue("C");headerRow.CreateCell(3).SetCellValue("A");headerRow.CreateCell(4).SetCellValue("B");headerRow.CreateCell(5).SetCellValue("C");var dataRow = sheet.CreateRow(1);dataRow.CreateCell(0).SetCellValue("1");dataRow.CreateCell(1).SetCellValue("2");dataRow.CreateCell(2).SetCellValue("3");dataRow.CreateCell(3).SetCellValue("4");dataRow.CreateCell(4).SetCellValue("5");dataRow.CreateCell(5).SetCellValue("6");var dataTable = sheet.ToDataTable();Assert.Equal(headerRow.Cells.Count, dataTable.Columns.Count);Assert.Equal(1, dataTable.Rows.Count);var newWorkbook = ExcelHelper.LoadExcel(dataTable.ToExcelBytes());var newSheet = newWorkbook.GetSheetAt(0);Assert.Equal(sheet.PhysicalNumberOfRows, newSheet.PhysicalNumberOfRows);for (var i = 0; i < sheet.PhysicalNumberOfRows; i++){Assert.Equal(sheet.GetRow(i).Cells.Count, newSheet.GetRow(i).Cells.Count);for (var j = 0; j < headerRow.Cells.Count; j++){Assert.Equal(sheet.GetRow(i).GetCell(j).GetCellValue<string>(),newSheet.GetRow(i).GetCell(j).GetCellValue<string>());}}
}
实现方式上一定程度参考了 issue 给出的建议,导入时重复列会添加一个 duplicate 标识和一个唯一 id 使得名称不会重复,从而不会引发异常,导出时如果是重复列会把 duplicate 标识和唯一 id 去掉从而还原真实的列名称,更多细节可以查看 Github 上的 PR https://github.com/WeihanLi/WeihanLi.Npoi/pull/126
Bug Fixes
修复了 sheet name 配置可能会不生效的 BUG
本次更新修复了在导出成文件的时候 sheet name 的配置没有生效的一个 BUG,详细可以参考 issue: https://github.com/WeihanLi/WeihanLi.Npoi/issues/127
开始并没有重现这个 BUG,因为只有在导出为文件的时候才会有问题,如果是 bytes 或者 stream 是不会有这个问题的,现在已经增加了下面的测试用例来覆盖这个情况
[Theory]
[ExcelFormatData]
public void SheetNameTest_ToExcelFile(ExcelFormat excelFormat)
{IReadOnlyList<Notice> list = Enumerable.Range(0, 10).Select(i => new Notice(){Id = i + 1,Content = $"content_{i}",Title = $"title_{i}",PublishedAt = DateTime.UtcNow.AddDays(-i),Publisher = $"publisher_{i}"}).ToArray();var settings = FluentSettings.For<Notice>();lock (settings){settings.HasSheetSetting(s =>{s.SheetName = "Test";});var filePath = $"{Path.GetTempFileName()}.{excelFormat.ToString().ToLower()}";list.ToExcelFile(filePath);var excel = ExcelHelper.LoadExcel(filePath);Assert.Equal("Test", excel.GetSheetAt(0).SheetName);settings.HasSheetSetting(s =>{s.SheetName = "NoticeList";});}}[Theory]
[ExcelFormatData]
public void SheetNameTest_ToExcelBytes(ExcelFormat excelFormat)
{IReadOnlyList<Notice> list = Enumerable.Range(0, 10).Select(i => new Notice(){Id = i + 1,Content = $"content_{i}",Title = $"title_{i}",PublishedAt = DateTime.UtcNow.AddDays(-i),Publisher = $"publisher_{i}"}).ToArray();var settings = FluentSettings.For<Notice>();lock (settings){settings.HasSheetSetting(s =>{s.SheetName = "Test";});var excelBytes = list.ToExcelBytes(excelFormat);var excel = ExcelHelper.LoadExcel(excelBytes, excelFormat);Assert.Equal("Test", excel.GetSheetAt(0).SheetName);settings.HasSheetSetting(s =>{s.SheetName = "NoticeList";});}
}
修复导出到文件 excel 文件格式不对的 BUG
根据文件路径创建 excel workbook 的时候原来是有 BUG 的可能会导致文件格式不对,原来没有先换取文件扩展名,新版本中修复了这个 bug,会先获取文件扩展名再判断文件格式
- !excelPath.EqualsIgnoreCase(".xls")
+ !Path.GetExtension(excelPath).EqualsIgnoreCase(".xls")
More
这个新版本中还有个针对 CsvHelper
的小优化,主要是获取导出的 CSV 字符串时 includeHeader
参数变成了一个可选参数,对于调用方来说可以调用会变得更简单一些,默认值是 true
,默认会包含 header
public static string GetCsvText(this DataTable? dataTable, bool includeHeader = true);
public static string GetCsvText<TEntity>(this IEnumerable<TEntity> entities, bool includeHeader = true);
更多细节可以参考 PR 变更 https://github.com/WeihanLi/WeihanLi.Npoi/pull/130
References
https://github.com/WeihanLi/WeihanLi.Npoi
https://github.com/WeihanLi/WeihanLi.Npoi/pull/130
https://www.nuget.org/packages/WeihanLi.Npoi/1.21.0
https://github.com/WeihanLi/WeihanLi.Npoi/tree/1.21.0