Skip to content

Commit e1c6869

Browse files
Added net461 compilation target (#978)
* Added net461 compilation target This was necessary to prevent .net framework projects to restore the net45 version of the package (which lacks support for exporting data from async enumerables among other things) instead of the "intended" netstandard2.0
1 parent ec8b7d3 commit e1c6869

10 files changed

Lines changed: 156 additions & 154 deletions

README-NuGet.md

Lines changed: 92 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,132 @@
1+
## MiniExcel
2+
[![NuGet Version](https://img.shields.io/nuget/v/MiniExcel.svg)](https://www.nuget.org/packages/MiniExcel) 
3+
[![NuGet Downloads](https://img.shields.io/nuget/dt/MiniExcel.svg)](https://www.nuget.org/packages/MiniExcel) 
4+
[![GitHub Stars](https://img.shields.io/github/stars/mini-software/MiniExcel?logo=github)](https://github.com/mini-software/MiniExcel) 
5+
[![Gitee Stars](https://gitee.com/dotnetchina/MiniExcel/badge/star.svg)](https://gitee.com/dotnetchina/MiniExcel) 
6+
[![.NET Version](https://img.shields.io/badge/.NET-%3E%3D%204.5-red.svg)](https://www.nuget.org/packages/MiniExcel) 
7+
[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/mini-software/MiniExcel)
18

29
This project is part of the [.NET Foundation](https://dotnetfoundation.org/projects/project-detail/miniexcel) and operates under their code of conduct.
310

411
---
512

6-
### Introduction
13+
MiniExcel is a simple and efficient Excel processing tool for .NET, specifically designed to minimize memory usage.
714

8-
MiniExcel is simple and efficient to avoid OOM's .NET processing Excel tool.
15+
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.
916

10-
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).
17+
```mermaid
18+
flowchart LR
19+
A1(["Excel analysis<br>process"]) --> A2{{"Unzipping<br>XLSX file"}} --> A3{{"Parsing<br>OpenXML"}} --> A4{{"Model<br>conversion"}} --> A5(["Output"])
1120
12-
![image](https://user-images.githubusercontent.com/12729184/113086657-ab8bd000-9214-11eb-9563-c970ac1ee35e.png)
21+
B1(["Other Excel<br>Frameworks"]) --> B2{{"Memory"}} --> B3{{"Memory"}} --> B4{{"Workbooks &<br>Worksheets"}} --> B5(["All rows at<br>the same time"])
1322
23+
C1(["MiniExcel"]) --> C2{{"Stream"}} --> C3{{"Stream"}} --> C4{{"POCO or dynamic"}} --> C5(["Deferred execution<br>row by row"])
1424
15-
### Features
25+
classDef analysis fill:#D0E8FF,stroke:#1E88E5,color:#0D47A1,font-weight:bold;
26+
classDef others fill:#FCE4EC,stroke:#EC407A,color:#880E4F,font-weight:bold;
27+
classDef miniexcel fill:#E8F5E9,stroke:#388E3C,color:#1B5E20,font-weight:bold;
1628
17-
- Low memory consumption, avoid OOM (out of memory) and full GC
18-
- Supports real time operation of each row of data
19-
- Supports LINQ deferred execution, it can do low-consumption, fast paging and other complex queries
20-
- Lightweight, without Microsoft Office installed, no COM+, DLL size is less than 400KB
21-
- Easy API style to read/write/fill excel
29+
class A1,A2,A3,A4,A5 analysis;
30+
class B1,B2,B3,B4,B5 others;
31+
class C1,C2,C3,C4,C5 miniexcel;
32+
```
2233

23-
### Get Started
34+
### Features
2435

25-
- [Import/Query Excel](#getstart1)
36+
- Minimizes memory consumption, preventing out-of-memory (OOM) errors and avoiding full garbage collections
37+
- Enables real-time, row-level data operations for better performance on large datasets
38+
- Supports LINQ with deferred execution, allowing for fast, memory-efficient paging and complex queries
39+
- Lightweight, without the need for Microsoft Office or COM+ components, and a size under 800KB
40+
- Simple and intuitive API to import, export, and template Excel worksheets
2641

27-
- [Export/Create Excel](#getstart2)
42+
### Quickstart
2843

29-
- [Excel Template](#getstart3)
44+
#### Importing
3045

31-
- [Excel Column Name/Index/Ignore Attribute](#getstart4)
46+
You can query worksheets and map the data either to strongly typed classes or dynamic objects:
3247

33-
- [Examples](#getstart5)
48+
```csharp
49+
public class UserAccount
50+
{
51+
public Guid ID { get; set; }
52+
public string Name { get; set; }
53+
public DateTime DateOfBirth { get; set; }
54+
public int Age { get; set; }
55+
public bool Vip { get; set; }
56+
public decimal Points { get; set; }
57+
}
3458

59+
var userRows = MiniExcel.Query<UserAccount>(path);
3560

61+
// or simply
3662
37-
### Installation
63+
var rows = MiniExcel.Query(path);
64+
```
3865

39-
You can install the package [from NuGet](https://www.nuget.org/packages/MiniExcel)
66+
#### Exporting
4067

41-
### Release Notes
68+
There are multiple ways to export data to an Excel document:
4269

43-
Please Check [Release Notes](docs)
70+
```csharp
71+
// From strongly typed objects
4472
45-
### TODO
73+
var values = new[]
74+
{
75+
new { Name = "MiniExcel", Value = 1 },
76+
new { Name = "Github", Value = 2 }
77+
};
78+
MiniExcel.SaveAs(yourPath, values);
4679

47-
Please Check [TODO](https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true)
4880

49-
### Performance
81+
// From anonymous objects
5082
51-
The code for the benchmarks can be found in [MiniExcel.Benchmarks](https://github.com/mini-software/MiniExcel/tree/master/benchmarks/MiniExcel.Benchmarks).
52-
To run all the benchmarks use:
83+
public class TestType
84+
{
85+
public string Name { get; set; }
86+
public int Value { get; set; }
87+
}
5388

54-
```bash
55-
dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
56-
```
89+
TestType[] values =
90+
[
91+
new TestType { Name = "MiniExcel", Value = 1 },
92+
new TestType { Name = "Github", Value = 2 }
93+
];
94+
MiniExcel.SaveAs(yourPath, values);
5795

58-
Hardware and settings used are the following:
59-
```
60-
BenchmarkDotNet v0.15.0, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
61-
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
62-
.NET SDK 9.0.300
63-
[Host] : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
64-
ShortRun : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
65-
```
6696

67-
#### Import/Query Excel
97+
//From a IEnumerable<IDictionary<string, object>>
6898
69-
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".
99+
List<Dictionary<string, object>>() dicts =
100+
[
101+
new Dictionary<string, object> { { "Name", "MiniExcel" }, { "Value", 1 } },
102+
new Dictionary<string, object> { { "Name", "Github" }, { "Value", 2 } }
103+
];
104+
MiniExcel.SaveAs(yourPath, dicts);
70105

71-
| Method | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated |
72-
|--------------------------------------|-----------------:|---------------:|-----------------:|------------:|------------:|----------:|--------------:|
73-
| &#39;MiniExcel QueryFirst&#39; | 63.70 μs | 0.337 μs | 6.144 μs | 2.9297 | 2.7669 | - | 49.67 KB |
74-
| &#39;ExcelDataReader QueryFirst&#39; | 5,010,679.51 μs | 53,245.186 μs | 971,390.400 μs | 105000.0000 | 333.3333 | - | 1717272.56 KB |
75-
| &#39;MiniExcel Query&#39; | 9,172,286.91 μs | 12,805.326 μs | 233,616.824 μs | 448500.0000 | 4666.6667 | - | 7327883.36 KB |
76-
| &#39;ExcelDataReader Query&#39; | 10,609,617.09 μs | 29,055.953 μs | 530,088.745 μs | 275666.6667 | 68666.6667 | - | 4504691.87 KB |
77-
| &#39;Epplus QueryFirst&#39; | 13,770,656.24 μs | 45,909.809 μs | 837,565.827 μs | 174333.3333 | 88833.3333 | 4333.3333 | 3700587.76 KB |
78-
| &#39;Epplus Query&#39; | 19,257,306.83 μs | 63,117.956 μs | 1,151,506.486 μs | 452333.3333 | 90500.0000 | 5333.3333 | 8223933.16 KB |
79-
| &#39;ClosedXml Query&#39; | 31,070,263.83 μs | 342,973.671 μs | 6,257,116.502 μs | 401666.6667 | 104166.6667 | 3333.3333 | 6822559.68 KB |
80-
| &#39;ClosedXml QueryFirst&#39; | 31,141,877.48 μs | 21,006.538 μs | 383,237.459 μs | 402166.6667 | 104833.3333 | 3833.3333 | 6738357.8 KB |
81-
| &#39;OpenXmlSDK QueryFirst&#39; | 31,750,686.63 μs | 263,328.569 μs | 4,804,093.357 μs | 374666.6667 | 374500.0000 | 3166.6667 | 6069266.96 KB |
82-
| &#39;OpenXmlSDK Query&#39; | 32,919,119.46 μs | 411,395.682 μs | 7,505,388.691 μs | 374666.6667 | 374500.0000 | 3166.6667 | 6078467.83 KB |
83106

107+
// Directly from a IDataReader
84108
85-
#### Export/Create Excel
109+
using var connection = yourConnectionProvider.GetConnection();
110+
connection.Open();
86111

87-
Logic: create a total of 10,000,000 "HelloWorld" cells Excel document
112+
using var cmd = connection.CreateCommand();
113+
cmd.CommandText = """
114+
SELECT 'MiniExcel' AS "Name", 1 AS "Value"
115+
UNION ALL
116+
SELECT 'Github', 2
117+
""";
88118

89-
| Method | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated |
90-
|----------------------------------------------|---------:|---------:|---------:|------------:|------------:|----------:|----------:|
91-
| &#39;MiniExcel Create Xlsx&#39; | 4.427 s | 0.0056 s | 0.1023 s | 251666.6667 | 1833.3333 | 1666.6667 | 3.92 GB |
92-
| &#39;OpenXmlSdk Create Xlsx by DOM mode&#39; | 22.729 s | 0.1226 s | 2.2374 s | 307000.0000 | 306833.3333 | 3833.3333 | 6.22 GB |
93-
| &#39;ClosedXml Create Xlsx&#39; | 22.851 s | 0.0190 s | 0.3473 s | 195500.0000 | 54500.0000 | 4166.6667 | 4.48 GB |
94-
| &#39;Epplus Create Xlsx&#39; | 23.027 s | 0.0088 s | 0.1596 s | 89000.0000 | 17500.0000 | 6000.0000 | 2.51 GB |
119+
using var reader = cmd.ExecuteReader();
120+
MiniExcel.SaveAs(yourPath, reader);
95121

96-
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).
97122

123+
// From a DataTable
98124
99-
### Documents
125+
var table = new DataTable();
126+
table.Columns.Add("Name", typeof(string));
127+
table.Columns.Add("Value", typeof(int));
128+
table.Rows.Add("MiniExcel", 1);
129+
table.Rows.Add("Github", 2);
100130

101-
https://github.com/mini-software/MiniExcel
131+
MiniExcel.SaveAs(path, table);
132+
```

src/MiniExcel/Csv/CsvWriter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@ private async Task<int> WriteValuesAsync(StreamWriter writer, object values, str
104104
cancellationToken.ThrowIfCancellationRequested();
105105

106106
IMiniExcelWriteAdapter writeAdapter = null;
107-
#if NETSTANDARD2_0_OR_GREATER || NET
107+
#if !NET45
108108
IAsyncMiniExcelWriteAdapter asyncWriteAdapter = null;
109109
#endif
110110
try
111111
{
112-
#if NETSTANDARD2_0_OR_GREATER || NET
112+
#if !NET45
113113
if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out asyncWriteAdapter))
114114
{
115115
writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration);
@@ -156,7 +156,7 @@ private async Task<int> WriteValuesAsync(StreamWriter writer, object values, str
156156
rowsWritten++;
157157
}
158158
}
159-
#if NETSTANDARD2_0_OR_GREATER || NET
159+
#if !NET45
160160
else
161161
{
162162
await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken))
@@ -182,7 +182,7 @@ private async Task<int> WriteValuesAsync(StreamWriter writer, object values, str
182182
}
183183
finally
184184
{
185-
#if NETSTANDARD2_0_OR_GREATER || NET
185+
#if !NET45
186186
if (asyncWriteAdapter is IAsyncDisposable asyncDisposable)
187187
{
188188
await asyncDisposable.DisposeAsync().ConfigureAwait(false);

src/MiniExcel/MiniExcelLibs.csproj

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>net45;netstandard2.0;net8.0;net9.0;net10.0</TargetFrameworks>
4-
<Version>1.44.1</Version>
3+
<TargetFrameworks>net45;net461;netstandard2.0;net8.0;net9.0;net10.0</TargetFrameworks>
4+
<Version>1.45.0</Version>
55
<LangVersion>14</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<RootNamespace>MiniExcelLibs</RootNamespace>
@@ -13,11 +13,7 @@
1313
<Title>MiniExcel</Title>
1414
<Product>MiniExcel</Product>
1515
<PackageTags>excel;xlsx;csv;micro-helper;mini;openxml;helper;</PackageTags>
16-
<Description>Fast, Low-Memory, Easy Excel .NET helper to import/export/template spreadsheet
17-
Github : https://github.com/mini-software/MiniExcel
18-
Gitee : https://gitee.com/dotnetchina/MiniExcel
19-
Issues : https://github.com/mini-software/MiniExcel/issues
20-
Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true</Description>
16+
<Description>Lightweight, fast and simple .NET processing tool for importing, exporting and templating spreadsheets.</Description>
2117
<Authors>Wei Lin, Michele Bastione, PING-HSIU SHIH, Amos(izanhzh), eynarhaji, Mini-Software team</Authors>
2218
<PackageId>MiniExcel</PackageId>
2319
<Copyright>Wei Lin, 2021 onwards</Copyright>
@@ -43,13 +39,15 @@ Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true</De
4339
<None Include="..\..\README-NuGet.md" Link="README.md" Pack="true" PackagePath="README.md" />
4440
</ItemGroup>
4541

46-
<ItemGroup Condition=" '$(TargetFramework)' == 'net45'">
47-
<Reference Include="System.IO.Compression" />
48-
</ItemGroup>
4942
<ItemGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
50-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
43+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300" PrivateAssets="All" />
44+
</ItemGroup>
45+
46+
<ItemGroup Condition="'$(TargetFramework)' == 'net45' OR '$(TargetFramework)' == 'net461'">
47+
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
5148
</ItemGroup>
52-
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
53-
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
49+
50+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'net461'">
51+
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.9" />
5452
</ItemGroup>
5553
</Project>

src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ private async Task<int> WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje
171171
cancellationToken.ThrowIfCancellationRequested();
172172

173173
IMiniExcelWriteAdapter writeAdapter = null;
174-
#if NETSTANDARD2_0_OR_GREATER || NET
174+
#if !NET45
175175
IAsyncMiniExcelWriteAdapter asyncWriteAdapter = null;
176176
#endif
177177
try
178178
{
179-
#if NETSTANDARD2_0_OR_GREATER || NET
179+
#if !NET45
180180
if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out asyncWriteAdapter))
181181
{
182182
writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration);
@@ -261,7 +261,7 @@ await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Val
261261
await writer.WriteAsync(WorksheetXml.EndRow);
262262
}
263263
}
264-
#if NETSTANDARD2_0_OR_GREATER || NET
264+
#if !NET45
265265
else
266266
{
267267
await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken))
@@ -309,7 +309,7 @@ await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosit
309309
}
310310
finally
311311
{
312-
#if NETSTANDARD2_0_OR_GREATER || NET
312+
#if !NET45
313313
if (asyncWriteAdapter is IAsyncDisposable asyncDisposable)
314314
{
315315
await asyncDisposable.DisposeAsync().ConfigureAwait(false);

0 commit comments

Comments
 (0)