- A+
WeihanLi.Npoi 1.11.0/1.12.0 Release Notes
Intro
最近 NPOI 扩展新更新了两个版本,感谢 shaka chow
的帮忙和支持,这两个 Feature 都是来自于他,并且帮我测试了几个版本,还帮我提供了一个更好用的读文件的方式,十分感谢。
最近更新的两个功能都是 Excel 导入方面的,1.11.0 版本支持公式导入处理,1.12.0 支持通过 Attribute 配置允许读取的列范围, 也可以通过 Fluent API 的方式设置 CellFilter
来更灵活的过滤不想读取的列。
公式导入的支持
在之前的版本中是没有公式支持的,导入一个公式的单元格会是一个字符串去读取,某些场景下可能用 Excel 会很有用,于是尝试添加了公式的支持,仅是导入,导出的时候没有支持公式,一方面是因为根据公式去计算导出的值的时候可能会需要先把所有值先填完整再计算公式的值,这样的场景会导致效率很低,另外一方面我觉得 Fluent API 的方式已经可以满足大多数场景的需要,不是特别需要,所以没有支持公式的导出。
Row/Cell Filter 支持
在 1.10.0 版本中,我们支持了一个 EndRowIndex
来配置一个结束行,用以提前结束读取数据,在 1.12.0 版本中 shaka
提出了可以增加 StartColumnIndex
以及 EndColumnIndex
配置来指定要读取的列范围,在此基础上想出了基于 Fluent API 给 Sheet 增加 RowFilter
和 CellFilter
来更灵活的配置自己想要读取的数据范围,借助于此我们可以轻松实现隔行读取或多个范围读取等。
Show the Code
Excel 公式导入支持的示例:
[Theory] [InlineData(ExcelFormat.Xls)] [InlineData(ExcelFormat.Xlsx)] public void ExcelImportWithFormula(ExcelFormat excelFormat) { var setting = FluentSettings.For<ExcelFormulaTestModel>(); setting.HasSheetConfiguration(0, "Test", 0); setting.Property(x => x.Num1).HasColumnIndex(0); setting.Property(x => x.Num2).HasColumnIndex(1); setting.Property(x => x.Sum).HasColumnIndex(2); var workbook = ExcelHelper.PrepareWorkbook(excelFormat); var sheet = workbook.CreateSheet(); var row = sheet.CreateRow(0); row.CreateCell(0, CellType.Numeric).SetCellValue(1); row.CreateCell(1, CellType.Numeric).SetCellValue(2); row.CreateCell(2, CellType.Formula).SetCellFormula("$A1+$B1"); var excelBytes = workbook.ToExcelBytes(); var list = ExcelHelper.ToEntityList<ExcelFormulaTestModel>(excelBytes, excelFormat); Assert.NotNull(list); Assert.NotEmpty(list); Assert.Equal(1, list[0].Num1); Assert.Equal(2, list[0].Num2); Assert.Equal(3, list[0].Sum); }
公式的支持不需要修改任何代码,和原来的 API 是完全兼容的,可以看到上面公式的导入的值成功被替换成了计算后的值
Cell Filter 使用 Attribute 方式示例
[Theory] [InlineData(ExcelFormat.Xls)] [InlineData(ExcelFormat.Xlsx)] public void ExcelImportWithCellFilterAttributeTest(ExcelFormat excelFormat) { IReadOnlyList<CellFilterAttributeTest> list = Enumerable.Range(0, 10).Select(i => new CellFilterAttributeTest() { Id = i + 1, Description = $"content_{i}", Name = $"title_{i}", }).ToArray(); var excelBytes = list.ToExcelBytes(excelFormat); var importedList = ExcelHelper.ToEntityList<CellFilterAttributeTest>(excelBytes, excelFormat); Assert.NotNull(importedList); Assert.Equal(list.Count, importedList.Count); for (var i = 0; i < importedList.Count; i++) { Assert.Equal(list[i].Id, importedList[i].Id); Assert.Equal(list[i].Name, importedList[i].Name); Assert.Null(importedList[i].Description); } } [Sheet(SheetName = "test", AutoColumnWidthEnabled = true, StartColumnIndex = 0, EndColumnIndex = 1)] private class CellFilterAttributeTest { [Column(Index = 0)] public int Id { get; set; } [Column(Index = 1)] public string Name { get; set; } [Column(Index = 2)] public string Description { get; set; } }
可以看到最后一列的值其实是被忽略掉的,最后一列对应的 Description
属性永远是 null
Cell Filter 使用 Fluent API 方式示例
[Theory] [InlineData(ExcelFormat.Xls)] [InlineData(ExcelFormat.Xlsx)] public void ExcelImportWithCellFilter(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 excelBytes = list.ToExcelBytes(excelFormat); var settings = FluentSettings.For<Notice>(); settings.HasSheetSetting(setting => { setting.CellFilter = cell => cell.ColumnIndex == 0; }); var importedList = ExcelHelper.ToEntityList<Notice>(excelBytes, excelFormat); Assert.Equal(list.Count, importedList.Count); for (var i = 0; i < list.Count; i++) { if (list[i] == null) { Assert.Null(importedList[i]); } else { Assert.Equal(list[i].Id, importedList[i].Id); Assert.Null(importedList[i].Title); Assert.Null(importedList[i].Content); Assert.Null(importedList[i].Publisher); Assert.Equal(default(DateTime).ToStandardTimeString(), importedList[i].PublishedAt.ToStandardTimeString()); } } settings.HasSheetSetting(setting => { setting.CellFilter = null; }); }
这个示例比较简单,只导入了第一列的数据 ,其他列数据对应的属性都是默认值
More
除了这两个主要的 Feature 之外,还有几个小更新,重构了 ExcelSetting
和 SheetSetting
,提供了基于委托来配置的方法,原来的方法作为扩展方法来使用,另外就是优化了文件读取,主要是读取文件的时候指定了一个 FileShare
Mode(详细可以参考文末给出的链接),原来如果别的进程已经打开了文件,这时候再导入就会抛出异常,优化之后即使文件被别的进程占用,依然可以读取文件内容进行导入操作,操作体验可能会更好一些。
更多细节可以参考 Github 仓库里的示例和单元测试