diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 1ae0d79f..02f35fe4 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -120,4 +120,4 @@ jobs:
git config user.email github-actions@github.com
git add ./*.md
git commit -am "Automated benchmark report - ${{ github.ref_name }}"
- git push origin master --force-with-lease
+ git push origin HEAD:master --force-with-lease
diff --git a/MiniExcel.slnx b/MiniExcel.slnx
index 73698bc3..290ecf86 100644
--- a/MiniExcel.slnx
+++ b/MiniExcel.slnx
@@ -9,7 +9,6 @@
-
diff --git a/README-NuGet.md b/README-NuGet.md
deleted file mode 100644
index 6d9fb9f5..00000000
--- a/README-NuGet.md
+++ /dev/null
@@ -1,101 +0,0 @@
-
-This project is part of the [.NET Foundation](https://dotnetfoundation.org/projects/project-detail/miniexcel) and operates under their code of conduct.
-
----
-
-### Introduction
-
-MiniExcel is simple and efficient to avoid OOM's .NET processing Excel tool.
-
-At present, most popular frameworks need to load all the data into the memory to facilitate operation, but it will cause memory consumption problems. MiniExcel tries to use algorithm from a stream to reduce the original 1000 MB occupation to a few MB to avoid OOM(out of memory).
-
-
-
-
-### Features
-
-- Low memory consumption, avoid OOM (out of memory) and full GC
-- Supports real time operation of each row of data
-- Supports LINQ deferred execution, it can do low-consumption, fast paging and other complex queries
-- Lightweight, without Microsoft Office installed, no COM+, DLL size is less than 400KB
-- Easy API style to read/write/fill excel
-
-### Get Started
-
-- [Import/Query Excel](#getstart1)
-
-- [Export/Create Excel](#getstart2)
-
-- [Excel Template](#getstart3)
-
-- [Excel Column Name/Index/Ignore Attribute](#getstart4)
-
-- [Examples](#getstart5)
-
-
-
-### Installation
-
-You can install the package [from NuGet](https://www.nuget.org/packages/MiniExcel)
-
-### Release Notes
-
-Please Check [Release Notes](docs)
-
-### TODO
-
-Please Check [TODO](https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true)
-
-### Performance
-
-The code for the benchmarks can be found in [MiniExcel.Benchmarks](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/MiniExcel.Benchmarks).
-To run all the benchmarks use:
-
-```bash
-dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
-```
-
-Hardware and settings used are the following:
-```
-BenchmarkDotNet v0.15.0, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
-AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
-.NET SDK 9.0.300
- [Host] : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
- ShortRun : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
-```
-
-#### Import/Query Excel
-
-The file used to test performance is [**Test1,000,000x10.xlsx**](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/MiniExcel.Benchmarks/Test1%2C000%2C000x10.xlsx), a 32MB document containing 1,000,000 rows * 10 columns whose cells are filled with the string "HelloWorld".
-
-| Method | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated |
-|--------------------------------------|-----------------:|---------------:|-----------------:|------------:|------------:|----------:|--------------:|
-| 'MiniExcel QueryFirst' | 63.70 μs | 0.337 μs | 6.144 μs | 2.9297 | 2.7669 | - | 49.67 KB |
-| 'ExcelDataReader QueryFirst' | 5,010,679.51 μs | 53,245.186 μs | 971,390.400 μs | 105000.0000 | 333.3333 | - | 1717272.56 KB |
-| 'MiniExcel Query' | 9,172,286.91 μs | 12,805.326 μs | 233,616.824 μs | 448500.0000 | 4666.6667 | - | 7327883.36 KB |
-| 'ExcelDataReader Query' | 10,609,617.09 μs | 29,055.953 μs | 530,088.745 μs | 275666.6667 | 68666.6667 | - | 4504691.87 KB |
-| 'Epplus QueryFirst' | 13,770,656.24 μs | 45,909.809 μs | 837,565.827 μs | 174333.3333 | 88833.3333 | 4333.3333 | 3700587.76 KB |
-| 'Epplus Query' | 19,257,306.83 μs | 63,117.956 μs | 1,151,506.486 μs | 452333.3333 | 90500.0000 | 5333.3333 | 8223933.16 KB |
-| 'ClosedXml Query' | 31,070,263.83 μs | 342,973.671 μs | 6,257,116.502 μs | 401666.6667 | 104166.6667 | 3333.3333 | 6822559.68 KB |
-| 'ClosedXml QueryFirst' | 31,141,877.48 μs | 21,006.538 μs | 383,237.459 μs | 402166.6667 | 104833.3333 | 3833.3333 | 6738357.8 KB |
-| 'OpenXmlSDK QueryFirst' | 31,750,686.63 μs | 263,328.569 μs | 4,804,093.357 μs | 374666.6667 | 374500.0000 | 3166.6667 | 6069266.96 KB |
-| 'OpenXmlSDK Query' | 32,919,119.46 μs | 411,395.682 μs | 7,505,388.691 μs | 374666.6667 | 374500.0000 | 3166.6667 | 6078467.83 KB |
-
-
-#### Export/Create Excel
-
-Logic: create a total of 10,000,000 "HelloWorld" cells Excel document
-
-| Method | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated |
-|----------------------------------------------|---------:|---------:|---------:|------------:|------------:|----------:|----------:|
-| 'MiniExcel Create Xlsx' | 4.427 s | 0.0056 s | 0.1023 s | 251666.6667 | 1833.3333 | 1666.6667 | 3.92 GB |
-| 'OpenXmlSdk Create Xlsx by DOM mode' | 22.729 s | 0.1226 s | 2.2374 s | 307000.0000 | 306833.3333 | 3833.3333 | 6.22 GB |
-| 'ClosedXml Create Xlsx' | 22.851 s | 0.0190 s | 0.3473 s | 195500.0000 | 54500.0000 | 4166.6667 | 4.48 GB |
-| 'Epplus Create Xlsx' | 23.027 s | 0.0088 s | 0.1596 s | 89000.0000 | 17500.0000 | 6000.0000 | 2.51 GB |
-
-Warning: these results may be outdated. You can find the benchmarks for the latest release [here](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/results).
-
-
-### Documents
-
-https://github.com/mini-software/MiniExcel
diff --git a/README-V2.md b/README-V2.md
index 3f77c5af..ccec8fcc 100644
--- a/README-V2.md
+++ b/README-V2.md
@@ -107,12 +107,12 @@ You can download the full package from [NuGet](https://www.nuget.org/packages/Mi
dotnet add package MiniExcel
```
-This package will contain the assemblies with both Excel and Csv functionalities, along with the original `v1.x` methods' signatures.
-~~If you don't care for those you can also install the Excel and Csv packages separately:~~
-We're still pondering whether this is the best way to move forward with the library, and if we do this is how you'll be able to add the separate packages:
+This package will contain the assemblies with both Excel and Csv functionalities,
+along with the `MiniExcelConverter` utility class and the original `v1.x` methods' signatures.
+If you don't care for those you can also install the OpenXml and Csv packages separately:
```bash
-dotnet add package MiniExcel.Core
+dotnet add package MiniExcel.OpenXml
```
```bash
diff --git a/V2-Upgrade-Notes.md b/V2-Upgrade-Notes.md
index 7135c8a1..3056388c 100644
--- a/V2-Upgrade-Notes.md
+++ b/V2-Upgrade-Notes.md
@@ -6,7 +6,7 @@
`MiniExcel.Importers`, `MiniExcel.Exporters` and `MiniExcel.Templaters` will give you access to, respectively, the `MiniExcelImporterProvider`, `MiniExcelExporterProvider` and `MiniExcelTemplaterProvider`.
- This way Excel and Csv query methods are split between the `OpenXmlImporter` and the `CsvImporter`, accessible from the `MiniExcelImporterProvider`.
- The same structure was adopted for export methods through `OpenXmlExporter` and `CsvExporter`, while template methods are instead currently only found in `OpenXmlTemplater`.
-- Csv methods are only available if the MiniExcel.Csv package is installed, which is pulled down automatically when the full MiniExcel package is downloaded.
+- OpenXml and Csv methods are only available if the respective `MiniExcel.OpenXml` and `MiniExcel.Csv` packages are downloaded, or if the complete `MiniExcel` package is installed.
- You can only access the conversion methods `ConvertCsvToXlsx` and `ConvertXlsxToCsv` from the `MiniExcelConverter` utility class, which is part of the full MiniExcel package.
- If the full MiniExcel package is downloaded, the previous namespace will coexist along the new one, containing the original static methods' signatures, which have become a facade for the aferomentioned providers.
- `IConfiguration` is now `IMiniExcelConfiguration`, but most methods now require the proper implementation (`OpenXmlConfiguration` or `CsvConfiguration`) to be provided rather than the interface
diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs
index bbd60a07..943ca502 100644
--- a/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs
+++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs
@@ -2,10 +2,10 @@
public abstract class BenchmarkBase
{
- public const string FilePath = "Test1,000,000x10.xlsx";
- public const int RowCount = 1_000_000;
+ protected const string FilePath = "Test100,000x10.xlsx";
+ protected const int RowCount = 100_000;
- public IEnumerable GetValue() => Enumerable.Range(1, RowCount).Select(s => new DemoDto());
+ protected IEnumerable GetValue() => Enumerable.Range(1, RowCount).Select(_ => new DemoDto());
public class DemoDto
{
diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs
index 531d6a64..83218068 100644
--- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs
+++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs
@@ -53,7 +53,10 @@ public void MiniExcel_QueryFirst_Test()
[Benchmark(Description = "MiniExcel Query")]
public void MiniExcel_Query()
{
- foreach (var _ in _importer.Query(FilePath)) { }
+ foreach (var row in _importer.Query(FilePath))
+ {
+ var value = row;
+ }
}
[Benchmark(Description = "MiniExcel QueryFirst with Mapping")]
@@ -65,7 +68,10 @@ public void MiniExcel_QueryFirst_Mapping_Test()
[Benchmark(Description = "MiniExcel Query with Mapping")]
public void MiniExcel_Query_Mapping()
{
- foreach (var _ in _mappingImporter.Query(FilePath)) { }
+ foreach (var row in _mappingImporter.Query(FilePath))
+ {
+ var value = row;
+ }
}
[Benchmark(Description = "ExcelDataReader QueryFirst")]
@@ -74,11 +80,11 @@ public void ExcelDataReader_QueryFirst_Test()
using var stream = File.Open(FilePath, FileMode.Open, FileAccess.Read);
using var reader = ExcelReaderFactory.CreateReader(stream);
- List d = [];
reader.Read();
-
for (var i = 0; i < reader.FieldCount; i++)
- d.Add(reader.GetValue(i));
+ {
+ var value = reader.GetValue(i);
+ }
}
[Benchmark(Description = "ExcelDataReader Query")]
@@ -89,9 +95,10 @@ public void ExcelDataReader_Query_Test()
while (reader.Read())
{
- List d = [];
for (var i = 0; i < reader.FieldCount; i++)
- d.Add(reader.GetValue(i));
+ {
+ var value = reader.GetValue(i);
+ }
}
}
@@ -105,8 +112,6 @@ public void Epplus_QueryFirst_Test()
[Benchmark(Description = "Epplus Query")]
public void Epplus_Query_Test()
{
- // [How do I iterate through rows in an excel table using epplus? - Stack Overflow] (https://stackoverflow.com/questions/21742038/how-do-i-iterate-through-rows-in-an-excel-table-using-epplus)
-
using var p = new ExcelPackage(new FileInfo(FilePath));
var workSheet = p.Workbook.Worksheets[0];
@@ -135,12 +140,14 @@ public void ClosedXml_Query_Test()
using var workbook = new XLWorkbook(FilePath);
workbook.Worksheet(1).Rows();
}
+
[Benchmark(Description = "NPOI QueryFirst")]
public void NPOI_QueryFirst_Test()
{
using var wb = new XSSFWorkbook(FilePath,true);
wb.GetSheetAt(0).GetRow(1);
}
+
[Benchmark(Description = "NPOI Query")]
public void NPOI_Query_Test()
{
@@ -181,6 +188,9 @@ public void OpenXmlSDK_Query_Test()
var worksheetPart = workbookPart!.WorksheetParts.First();
var sheetData = worksheetPart.Worksheet.Elements().First();
- var firstRow = sheetData.Elements().ToList();
+ foreach(var row in sheetData.Elements())
+ {
+ var cellValue = row;
+ }
}
}
\ No newline at end of file
diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs
index 373db436..20cc7d92 100644
--- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs
+++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs
@@ -35,7 +35,7 @@ public void Setup()
_mappingTemplater = MiniExcel.Templaters.GetMappingTemplater(registry);
}
- [Benchmark(Description = "MiniExcel Template Generate")]
+ [Benchmark(Description = "MiniExcel Fill Template")]
public void MiniExcel_Template_Generate_Test()
{
const string templatePath = "TestTemplateBasicIEmumerableFill.xlsx";
@@ -54,7 +54,7 @@ public void MiniExcel_Template_Generate_Test()
_templater.FillTemplate(path.FilePath, templatePath, value);
}
- [Benchmark(Description = "ClosedXml.Report Template Generate")]
+ [Benchmark(Description = "ClosedXml.Report Generate Template")]
public void ClosedXml_Report_Template_Generate_Test()
{
const string templatePath = "TestTemplateBasicIEmumerableFill_ClosedXML_Report.xlsx";
@@ -77,7 +77,7 @@ public void ClosedXml_Report_Template_Generate_Test()
template.SaveAs(path.FilePath);
}
- [Benchmark(Description = "MiniExcel Mapping Template Generate")]
+ [Benchmark(Description = "MiniExcel Mapping Fill Template")]
public void MiniExcel_Mapping_Template_Generate_Test()
{
using var templatePath = AutoDeletingPath.Create();
@@ -96,6 +96,6 @@ public void MiniExcel_Mapping_Template_Generate_Test()
Department = "HR"
});
- _mappingTemplater.ApplyTemplate(outputPath.FilePath, templatePath.FilePath, employees);
+ _mappingTemplater.FillTemplate(outputPath.FilePath, templatePath.FilePath, employees);
}
}
\ No newline at end of file
diff --git a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj
index fb04cf56..c5d151fb 100644
--- a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj
+++ b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj
@@ -11,12 +11,12 @@
-
+
-
-
-
+
+
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 8247ea17..a2cb8fdf 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,7 +2,7 @@
netstandard2.0;net8.0;net9.0;net10.0
- 2.0.0-preview.2
+ 2.0.0-preview.3
enable
enable
14
@@ -12,11 +12,7 @@
MiniExcel
Mini-Software
excel;xlsx;csv;micro-helper;mini;openxml;helper;
- Fast, Low-Memory, Easy Excel .NET processing tool for importing, exporting and templating spreadsheets
- Github : https://github.com/mini-software/MiniExcel
- Gitee : https://gitee.com/dotnetchina/MiniExcel
- Issues : https://github.com/mini-software/MiniExcel/issues
- Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true
+ Lightweight, fast and simple .NET processing tool for importing, exporting and templating spreadsheets.
Wei Lin, Michele Bastione, PING-HSIU SHIH, Amos(izanhzh), eynarhaji, Mini-Software team
Mini-Software, 2021 onwards
en
@@ -25,7 +21,7 @@
https://github.com/mini-software/MiniExcel
Github
icon.png
- Please Check [Release Notes](https://github.com/mini-software/MiniExcel/tree/master/docs)
+ https://github.com/mini-software/MiniExcel/tree/master/docs
true
true
snupkg
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index b152565c..d3a0242d 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -2,10 +2,12 @@
+
-
-
+
+
+
diff --git a/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs b/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs
index e6fcc435..3dabd0e0 100644
--- a/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs
+++ b/src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs
@@ -132,4 +132,49 @@ private static OpenXmlTemplate GetOpenXmlTemplate(Stream stream, OpenXmlConfigur
}
#endregion
+
+ #region Obsolete
+
+ [CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead")]
+ public Task ApplyTemplateAsync(string path, string templatePath, object value, bool overwriteFile = false,
+ OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
+ {
+ return FillTemplateAsync(path, templatePath, value, overwriteFile, configuration, cancellationToken);
+ }
+
+ [CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead")]
+ public Task ApplyTemplateAsync(string path, Stream templateStream, object value, bool overwriteFile = false,
+ OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
+ {
+ return FillTemplateAsync(path, templateStream, value, overwriteFile, configuration, cancellationToken);
+ }
+
+ [CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead")]
+ public Task ApplyTemplateAsync(Stream stream, string templatePath, object value,
+ OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
+ {
+ return FillTemplateAsync(stream, templatePath, value, configuration, cancellationToken);
+ }
+
+ [CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead")]
+ public Task ApplyTemplateAsync(Stream stream, Stream templateStream, object value,
+ OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
+ {
+ return FillTemplateAsync(stream, templateStream, value, configuration, cancellationToken);
+ }
+
+ [CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead")]
+ public Task ApplyTemplateAsync(string path, byte[] templateBytes, object value, bool overwriteFile = false,
+ OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
+ {
+ return FillTemplateAsync(path, templateBytes, value, overwriteFile, configuration, cancellationToken);
+ }
+
+ [CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead")]
+ public Task ApplyTemplateAsync(Stream stream, byte[] templateBytes, object value,
+ OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
+ {
+ return FillTemplateAsync(stream, templateBytes, value, configuration, cancellationToken);
+ }
+ #endregion
}
\ No newline at end of file
diff --git a/src/MiniExcel.OpenXml/FluentMapping/Api/MappingExporter.cs b/src/MiniExcel.OpenXml/FluentMapping/Api/MappingExporter.cs
index d4dc10ea..5cfed4c6 100644
--- a/src/MiniExcel.OpenXml/FluentMapping/Api/MappingExporter.cs
+++ b/src/MiniExcel.OpenXml/FluentMapping/Api/MappingExporter.cs
@@ -1,5 +1,3 @@
-using Zomp.SyncMethodGenerator;
-
namespace MiniExcelLib.OpenXml.FluentMapping.Api;
public sealed partial class MappingExporter
@@ -20,8 +18,14 @@ public MappingExporter(MappingRegistry registry)
public async Task ExportAsync(string path, IEnumerable? values, bool overwriteFile = false, CancellationToken cancellationToken = default) where T : class
{
var filePath = path.EndsWith(".xlsx", StringComparison.InvariantCultureIgnoreCase) ? path : $"{path}.xlsx" ;
-
+
+#if NET8_0_OR_GREATER
+ var stream = overwriteFile ? File.Create(filePath) : new FileStream(filePath, FileMode.CreateNew);
+ await using var disposableStream = stream.ConfigureAwait(false);
+#else
using var stream = overwriteFile ? File.Create(filePath) : new FileStream(filePath, FileMode.CreateNew);
+#endif
+
await ExportAsync(stream, values, cancellationToken).ConfigureAwait(false);
}
diff --git a/src/MiniExcel.OpenXml/FluentMapping/Api/MappingImporter.cs b/src/MiniExcel.OpenXml/FluentMapping/Api/MappingImporter.cs
index f0d37781..d6427048 100644
--- a/src/MiniExcel.OpenXml/FluentMapping/Api/MappingImporter.cs
+++ b/src/MiniExcel.OpenXml/FluentMapping/Api/MappingImporter.cs
@@ -1,6 +1,3 @@
-using System.Runtime.CompilerServices;
-using Zomp.SyncMethodGenerator;
-
namespace MiniExcelLib.OpenXml.FluentMapping.Api;
public sealed partial class MappingImporter()
@@ -15,7 +12,13 @@ public MappingImporter(MappingRegistry registry) : this()
[CreateSyncVersion]
public async IAsyncEnumerable QueryAsync(string path, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new()
{
+ #if NET8_0_OR_GREATER
+ var stream = File.OpenRead(path);
+ await using var disposableStream = stream.ConfigureAwait(false);
+ #else
using var stream = File.OpenRead(path);
+ #endif
+
await foreach (var item in QueryAsync(stream, cancellationToken).ConfigureAwait(false))
yield return item;
}
@@ -36,7 +39,12 @@ public MappingImporter(MappingRegistry registry) : this()
[CreateSyncVersion]
public async Task QuerySingleAsync(string path, CancellationToken cancellationToken = default) where T : class, new()
{
+#if NET8_0_OR_GREATER
+ var stream = File.OpenRead(path);
+ await using var disposableStream = stream.ConfigureAwait(false);
+#else
using var stream = File.OpenRead(path);
+#endif
return await QuerySingleAsync(stream, cancellationToken).ConfigureAwait(false);
}
diff --git a/src/MiniExcel.OpenXml/FluentMapping/Api/MappingTemplater.cs b/src/MiniExcel.OpenXml/FluentMapping/Api/MappingTemplater.cs
index f185b088..248f9702 100644
--- a/src/MiniExcel.OpenXml/FluentMapping/Api/MappingTemplater.cs
+++ b/src/MiniExcel.OpenXml/FluentMapping/Api/MappingTemplater.cs
@@ -1,5 +1,3 @@
-using Zomp.SyncMethodGenerator;
-
namespace MiniExcelLib.OpenXml.FluentMapping.Api;
public sealed partial class MappingTemplater()
@@ -12,7 +10,7 @@ public MappingTemplater(MappingRegistry registry) : this()
}
[CreateSyncVersion]
- public async Task ApplyTemplateAsync(
+ public async Task FillTemplateAsync(
string? outputPath,
string? templatePath,
IEnumerable? values,
@@ -25,13 +23,21 @@ public async Task ApplyTemplateAsync(
if (values is null)
throw new ArgumentNullException(nameof(values));
+#if NET8_0_OR_GREATER
+ var outputStream = File.Create(outputPath);
+ await using var disposableOutputStream = outputStream.ConfigureAwait(false);
+
+ var templateStream = File.OpenRead(templatePath);
+ await using var disposableTemplateStream = templateStream.ConfigureAwait(false);
+#else
using var outputStream = File.Create(outputPath);
using var templateStream = File.OpenRead(templatePath);
- await ApplyTemplateAsync(outputStream, templateStream, values, cancellationToken).ConfigureAwait(false);
+#endif
+ await FillTemplateAsync(outputStream, templateStream, values, cancellationToken).ConfigureAwait(false);
}
[CreateSyncVersion]
- public async Task ApplyTemplateAsync(
+ public async Task FillTemplateAsync(
Stream? outputStream,
Stream? templateStream,
IEnumerable? values,
@@ -54,7 +60,7 @@ await MappingTemplateApplicator.ApplyTemplateAsync(
}
[CreateSyncVersion]
- public async Task ApplyTemplateAsync(
+ public async Task FillTemplateAsync(
Stream? outputStream,
byte[]? templateBytes,
IEnumerable? values,
@@ -67,7 +73,45 @@ public async Task ApplyTemplateAsync(
if (values is null)
throw new ArgumentNullException(nameof(values));
+#if NET8_0_OR_GREATER
+ var templateStream = new MemoryStream(templateBytes);
+ await using var disposableTemplateStream = templateStream.ConfigureAwait(false);
+#else
using var templateStream = new MemoryStream(templateBytes);
- await ApplyTemplateAsync(outputStream, templateStream, values, cancellationToken).ConfigureAwait(false);
+#endif
+ await FillTemplateAsync(outputStream, templateStream, values, cancellationToken).ConfigureAwait(false);
}
-}
\ No newline at end of file
+
+#region Obsolete
+[CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead.")]
+public Task ApplyTemplateAsync(
+ string? outputPath,
+ string? templatePath,
+ IEnumerable? values,
+ CancellationToken cancellationToken = default) where T : class
+{
+ return FillTemplateAsync(outputPath, templatePath, values, cancellationToken);
+}
+
+[CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead.")]
+public Task ApplyTemplateAsync(
+ Stream? outputStream,
+ Stream? templateStream,
+ IEnumerable? values,
+ CancellationToken cancellationToken = default) where T : class
+{
+ return FillTemplateAsync(outputStream, templateStream, values, cancellationToken);
+}
+
+
+[CreateSyncVersion, Obsolete("Please use FillTemplate or FillTemplateAsync instead.")]
+public Task ApplyTemplateAsync(
+ Stream? outputStream,
+ byte[]? templateBytes,
+ IEnumerable? values,
+ CancellationToken cancellationToken = default) where T : class
+{
+ return FillTemplateAsync(outputStream, templateBytes, values, cancellationToken);
+}
+#endregion
+}
diff --git a/src/README.md b/src/README.md
index 6d9fb9f5..d9cb2979 100644
--- a/src/README.md
+++ b/src/README.md
@@ -1,101 +1,148 @@
-
-This project is part of the [.NET Foundation](https://dotnetfoundation.org/projects/project-detail/miniexcel) and operates under their code of conduct.
+## MiniExcel
+
+
---
-### Introduction
+MiniExcel is a simple and efficient Excel processing tool for .NET, specifically designed to minimize memory usage.
-MiniExcel is simple and efficient to avoid OOM's .NET processing Excel tool.
+At present, most popular frameworks need to load all the data from an Excel document into memory to facilitate operations, but this may cause memory consumption problems. MiniExcel's approach is different: the data is processed row by row in a streaming manner, reducing the original consumption from potentially hundreds of megabytes to just a few megabytes, effectively preventing out-of-memory(OOM) issues.
-At present, most popular frameworks need to load all the data into the memory to facilitate operation, but it will cause memory consumption problems. MiniExcel tries to use algorithm from a stream to reduce the original 1000 MB occupation to a few MB to avoid OOM(out of memory).
+```mermaid
+flowchart LR
+ A1(["Excel analysis process"]) --> A2{{"Unzipping XLSX file"}} --> A3{{"Parsing OpenXML"}} --> A4{{"Model conversion"}} --> A5(["Output"])
-
+ B1(["Other Excel Frameworks"]) --> B2{{"Memory"}} --> B3{{"Memory"}} --> B4{{"Workbooks & Worksheets"}} --> B5(["All rows at the same time"])
+ C1(["MiniExcel"]) --> C2{{"Stream"}} --> C3{{"Stream"}} --> C4{{"POCO or dynamic"}} --> C5(["Deferred execution row by row"])
-### Features
+ classDef analysis fill:#D0E8FF,stroke:#1E88E5,color:#0D47A1,font-weight:bold;
+ classDef others fill:#FCE4EC,stroke:#EC407A,color:#880E4F,font-weight:bold;
+ classDef miniexcel fill:#E8F5E9,stroke:#388E3C,color:#1B5E20,font-weight:bold;
-- Low memory consumption, avoid OOM (out of memory) and full GC
-- Supports real time operation of each row of data
-- Supports LINQ deferred execution, it can do low-consumption, fast paging and other complex queries
-- Lightweight, without Microsoft Office installed, no COM+, DLL size is less than 400KB
-- Easy API style to read/write/fill excel
+ class A1,A2,A3,A4,A5 analysis;
+ class B1,B2,B3,B4,B5 others;
+ class C1,C2,C3,C4,C5 miniexcel;
+```
-### Get Started
+### Features
-- [Import/Query Excel](#getstart1)
+- Minimizes memory consumption, preventing out-of-memory (OOM) errors and avoiding full garbage collections
+- Enables real-time, row-level data operations for better performance on large datasets
+- Supports LINQ with deferred execution, allowing for fast, memory-efficient paging and complex queries
+- Lightweight, without the need for Microsoft Office or COM+ components, and a size under 800KB
+- Simple and intuitive API style to import, export, and template Excel worksheets
-- [Export/Create Excel](#getstart2)
+### Quickstart
-- [Excel Template](#getstart3)
+#### Importing
-- [Excel Column Name/Index/Ignore Attribute](#getstart4)
+You can query worksheets and map the data either to strongly typed classes or dynamic objects:
-- [Examples](#getstart5)
+```csharp
+public class UserAccount
+{
+ public Guid ID { get; set; }
+ public string Name { get; set; }
+ public DateTime DateOfBirth { get; set; }
+ public int Age { get; set; }
+ public bool Vip { get; set; }
+ public decimal Points { get; set; }
+}
+var userRows = MiniExcel.Query(path);
+// or simply
-### Installation
+var dynamicRows = MiniExcel.Query(path);
+```
-You can install the package [from NuGet](https://www.nuget.org/packages/MiniExcel)
+#### Exporting
-### Release Notes
+There are multiple ways to exprt data to an Excel document:
-Please Check [Release Notes](docs)
+```csharp
+// From strongly typed objects
-### TODO
+var values = new[]
+{
+ new { Name = "MiniExcel", Value = 1 },
+ new { Name = "Github", Value = 2 }
+};
+MiniExcel.SaveAs(yourPath, values);
-Please Check [TODO](https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true)
-### Performance
+// From anonymous objects
-The code for the benchmarks can be found in [MiniExcel.Benchmarks](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/MiniExcel.Benchmarks).
-To run all the benchmarks use:
+public class TestType
+{
+ public string Name { get; set; }
+ public int Value { get; set; }
+}
-```bash
-dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
-```
+TestType[] values =
+[
+ new TestType { Name = "MiniExcel", Value = 1 },
+ new TestType { Name = "Github", Value = 2 }
+];
+MiniExcel.SaveAs(yourPath, values);
-Hardware and settings used are the following:
-```
-BenchmarkDotNet v0.15.0, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
-AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
-.NET SDK 9.0.300
- [Host] : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
- ShortRun : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
-```
-#### Import/Query Excel
+//From a IEnumerable>
-The file used to test performance is [**Test1,000,000x10.xlsx**](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/MiniExcel.Benchmarks/Test1%2C000%2C000x10.xlsx), a 32MB document containing 1,000,000 rows * 10 columns whose cells are filled with the string "HelloWorld".
+new List>() dicts =
+[
+ new Dictionary { { "Name", "MiniExcel" }, { "Value", 1 } },
+ new Dictionary { { "Name", "Github" }, { "Value", 2 } }
+];
+MiniExcel.SaveAs(yourPath, dicts);
-| Method | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated |
-|--------------------------------------|-----------------:|---------------:|-----------------:|------------:|------------:|----------:|--------------:|
-| 'MiniExcel QueryFirst' | 63.70 μs | 0.337 μs | 6.144 μs | 2.9297 | 2.7669 | - | 49.67 KB |
-| 'ExcelDataReader QueryFirst' | 5,010,679.51 μs | 53,245.186 μs | 971,390.400 μs | 105000.0000 | 333.3333 | - | 1717272.56 KB |
-| 'MiniExcel Query' | 9,172,286.91 μs | 12,805.326 μs | 233,616.824 μs | 448500.0000 | 4666.6667 | - | 7327883.36 KB |
-| 'ExcelDataReader Query' | 10,609,617.09 μs | 29,055.953 μs | 530,088.745 μs | 275666.6667 | 68666.6667 | - | 4504691.87 KB |
-| 'Epplus QueryFirst' | 13,770,656.24 μs | 45,909.809 μs | 837,565.827 μs | 174333.3333 | 88833.3333 | 4333.3333 | 3700587.76 KB |
-| 'Epplus Query' | 19,257,306.83 μs | 63,117.956 μs | 1,151,506.486 μs | 452333.3333 | 90500.0000 | 5333.3333 | 8223933.16 KB |
-| 'ClosedXml Query' | 31,070,263.83 μs | 342,973.671 μs | 6,257,116.502 μs | 401666.6667 | 104166.6667 | 3333.3333 | 6822559.68 KB |
-| 'ClosedXml QueryFirst' | 31,141,877.48 μs | 21,006.538 μs | 383,237.459 μs | 402166.6667 | 104833.3333 | 3833.3333 | 6738357.8 KB |
-| 'OpenXmlSDK QueryFirst' | 31,750,686.63 μs | 263,328.569 μs | 4,804,093.357 μs | 374666.6667 | 374500.0000 | 3166.6667 | 6069266.96 KB |
-| 'OpenXmlSDK Query' | 32,919,119.46 μs | 411,395.682 μs | 7,505,388.691 μs | 374666.6667 | 374500.0000 | 3166.6667 | 6078467.83 KB |
+// Directly from a IDataReader
-#### Export/Create Excel
+using var connection = yourConnectionProvider.GetConnection();
+connection.Open();
-Logic: create a total of 10,000,000 "HelloWorld" cells Excel document
+using var cmd = connection.CreateCommand();
+cmd.CommandText = """
+ SELECT 'MiniExcel' AS "Name", 1 AS "Value"
+ UNION ALL
+ SELECT 'Github', 2
+ """;
-| Method | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated |
-|----------------------------------------------|---------:|---------:|---------:|------------:|------------:|----------:|----------:|
-| 'MiniExcel Create Xlsx' | 4.427 s | 0.0056 s | 0.1023 s | 251666.6667 | 1833.3333 | 1666.6667 | 3.92 GB |
-| 'OpenXmlSdk Create Xlsx by DOM mode' | 22.729 s | 0.1226 s | 2.2374 s | 307000.0000 | 306833.3333 | 3833.3333 | 6.22 GB |
-| 'ClosedXml Create Xlsx' | 22.851 s | 0.0190 s | 0.3473 s | 195500.0000 | 54500.0000 | 4166.6667 | 4.48 GB |
-| 'Epplus Create Xlsx' | 23.027 s | 0.0088 s | 0.1596 s | 89000.0000 | 17500.0000 | 6000.0000 | 2.51 GB |
+using var reader = cmd.ExecuteReader();
+MiniExcel.SaveAs(yourPath, reader);
-Warning: these results may be outdated. You can find the benchmarks for the latest release [here](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/results).
+// From a DataTable
-### Documents
+var table = new DataTable();
+table.Columns.Add("Name", typeof(string));
+table.Columns.Add("Value", typeof(int));
+table.Rows.Add("MiniExcel", 1);
+table.Rows.Add("Github", 2);
-https://github.com/mini-software/MiniExcel
+MiniExcel.SaveAs(path, table);
+```
diff --git a/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj b/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj
index 7e818ea0..e3901b2c 100644
--- a/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj
+++ b/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTemplateTests.cs b/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTemplateTests.cs
index d0084c2d..9d778d5c 100644
--- a/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTemplateTests.cs
+++ b/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTemplateTests.cs
@@ -71,7 +71,7 @@ public async Task BasicTemplateTest()
using var outputPath = AutoDeletingPath.Create();
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputPath.ToString(), templatePath.ToString(), [data]);
+ await templater.FillTemplateAsync(outputPath.ToString(), templatePath.ToString(), [data]);
var rows = _importer.Query(outputPath.ToString(), useHeaderRow: false).ToList();
@@ -130,7 +130,7 @@ public async Task StreamOverloadTest()
using (var templateStream = File.OpenRead(templatePath.ToString()))
{
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputStream, templateStream, [data]);
+ await templater.FillTemplateAsync(outputStream, templateStream, [data]);
}
var rows = _importer.Query(outputPath.ToString(), useHeaderRow: false).ToList();
@@ -171,7 +171,7 @@ public async Task ByteArrayOverloadTest()
using (var outputStream = File.Create(outputPath.ToString()))
{
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputStream, templateBytes, [data]);
+ await templater.FillTemplateAsync(outputStream, templateBytes, [data]);
}
var rows = _importer.Query(outputPath.ToString(), useHeaderRow: false).ToList();
@@ -236,7 +236,7 @@ public async Task CollectionTemplateTest()
using var outputPath = AutoDeletingPath.Create();
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputPath.ToString(), templatePath.ToString(), [dept]);
+ await templater.FillTemplateAsync(outputPath.ToString(), templatePath.ToString(), [dept]);
var rows = _importer.Query(outputPath.ToString(), useHeaderRow: false).ToList();
@@ -284,7 +284,7 @@ public async Task EmptyDataTest()
using var outputPath = AutoDeletingPath.Create();
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputPath.ToString(), templatePath.ToString(), Array.Empty());
+ await templater.FillTemplateAsync(outputPath.ToString(), templatePath.ToString(), Array.Empty());
var rows = _importer.Query(outputPath.ToString(), useHeaderRow: false).ToList();
Assert.Equal(3, rows.Count); // Column headers + our headers + empty data row
@@ -328,7 +328,7 @@ public async Task NullValuesTest()
// Apply template
using var outputPath = AutoDeletingPath.Create();
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputPath.ToString(), templatePath.ToString(), [data]);
+ await templater.FillTemplateAsync(outputPath.ToString(), templatePath.ToString(), [data]);
// Verify null handling
// Verify - use useHeaderRow=false since we want to see all rows
@@ -373,7 +373,7 @@ public async Task MultipleItemsTest()
// Apply template
using var outputPath = AutoDeletingPath.Create();
var templater = MiniExcel.Templaters.GetMappingTemplater(registry);
- await templater.ApplyTemplateAsync(outputPath.ToString(), templatePath.ToString(), data);
+ await templater.FillTemplateAsync(outputPath.ToString(), templatePath.ToString(), data);
// Verify - should only update first item since mapping is for specific cells
// Verify - use useHeaderRow=false since we want to see all rows
diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj b/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj
index ffe2eebc..a31d2f89 100644
--- a/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj
+++ b/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj
@@ -15,11 +15,10 @@
-
-
-
-
-
+
+
+
+
@@ -53,7 +52,6 @@
-
diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs
index 3c11664d..ec9f7891 100644
--- a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs
+++ b/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs
@@ -181,14 +181,16 @@ public async Task Issue132()
{
using var path = AutoDeletingPath.Create();
- var value = JsonConvert.DeserializeObject(
- JsonConvert.SerializeObject(new[]
- {
- new { Name ="Jack", Age=25,InDate=new DateTime(2021,01,03)},
- new { Name ="Henry", Age=36,InDate=new DateTime(2020,05,03)},
- })
- );
- var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value);
+
+ var dt = new DataTable();
+ dt.Columns.Add("Name");
+ dt.Columns.Add("Age");
+ dt.Columns.Add("Date");
+
+ dt.Rows.Add("Jack", 25, new DateTime(2021, 01, 03));
+ dt.Rows.Add("Henry", 36, new DateTime(2021, 01, 03));
+
+ var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), dt);
Assert.Single(rowsWritten);
Assert.Equal(2, rowsWritten[0]);
@@ -203,15 +205,22 @@ public async Task Issue235()
{
using var file = AutoDeletingPath.Create();
var path = file.ToString();
- var sheets = new DataSet();
- var users = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } }));
- users.TableName = "users";
+ var users = new DataTable { TableName = "users" };
+ users.Columns.Add("Name", typeof(string));
+ users.Columns.Add("Age", typeof(int));
+ users.Rows.Add("Jack", 25);
+ users.Rows.Add("Mike", 44);
+
+ var departments = new DataTable { TableName = "departments" };
+ departments.Columns.Add("ID");
+ departments.Columns.Add("Name");
+ departments.Rows.Add("01", "HR");
+ departments.Rows.Add("02", "IT");
+
+ DataSet sheets = new();
sheets.Tables.Add(users);
-
- var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } }));
- department.TableName = "department";
- sheets.Tables.Add(department);
+ sheets.Tables.Add(departments);
var rowsWritten = await _excelExporter.ExportAsync(path, sheets);
Assert.Equal(2, rowsWritten.Length);
@@ -219,24 +228,19 @@ public async Task Issue235()
var sheetNames = await _excelImporter.GetSheetNamesAsync(path);
Assert.Equal("users", sheetNames[0]);
- Assert.Equal("department", sheetNames[1]);
-
- {
- var q = _excelImporter.QueryAsync(path, true, sheetName: "users").ToBlockingEnumerable();
- var rows = q.ToList();
- Assert.Equal("Jack", rows[0].Name);
- Assert.Equal(25, rows[0].Age);
- Assert.Equal("Mike", rows[1].Name);
- Assert.Equal(44, rows[1].Age);
- }
- {
- var q = _excelImporter.QueryAsync(path, true, sheetName: "department").ToBlockingEnumerable();
- var rows = q.ToList();
- Assert.Equal("01", rows[0].ID);
- Assert.Equal("HR", rows[0].Name);
- Assert.Equal("02", rows[1].ID);
- Assert.Equal("IT", rows[1].Name);
- }
+ Assert.Equal("departments", sheetNames[1]);
+
+ var rows1 = await _excelImporter.QueryAsync(path, true, sheetName: "users").ToListAsync();
+ Assert.Equal("Jack", rows1[0].Name);
+ Assert.Equal(25, rows1[0].Age);
+ Assert.Equal("Mike", rows1[1].Name);
+ Assert.Equal(44, rows1[1].Age);
+
+ var rows2 = await _excelImporter.QueryAsync(path, true, sheetName: "departments").ToListAsync();
+ Assert.Equal("01", rows2[0].ID);
+ Assert.Equal("HR", rows2[0].Name);
+ Assert.Equal("02", rows2[1].ID);
+ Assert.Equal("IT", rows2[1].Name);
}
///
@@ -246,11 +250,8 @@ public async Task Issue235()
public async Task Issue233()
{
var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx");
-
var dt = await _excelImporter.QueryAsDataTableAsync(path);
-
-
var rows = dt.Rows;
Assert.Equal(0.55, rows[0]["Size"]);
@@ -1131,59 +1132,49 @@ public async Task Issue157()
{
using var file = AutoDeletingPath.Create();
var path = file.ToString();
-
- _output.WriteLine("==== SaveAs by strongly type ====");
- var input = JsonConvert.DeserializeObject>(
- """
- [
- {
- "ID":"78de23d2-dcb6-bd3d-ec67-c112bbc322a2",
- "Name":"Wade",
- "BoD":"2020-09-27T00:00:00",
- "Age":5019,
- "VIP":false,
- "Points":5019.12,
- "IgnoredProperty":null
- },
- {
- "ID":"20d3bfce-27c3-ad3e-4f70-35c81c7e8e45",
- "Name":"Felix",
- "BoD":"2020-10-25T00:00:00",
- "Age":7028,
- "VIP":true,
- "Points":7028.46,
- "IgnoredProperty":null
- },
- {
- "ID":"52013bf0-9aeb-48e6-e5f5-e9500afb034f",
- "Name":"Phelan",
- "BoD":"2021-10-04T00:00:00",
- "Age":3836,
- "VIP":true,
- "Points":3835.7,
- "IgnoredProperty":null
- },
- {
- "ID":"3b97b87c-7afe-664f-1af5-6914d313ae25",
- "Name":"Samuel",
- "BoD":"2020-06-21T00:00:00",
- "Age":9352,
- "VIP":false,
- "Points":9351.71,
- "IgnoredProperty":null
- },
- {
- "ID":"9a989c43-d55f-5306-0d2f-0fbafae135bb",
- "Name":"Raymond",
- "BoD":"2021-07-12T00:00:00",
- "Age":8210,
- "VIP":true,
- "Points":8209.76,
- "IgnoredProperty":null
- }
- ]
- """);
- var rowsWritten = await _excelExporter.ExportAsync(path, input);
+
+ List data =
+ [
+ new()
+ {
+ ID = new Guid("78de23d2-dcb6-bd3d-ec67-c112bbc322a2"),
+ Name = "Wade",
+ BoD = new DateTime(2020, 9, 27),
+ Points = 5019.12m
+ },
+ new()
+ {
+ ID = new Guid("20d3bfce-27c3-ad3e-4f70-35c81c7e8e45"),
+ Name = "Felix",
+ BoD = new DateTime(2020, 10, 25),
+ Points = 7028.46m
+ },
+ new()
+ {
+ ID = new Guid("52013bf0-9aeb-48e6-e5f5-e9500afb034f"),
+ Name = "Phelan",
+ BoD = new DateTime(2020, 10, 25),
+ Points = 3835.7m,
+ VIP = true
+ },
+ new()
+ {
+ ID = new Guid("3b97b87c-7afe-664f-1af5-6914d313ae25"),
+ Name = "Samuel",
+ BoD = new DateTime(2020, 6, 21),
+ Points = 9351.71m
+ },
+ new()
+ {
+ ID = new Guid("9a989c43-d55f-5306-0d2f-0fbafae135bb"),
+ Name = "Raymond",
+ BoD = new DateTime(2021, 7, 12),
+ Points = 8209.76m,
+ VIP = true
+ }
+ ];
+
+ var rowsWritten = await _excelExporter.ExportAsync(path, data);
Assert.Single(rowsWritten);
Assert.Equal(5, rowsWritten[0]);
@@ -1221,7 +1212,7 @@ public async Task Issue157()
Assert.Equal("Wade", rows[0].Name);
Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD);
Assert.False(rows[0].VIP);
- Assert.Equal(5019m, rows[0].Points);
+ Assert.Equal(5019.12m, rows[0].Points);
Assert.Equal(1, rows[0].IgnoredProperty);
}
}
diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs b/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs
index 15cbfb65..f79fb95c 100644
--- a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs
+++ b/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs
@@ -17,6 +17,10 @@ public class MiniExcelIssueTests(ITestOutputHelper output)
private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter();
private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater();
+ static MiniExcelIssueTests()
+ {
+ EpplusLicence.SetContext();
+ }
///
/// https://github.com/mini-software/MiniExcel/issues/549
@@ -207,34 +211,32 @@ public void TestIssue370()
{
DynamicColumns =
[
- new DynamicExcelColumn("id") { Ignore=true },
- new DynamicExcelColumn("name") { Index=1,Width=10 },
- new DynamicExcelColumn("createdate") { Index=0, Format="yyyy-MM-dd", Width=15 },
- new DynamicExcelColumn("point") { Index=2, Name="Account Point" }
+ new DynamicExcelColumn("Id") { Ignore = true },
+ new DynamicExcelColumn("Name") { Index = 1,Width = 10 },
+ new DynamicExcelColumn("Date") { Index = 0, Format="yyyy-MM-dd", Width = 15 },
+ new DynamicExcelColumn("Point") { Index = 2, Name = "Account Point" }
]
};
using var path = AutoDeletingPath.Create();
- var json = JsonConvert.SerializeObject(new[]
- {
- new
+ List> value =
+ [
+ new()
{
- id = 1,
- name = "Jack",
- createdate = new DateTime(2022, 04, 12),
- point = 123.456
+ ["Id"] = 1,
+ ["Name"] = "Jack",
+ ["Date"] = new DateTime(2022, 04, 12),
+ ["Point"] = 123.456
}
- }, Formatting.Indented);
-
- var value = JsonConvert.DeserializeObject>>(json);
+ ];
_excelExporter.Export(path.ToString(), value, configuration: config);
var rows = _excelImporter.Query(path.ToString()).ToList();
- Assert.Equal("createdate", rows[0].A);
+ Assert.Equal("Date", rows[0].A);
Assert.Equal(new DateTime(2022, 04, 12), rows[1].A);
- Assert.Equal("name", rows[0].B);
+ Assert.Equal("Name", rows[0].B);
Assert.Equal("Jack", rows[1].B);
Assert.Equal("Account Point", rows[0].C);
- Assert.Equal(123.456, rows[1].C);
+ Assert.Equal(123.456, rows[1].C);
}
[Fact]
@@ -1526,14 +1528,16 @@ public void Issue132()
{
using var path = AutoDeletingPath.Create();
- var value = JsonConvert.DeserializeObject(
- JsonConvert.SerializeObject(new[]
- {
- new { name = "Jack", Age = 25, InDate = new DateTime(2021,01,03)},
- new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)},
- })
- );
- _excelExporter.Export(path.ToString(), value);
+
+ var dt = new DataTable();
+ dt.Columns.Add("Name");
+ dt.Columns.Add("Age");
+ dt.Columns.Add("Date");
+
+ dt.Rows.Add("Jack", 25, new DateTime(2021, 01, 03));
+ dt.Rows.Add("Henry", 36, new DateTime(2021, 01, 03));
+
+ _excelExporter.Export(path.ToString(), dt);
}
}
@@ -1544,24 +1548,22 @@ public void Issue132()
public void Issue235()
{
using var path = AutoDeletingPath.Create();
+
+ var users = new DataTable { TableName = "users" };
+ users.Columns.Add("Name", typeof(string));
+ users.Columns.Add("Age", typeof(int));
+ users.Rows.Add("Jack", 25);
+ users.Rows.Add("Mike", 44);
+
+ var departments = new DataTable { TableName = "departments" };
+ departments.Columns.Add("ID");
+ departments.Columns.Add("Name");
+ departments.Rows.Add("01", "HR");
+ departments.Rows.Add("02", "IT");
DataSet dataSet = new();
- var users = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[]
- {
- new { Name = "Jack", Age = 25 },
- new { Name = "Mike", Age = 44 }
- }));
- users!.TableName = "users";
-
- var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[]
- {
- new { ID = "01", Name = "HR" },
- new { ID = "02", Name = "IT" }
- }));
- department!.TableName = "department";
-
dataSet.Tables.Add(users);
- dataSet.Tables.Add(department);
+ dataSet.Tables.Add(departments);
var rowsWritten = _excelExporter.Export(path.ToString(), dataSet);
Assert.Equal(2, rowsWritten.Length);
@@ -1569,22 +1571,19 @@ public void Issue235()
var sheetNames = _excelImporter.GetSheetNames(path.ToString());
Assert.Equal("users", sheetNames[0]);
- Assert.Equal("department", sheetNames[1]);
+ Assert.Equal("departments", sheetNames[1]);
- {
- var rows = _excelImporter.Query(path.ToString(), true, sheetName: "users").ToList();
- Assert.Equal("Jack", rows[0].Name);
- Assert.Equal(25, rows[0].Age);
- Assert.Equal("Mike", rows[1].Name);
- Assert.Equal(44, rows[1].Age);
- }
- {
- var rows = _excelImporter.Query(path.ToString(), true, sheetName: "department").ToList();
- Assert.Equal("01", rows[0].ID);
- Assert.Equal("HR", rows[0].Name);
- Assert.Equal("02", rows[1].ID);
- Assert.Equal("IT", rows[1].Name);
- }
+ var rows1 = _excelImporter.Query(path.ToString(), true, sheetName: "users").ToList();
+ Assert.Equal("Jack", rows1[0].Name);
+ Assert.Equal(25, rows1[0].Age);
+ Assert.Equal("Mike", rows1[1].Name);
+ Assert.Equal(44, rows1[1].Age);
+
+ var rows2 = _excelImporter.Query(path.ToString(), true, sheetName: "departments").ToList();
+ Assert.Equal("01", rows2[0].ID);
+ Assert.Equal("HR", rows2[0].Name);
+ Assert.Equal("02", rows2[1].ID);
+ Assert.Equal("IT", rows2[1].Name);
}
///
@@ -2355,44 +2354,49 @@ public void Issue157()
{
using var file = AutoDeletingPath.Create();
var path = file.ToString();
- _output.WriteLine("==== SaveAs by strongly type ====");
-
- var input = JsonConvert.DeserializeObject>(
- """
- [
- {
- "ID":"78de23d2-dcb6-bd3d-ec67-c112bbc322a2",
- "Name":"Wade","BoD":"2020-09-27T00:00:00",
- "Age":5019,"VIP":false,"Points":5019.12,
- "IgnoredProperty":null
- },
- {
- "ID":"20d3bfce-27c3-ad3e-4f70-35c81c7e8e45",
- "Name":"Felix","BoD":"2020-10-25T00:00:00",
- "Age":7028,"VIP":true,"Points":7028.46,
- "IgnoredProperty":null
- },
- {
- "ID":"52013bf0-9aeb-48e6-e5f5-e9500afb034f",
- "Name":"Phelan","BoD":"2021-10-04T00:00:00",
- "Age":3836,"VIP":true,"Points":3835.7,
- "IgnoredProperty":null
- },
- {
- "ID":"3b97b87c-7afe-664f-1af5-6914d313ae25",
- "Name":"Samuel","BoD":"2020-06-21T00:00:00",
- "Age":9352,"VIP":false,"Points":9351.71,
- "IgnoredProperty":null
- },
- {
- "ID":"9a989c43-d55f-5306-0d2f-0fbafae135bb",
- "Name":"Raymond","BoD":"2021-07-12T00:00:00",
- "Age":8210,"VIP":true,"Points":8209.76,
- "IgnoredProperty":null
- }
- ]
- """);
- _excelExporter.Export(path, input);
+
+ List data =
+ [
+ new()
+ {
+ ID = new Guid("78de23d2-dcb6-bd3d-ec67-c112bbc322a2"),
+ Name = "Wade",
+ BoD = new DateTime(2020, 9, 27),
+ Points = 5019.12m
+ },
+ new()
+ {
+ ID = new Guid("20d3bfce-27c3-ad3e-4f70-35c81c7e8e45"),
+ Name = "Felix",
+ BoD = new DateTime(2020, 10, 25),
+ Points = 7028.46m
+ },
+ new()
+ {
+ ID = new Guid("52013bf0-9aeb-48e6-e5f5-e9500afb034f"),
+ Name = "Phelan",
+ BoD = new DateTime(2020, 10, 25),
+ Points = 3835.7m,
+ VIP = true
+ },
+ new()
+ {
+ ID = new Guid("3b97b87c-7afe-664f-1af5-6914d313ae25"),
+ Name = "Samuel",
+ BoD = new DateTime(2020, 6, 21),
+ Points = 9351.71m
+ },
+ new()
+ {
+ ID = new Guid("9a989c43-d55f-5306-0d2f-0fbafae135bb"),
+ Name = "Raymond",
+ BoD = new DateTime(2021, 7, 12),
+ Points = 8209.76m,
+ VIP = true
+ }
+ ];
+
+ _excelExporter.Export(path, data);
var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList();
Assert.Equal(6, rows.Count);
@@ -2422,11 +2426,11 @@ public void Issue157()
var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList();
Assert.Equal(5, rows.Count);
- Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID);
+ Assert.Equal(new Guid("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID);
Assert.Equal("Wade", rows[0].Name);
- Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD);
+ Assert.Equal(new DateTime(2020,9,27), rows[0].BoD);
Assert.False(rows[0].VIP);
- Assert.Equal(5019m, rows[0].Points);
+ Assert.Equal(5019.12m, rows[0].Points);
Assert.Equal(1, rows[0].IgnoredProperty);
}
}
diff --git a/tests/MiniExcel.OpenXml.Tests/Utils/EpplusLicense.cs b/tests/MiniExcel.OpenXml.Tests/Utils/EpplusLicense.cs
index 37b8dcc8..e61a8f0d 100644
--- a/tests/MiniExcel.OpenXml.Tests/Utils/EpplusLicense.cs
+++ b/tests/MiniExcel.OpenXml.Tests/Utils/EpplusLicense.cs
@@ -2,8 +2,6 @@ namespace MiniExcelLib.OpenXml.Tests.Utils;
internal static class EpplusLicence
{
- static EpplusLicence()
- {
- ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
- }
+ internal static void SetContext()
+ => ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
}
\ No newline at end of file
diff --git a/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj b/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj
index cafefd80..67a7fb1a 100644
--- a/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj
+++ b/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/tests/data/xlsx/TestIssue157.xlsx b/tests/data/xlsx/TestIssue157.xlsx
index 11a782b1..07953f0c 100644
Binary files a/tests/data/xlsx/TestIssue157.xlsx and b/tests/data/xlsx/TestIssue157.xlsx differ