MiniExcel/README.md

1555 lines
44 KiB
Markdown
Raw Normal View History

2021-05-11 12:25:36 +08:00
<div align="center">
2022-03-14 12:07:32 +08:00
<p><a href="https://www.nuget.org/packages/MiniExcel"><img src="https://img.shields.io/nuget/v/MiniExcel.svg" alt="NuGet"></a> <a href="https://www.nuget.org/packages/MiniExcel"><img src="https://img.shields.io/nuget/dt/MiniExcel.svg" alt=""></a>
<a href="https://ci.appveyor.com/project/shps951023/miniexcel/branch/master"><img src="https://ci.appveyor.com/api/projects/status/b2vustrwsuqx45f4/branch/master?svg=true" alt="Build status"></a>
<a href="https://gitee.com/dotnetchina/MiniExcel"><img src="https://gitee.com/dotnetchina/MiniExcel/badge/star.svg" alt="star"></a> <a href="https://github.com/shps951023/MiniExcel" rel="nofollow"><img src="https://img.shields.io/github/stars/shps951023/MiniExcel?logo=github" alt="GitHub stars"></a>
2021-07-19 13:22:07 +08:00
<a href="https://www.nuget.org/packages/MiniExcel"><img src="https://img.shields.io/badge/.NET-%3E%3D%204.5-red.svg" alt="version"></a>
2022-03-14 12:07:32 +08:00
</p>
2021-05-11 12:25:36 +08:00
</div>
2022-03-14 12:07:32 +08:00
---
2021-07-19 13:22:07 +08:00
2021-05-11 12:25:36 +08:00
<div align="center">
2022-03-14 12:07:32 +08:00
<p><strong><a href="README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-Hant.md">繁體中文</a></strong></p>
2021-05-11 12:25:36 +08:00
</div>
2021-06-25 21:37:04 +08:00
---
2021-05-11 12:25:36 +08:00
<div align="center">
2022-01-28 05:32:36 +08:00
Your <a href="https://github.com/shps951023/MiniExcel">Star</a> and <a href="https://miniexcel.github.io">Donate</a> can make MiniExcel better
2021-05-11 12:25:36 +08:00
</div>
2021-03-29 19:16:56 +08:00
---
2021-03-30 10:14:42 +08:00
### Introduction
MiniExcel is simple and efficient to avoid OOM's .NET processing Excel tool.
2021-03-25 11:59:05 +08:00
2021-03-30 10:14:42 +08:00
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).
2021-03-25 11:59:05 +08:00
2021-03-31 11:37:05 +08:00
![image](https://user-images.githubusercontent.com/12729184/113086657-ab8bd000-9214-11eb-9563-c970ac1ee35e.png)
2021-03-03 13:19:27 +08:00
### Features
2021-03-31 11:37:05 +08:00
- Low memory consumption, avoid OOM (out of memory) and full GC
2021-03-30 10:14:42 +08:00
- Support `real-time` operation of each row of data
- Support 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 150KB
2021-04-14 16:35:28 +08:00
- Easy API style to read/write/fill excel
2021-04-14 14:37:55 +08:00
### Get Started
2021-07-10 02:36:35 +08:00
- [Import/Query Excel](#getstart1)
2021-07-10 02:36:35 +08:00
- [Export/Create Excel](#getstart2)
2021-07-10 02:36:35 +08:00
- [Excel Template](#getstart3)
2021-04-14 14:56:52 +08:00
- [Excel Column Name/Index/Ignore Attribute](#getstart4)
2021-04-13 18:40:04 +08:00
- [Examples](#getstart5)
2021-04-14 14:37:55 +08:00
2021-04-09 16:52:53 +08:00
2021-03-03 13:19:27 +08:00
### Installation
You can install the package [from NuGet](https://www.nuget.org/packages/MiniExcel)
2021-03-03 13:56:32 +08:00
### Release Notes
2021-04-23 14:28:24 +08:00
Please Check [Release Notes](docs)
### TODO
2021-03-29 10:29:55 +08:00
Please Check [TODO](https://github.com/shps951023/MiniExcel/projects/1?fullscreen=true)
2021-03-29 10:29:55 +08:00
2021-03-30 10:14:42 +08:00
### Performance
2021-07-10 02:36:35 +08:00
Benchmarks logic can be found in [MiniExcel.Benchmarks](benchmarks/MiniExcel.Benchmarks/Program.cs) , and test cli
2021-03-30 10:14:42 +08:00
2021-07-10 02:36:35 +08:00
```bash
2021-03-30 10:14:42 +08:00
dotnet run -p .\benchmarks\MiniExcel.Benchmarks\ -c Release -f netcoreapp3.1 -- -f * --join
```
2021-07-10 02:36:35 +08:00
Output from the latest run is :
2021-03-30 10:14:42 +08:00
2021-07-10 02:36:35 +08:00
```bash
2021-03-30 10:14:42 +08:00
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
Job-ZYYABG : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
IterationCount=3 LaunchCount=3 WarmupCount=3
```
2021-07-19 13:22:07 +08:00
Benchmark History : [Link](https://github.com/shps951023/MiniExcel/issues/276)
#### Import/Query Excel
2021-07-13 08:29:47 +08:00
Logic : [**Test1,000,000x10.xlsx**](benchmarks/MiniExcel.Benchmarks/Test1%2C000%2C000x10.xlsx) as performance test basic file, 1,000,000 rows * 10 columns "HelloWorld" cells, 23 MB file size
2021-07-10 02:36:35 +08:00
| Library | Method | Max Memory Usage | Mean |
| ---------------------------- | -------------: | ---------------: | ---------------: |
| MiniExcel | 'MiniExcel QueryFirst' | 0.109 MB | 0.0007264 sec |
| ExcelDataReader | 'ExcelDataReader QueryFirst' | 15.24 MB | 10.66421 sec |
| MiniExcel | 'MiniExcel Query' | 17.3 MB | 14.17933 sec |
| ExcelDataReader | 'ExcelDataReader Query' | 17.3 MB | 22.56508 sec |
| Epplus | 'Epplus QueryFirst' | 1,452 MB | 18.19801 sec |
| Epplus | 'Epplus Query' | 1,451 MB | 23.64747 sec |
| OpenXmlSDK | 'OpenXmlSDK Query' | 1,412 MB | 52.00327 sec |
| OpenXmlSDK | 'OpenXmlSDK QueryFirst' | 1,413 MB | 52.34865 sec |
| ClosedXml | 'ClosedXml QueryFirst' | 2,158 MB | 66.18897 sec |
| ClosedXml | 'ClosedXml Query' | 2,184 MB | 191.43412 sec |
#### Export/Create Excel
Logic : create a total of 10,000,000 "HelloWorld" excel
| Library | Method | Max Memory Usage | Mean |
| ------------------------ | -------------: | ---------------: | -----------: |
| MiniExcel | 'MiniExcel Create Xlsx' | 15 MB | 11.53181 sec |
| Epplus | 'Epplus Create Xlsx' | 1,204 MB | 22.50971 sec |
| OpenXmlSdk | 'OpenXmlSdk Create Xlsx' | 2,621 MB | 42.47399 sec |
| ClosedXml | 'ClosedXml Create Xlsx' | 7,141 MB | 140.93992 sec |
2021-03-30 21:37:12 +08:00
### Excel Query/Import <a name="getstart1"></a>
2021-03-30 10:14:42 +08:00
2021-04-14 14:37:55 +08:00
#### 1. Execute a query and map the results to a strongly typed IEnumerable [[Try it]](https://dotnetfiddle.net/w5WD1J)
2021-03-29 09:58:36 +08:00
Recommand to use Stream.Query because of better efficiency.
2021-04-29 17:25:43 +08:00
```csharp
public class UserAccount
{
public Guid ID { get; set; }
public string Name { get; set; }
public DateTime BoD { get; set; }
public int Age { get; set; }
public bool VIP { get; set; }
public decimal Points { get; set; }
}
2021-03-29 09:58:36 +08:00
var rows = MiniExcel.Query<UserAccount>(path);
// or
using (var stream = File.OpenRead(path))
var rows = stream.Query<UserAccount>();
```
![image](https://user-images.githubusercontent.com/12729184/111107423-c8c46b80-8591-11eb-982f-c97a2dafb379.png)
2021-04-14 14:37:55 +08:00
#### 2. Execute a query and map it to a list of dynamic objects without using head [[Try it]](https://dotnetfiddle.net/w5WD1J)
2021-03-16 20:39:49 +08:00
* dynamic key is `A.B.C.D..`
2021-03-30 10:14:42 +08:00
| MiniExcel | 1 |
| -------- | -------- |
| Github | 2 |
2021-04-29 17:25:43 +08:00
```csharp
2021-03-29 09:58:36 +08:00
var rows = MiniExcel.Query(path).ToList();
// or
using (var stream = File.OpenRead(path))
{
var rows = stream.Query().ToList();
Assert.Equal("MiniExcel", rows[0].A);
Assert.Equal(1, rows[0].B);
Assert.Equal("Github", rows[1].A);
Assert.Equal(2, rows[1].B);
}
```
2021-04-14 14:37:55 +08:00
#### 3. Execute a query with first header row [[Try it]](https://dotnetfiddle.net/w5WD1J)
2021-03-29 10:29:55 +08:00
note : same column name use last right one
Input Excel :
2021-03-30 10:14:42 +08:00
| Column1 | Column2 |
| -------- | -------- |
| MiniExcel | 1 |
| Github | 2 |
2021-04-29 17:25:43 +08:00
```csharp
2021-03-29 09:58:36 +08:00
var rows = MiniExcel.Query(useHeaderRow:true).ToList();
// or
using (var stream = File.OpenRead(path))
{
var rows = stream.Query(useHeaderRow:true).ToList();
Assert.Equal("MiniExcel", rows[0].Column1);
Assert.Equal(1, rows[0].Column2);
Assert.Equal("Github", rows[1].Column1);
Assert.Equal(2, rows[1].Column2);
}
```
2021-04-14 14:37:55 +08:00
#### 4. Query Support LINQ Extension First/Take/Skip ...etc
2021-03-21 18:26:00 +08:00
2021-03-29 09:58:36 +08:00
Query First
2021-04-29 17:25:43 +08:00
```csharp
2021-03-29 09:58:36 +08:00
var row = MiniExcel.Query(path).First();
Assert.Equal("HelloWorld", row.A);
// or
2021-03-21 18:26:00 +08:00
using (var stream = File.OpenRead(path))
2021-03-29 09:58:36 +08:00
{
var row = stream.Query().First();
Assert.Equal("HelloWorld", row.A);
}
2021-03-21 18:26:00 +08:00
```
2021-03-29 09:58:36 +08:00
Performance between MiniExcel/ExcelDataReader/ClosedXML/EPPlus
2021-03-21 18:26:00 +08:00
![queryfirst](https://user-images.githubusercontent.com/12729184/111072392-6037a900-8515-11eb-9693-5ce2dad1e460.gif)
2021-04-14 14:37:55 +08:00
#### 5. Query by sheet name
2021-04-29 17:25:43 +08:00
```csharp
2021-04-14 14:37:55 +08:00
MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
```
#### 6. Query all sheet name and rows
2021-04-29 17:25:43 +08:00
```csharp
var sheetNames = MiniExcel.GetSheetNames(path);
2021-04-14 14:37:55 +08:00
foreach (var sheetName in sheetNames)
{
var rows = MiniExcel.Query(path, sheetName: sheetName);
}
```
#### 7. Get Columns
2021-04-29 17:25:43 +08:00
```csharp
var columns = MiniExcel.GetColumns(path); // e.g result : ["A","B"...]
2021-04-14 16:35:28 +08:00
var cnt = columns.Count; // get column count
```
#### 8. Dynamic Query cast row to `IDictionary<string,object>`
2021-04-14 14:37:55 +08:00
2021-04-29 17:25:43 +08:00
```csharp
2021-04-14 14:37:55 +08:00
foreach(IDictionary<string,object> row in MiniExcel.Query(path))
{
//..
}
// or
var rows = MiniExcel.Query(path).Cast<IDictionary<string,object>>();
2021-04-14 14:37:55 +08:00
```
2021-03-21 18:26:00 +08:00
#### 9. Query Excel return DataTable
Not recommended, because DataTable will load all data into memory and lose MiniExcel's low memory consumption feature.
```C#
var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);
```
![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png)
#### 10. Specify the cell to start reading data
```csharp
MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
```
![image](https://user-images.githubusercontent.com/12729184/117260316-8593c400-ae81-11eb-9877-c087b7ac2b01.png)
#### 11. Fill Merged Cells
Note: The efficiency is slower compared to `not using merge fill`
Reason: The OpenXml standard puts mergeCells at the bottom of the file, which leads to the need to foreach the sheetxml twice
```csharp
var config = new OpenXmlConfiguration()
{
FillMergedCells = true
};
var rows = MiniExcel.Query(path, configuration: config);
```
![image](https://user-images.githubusercontent.com/12729184/117973630-3527d500-b35f-11eb-95c3-bde255f8114e.png)
support variable length and width multi-row and column filling
![image](https://user-images.githubusercontent.com/12729184/117973820-6d2f1800-b35f-11eb-88d8-555063938108.png)
#### 12. Reading big file by disk-base cache (Disk-Base Cache - SharedString)
If the SharedStrings size exceeds 5 MB, MiniExcel default will use local disk cache, e.g, [10x100000.xlsx](https://github.com/MiniExcel/MiniExcel/files/8403819/NotDuplicateSharedStrings_10x100000.xlsx)(one million rows data), when disable disk cache the maximum memory usage is 195MB, but able disk cache only needs 65MB. Note, this optimization needs some efficiency cost, so this case will increase reading time from 7.4 seconds to 27.2 seconds, If you don't need it that you can disable disk cache with the following code:
```csharp
var config = new OpenXmlConfiguration { EnableSharedStringCache = false };
MiniExcel.Query(path,configuration: config)
```
You can use `SharedStringCacheSize ` to change the sharedString file size beyond the specified size for disk caching
```csharp
var config = new OpenXmlConfiguration { SharedStringCacheSize=500*1024*1024 };
MiniExcel.Query(path, configuration: config);
```
![image](https://user-images.githubusercontent.com/12729184/161411851-1c3f72a7-33b3-4944-84dc-ffc1d16747dd.png)
![image](https://user-images.githubusercontent.com/12729184/161411825-17f53ec7-bef4-4b16-b234-e24799ea41b0.png)
### Create/Export Excel <a name="getstart2"></a>
2021-03-29 09:58:36 +08:00
2021-03-30 10:14:42 +08:00
1. Must be a non-abstract type with a public parameterless constructor .
2. MiniExcel support parameter IEnumerable Deferred Execution, If you want to use least memory, please do not call methods such as ToList
e.g : ToList or not memory usage
![image](https://user-images.githubusercontent.com/12729184/112587389-752b0b00-8e38-11eb-8a52-cfb76c57e5eb.png)
2021-04-14 14:37:55 +08:00
#### 1. Anonymous or strongly type [[Try it]](https://dotnetfiddle.net/w5WD1J)
2021-04-29 17:25:43 +08:00
```csharp
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
2021-03-14 20:56:10 +08:00
MiniExcel.SaveAs(path, new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
});
```
2021-06-09 09:09:18 +08:00
#### 2. `IEnumerable<IDictionary<string, object>>`
```csharp
var values = new List<Dictionary<string, object>>()
{
new Dictionary<string,object>{{ "Column1", "MiniExcel" }, { "Column2", 1 } },
new Dictionary<string,object>{{ "Column1", "Github" }, { "Column2", 2 } }
};
MiniExcel.SaveAs(path, values);
```
Create File Result :
| Column1 | Column2 |
| -------- | -------- |
| MiniExcel | 1 |
| Github | 2 |
#### 3. IDataReader
- `Recommended`, it can avoid to load all data into memory
```csharp
MiniExcel.SaveAs(path, reader);
```
![image](https://user-images.githubusercontent.com/12729184/121275378-149a5e80-c8bc-11eb-85fe-5453552134f0.png)
DataReader export multiple sheets (recommand by Dapper ExecuteReader)
```csharp
using (var cnn = Connection)
{
cnn.Open();
var sheets = new Dictionary<string,object>();
sheets.Add("sheet1", cnn.ExecuteReader("select 1 id"));
sheets.Add("sheet2", cnn.ExecuteReader("select 2 id"));
MiniExcel.SaveAs("Demo.xlsx", sheets);
}
```
2021-06-09 09:09:18 +08:00
#### 4. Datatable
- `Not recommended`, it will load all data into memory
2021-04-14 14:37:55 +08:00
2021-04-23 14:28:24 +08:00
- DataTable use Caption for column name first, then use columname
2021-04-29 17:25:43 +08:00
```csharp
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var table = new DataTable();
{
table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(decimal));
table.Rows.Add("MiniExcel", 1);
table.Rows.Add("Github", 2);
}
2021-03-14 20:56:10 +08:00
MiniExcel.SaveAs(path, table);
```
#### 5. Dapper Query
2021-06-09 09:09:18 +08:00
- `Not recommended`, it will load all data into memory, please use `ExecuteReader`
2021-04-14 14:37:55 +08:00
2021-04-29 17:25:43 +08:00
```csharp
using (var connection = GetConnection(connectionString))
{
var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2");
MiniExcel.SaveAs(path, rows);
}
```
2021-04-14 14:37:55 +08:00
2021-06-09 09:09:18 +08:00
#### 6. SaveAs to MemoryStream [[Try it]](https://dotnetfiddle.net/JOen0e)
2021-05-08 18:18:25 +08:00
```csharp
using (var stream = new MemoryStream()) //support FileStream,MemoryStream ect.
{
stream.SaveAs(values);
}
```
e.g : api of export excel
```csharp
public IActionResult DownloadExcel()
{
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};
var memoryStream = new MemoryStream();
memoryStream.SaveAs(values);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
```
#### 7. Create Multiple Sheets
```csharp
2021-05-19 10:23:50 +08:00
// 1. Dictionary<string,object>
var users = new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } };
var department = new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } };
var sheets = new Dictionary<string, object>
{
["users"] = users,
["department"] = department
};
MiniExcel.SaveAs(path, sheets);
2021-05-19 10:23:50 +08:00
// 2. DataSet
var sheets = new DataSet();
sheets.Add(UsersDataTable);
sheets.Add(DepartmentDataTable);
//..
MiniExcel.SaveAs(path, sheets);
```
![image](https://user-images.githubusercontent.com/12729184/118130875-6e7c4580-b430-11eb-9b82-22f02716bd63.png)
#### 8. TableStyles Options
Default style
![image](https://user-images.githubusercontent.com/12729184/138234373-cfa97109-b71f-4711-b7f5-0eaaa4a0a3a6.png)
Without style configuration
```csharp
var config = new OpenXmlConfiguration()
{
TableStyles = TableStyles.None
};
MiniExcel.SaveAs(path, value,configuration:config);
```
![image](https://user-images.githubusercontent.com/12729184/118784917-f3e57700-b8c2-11eb-8718-8d955b1bc197.png)
#### 9. AutoFilter
Since v0.19.0 `OpenXmlConfiguration.AutoFilter` can en/unable AutoFilter , default value is `true`, and setting AutoFilter way:
```csharp
MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration() { AutoFilter = false });
```
2022-01-21 12:01:22 +08:00
#### 10. Create Image
```csharp
var value = new[] {
new { Name="github",Image=File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png"))},
new { Name="google",Image=File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png"))},
new { Name="microsoft",Image=File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png"))},
new { Name="reddit",Image=File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png"))},
new { Name="statck_overflow",Image=File.ReadAllBytes(PathHelper.GetFile("images/statck_overflow_logo.png"))},
};
MiniExcel.SaveAs(path, value);
```
![image](https://user-images.githubusercontent.com/12729184/150462383-ad9931b3-ed8d-4221-a1d6-66f799743433.png)
#### 11. Byte Array File Export
2022-01-21 12:01:22 +08:00
Since 1.22.0, when value type is `byte[]` then system will save file path at cell by default, and when import system can be converted to `byte[]`. And if you don't want to use it, you can set `OpenXmlConfiguration.EnableConvertByteArray` to `false`, it can improve the system efficiency.
![image](https://user-images.githubusercontent.com/12729184/153702334-c3b834f4-6ae4-4ddf-bd4e-e5005d5d8c6a.png)
2022-01-21 12:01:22 +08:00
2021-04-14 14:56:52 +08:00
### Fill Data To Excel Template <a name="getstart3"></a>
2021-04-13 18:40:04 +08:00
2021-04-19 02:49:16 +08:00
- The declaration is similar to Vue template `{{variable name}}`, or the collection rendering `{{collection name.field name}}`
- Collection rendering support IEnumerable/DataTable/DapperRow
2021-04-13 18:40:04 +08:00
#### 1. Basic Fill
Template:
![image](https://user-images.githubusercontent.com/12729184/114537556-ed8d2b00-9c84-11eb-8303-a69f62c41e5b.png)
Result:
![image](https://user-images.githubusercontent.com/12729184/114537490-d8180100-9c84-11eb-8c69-db58692f3a85.png)
Code:
2021-04-29 17:25:43 +08:00
```csharp
2021-04-13 18:40:04 +08:00
// 1. By POCO
var value = new
{
Name = "Jack",
CreateDate = new DateTime(2021, 01, 01),
VIP = true,
Points = 123
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
// 2. By Dictionary
var value = new Dictionary<string, object>()
{
["Name"] = "Jack",
["CreateDate"] = new DateTime(2021, 01, 01),
["VIP"] = true,
["Points"] = 123
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
```
#### 2. IEnumerable Data Fill
2021-04-13 18:40:04 +08:00
> Note1: Use the first IEnumerable of the same column as the basis for filling list
2021-04-13 18:40:04 +08:00
Template:
![image](https://user-images.githubusercontent.com/12729184/114564652-14f2f080-9ca3-11eb-831f-09e3fedbc5fc.png)
2021-03-14 13:26:40 +08:00
Result:
![image](https://user-images.githubusercontent.com/12729184/114564204-b2015980-9ca2-11eb-900d-e21249f93f7c.png)
Code:
2021-04-29 17:25:43 +08:00
```csharp
//1. By POCO
var value = new
2021-03-14 13:26:40 +08:00
{
employees = new[] {
new {name="Jack",department="HR"},
new {name="Lisa",department="HR"},
new {name="John",department="HR"},
new {name="Mike",department="IT"},
new {name="Neo",department="IT"},
new {name="Loan",department="IT"}
}
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
//2. By Dictionary
var value = new Dictionary<string, object>()
{
["employees"] = new[] {
new {name="Jack",department="HR"},
new {name="Lisa",department="HR"},
new {name="John",department="HR"},
new {name="Mike",department="IT"},
new {name="Neo",department="IT"},
new {name="Loan",department="IT"}
}
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
2021-03-14 13:26:40 +08:00
```
2021-03-14 22:41:21 +08:00
#### 3. Complex Data Fill
> Note: Support multi-sheets and using same varible
Template:
![image](https://user-images.githubusercontent.com/12729184/114565255-acf0da00-9ca3-11eb-8a7f-8131b2265ae8.png)
Result:
![image](https://user-images.githubusercontent.com/12729184/114565329-bf6b1380-9ca3-11eb-85e3-3969e8bf6378.png)
2021-04-29 17:25:43 +08:00
```csharp
// 1. By POCO
var value = new
{
title = "FooCompany",
managers = new[] {
new {name="Jack",department="HR"},
new {name="Loan",department="IT"}
},
employees = new[] {
new {name="Wade",department="HR"},
new {name="Felix",department="HR"},
new {name="Eric",department="IT"},
new {name="Keaton",department="IT"}
}
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
// 2. By Dictionary
var value = new Dictionary<string, object>()
{
["title"] = "FooCompany",
["managers"] = new[] {
new {name="Jack",department="HR"},
new {name="Loan",department="IT"}
},
["employees"] = new[] {
new {name="Wade",department="HR"},
new {name="Felix",department="HR"},
new {name="Eric",department="IT"},
new {name="Keaton",department="IT"}
}
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
```
#### 4. Fill Big Data Performance
> NOTE: Using IEnumerable deferred execution not ToList can save max memory usage in MiniExcel
![image](https://user-images.githubusercontent.com/12729184/114577091-5046ec80-9cae-11eb-924b-087c7becf8da.png)
2021-04-29 17:25:43 +08:00
#### 5. Cell value auto mapping type
2021-04-29 17:25:43 +08:00
Template
![image](https://user-images.githubusercontent.com/12729184/114802504-64830a80-9dd0-11eb-8d56-8e8c401b3ace.png)
Result
![image](https://user-images.githubusercontent.com/12729184/114802419-43221e80-9dd0-11eb-9ffe-a2ce34fe7076.png)
Class
```csharp
public class Poco
{
public string @string { get; set; }
public int? @int { get; set; }
public decimal? @decimal { get; set; }
public double? @double { get; set; }
public DateTime? datetime { get; set; }
public bool? @bool { get; set; }
public Guid? Guid { get; set; }
}
```
Code
```csharp
var poco = new TestIEnumerableTypePoco { @string = "string", @int = 123, @decimal = decimal.Parse("123.45"), @double = (double)123.33, @datetime = new DateTime(2021, 4, 1), @bool = true, @Guid = Guid.NewGuid() };
var value = new
{
Ts = new[] {
poco,
new TestIEnumerableTypePoco{},
null,
poco
}
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
```
#### 6. Example : List Github Projects
Template
![image](https://user-images.githubusercontent.com/12729184/115068623-12073280-9f25-11eb-9124-f4b3efcdb2a7.png)
Result
![image](https://user-images.githubusercontent.com/12729184/115068639-1a5f6d80-9f25-11eb-9f45-27c434d19a78.png)
Code
```csharp
var projects = new[]
{
new {Name = "MiniExcel",Link="https://github.com/shps951023/MiniExcel",Star=146, CreateTime=new DateTime(2021,03,01)},
new {Name = "HtmlTableHelper",Link="https://github.com/shps951023/HtmlTableHelper",Star=16, CreateTime=new DateTime(2020,02,01)},
new {Name = "PocoClassGenerator",Link="https://github.com/shps951023/PocoClassGenerator",Star=16, CreateTime=new DateTime(2019,03,17)}
};
var value = new
{
User = "ITWeiHan",
Projects = projects,
TotalStar = projects.Sum(s => s.Star)
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
```
#### 7. DataTable as parameter
```csharp
var managers = new DataTable();
{
managers.Columns.Add("name");
managers.Columns.Add("department");
managers.Rows.Add("Jack", "HR");
managers.Rows.Add("Loan", "IT");
}
var value = new Dictionary<string, object>()
{
["title"] = "FooCompany",
["managers"] = managers,
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
```
#### 8. Other
##### 1. Checking template parameter key
Since V1.24.0 , default ignore template missing parameter key and replace it with empty string, `IgnoreTemplateParameterMissing` can control throwing exception or not.
```csharp
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = false,
};
MiniExcel.SaveAsByTemplate(path, templatePath, value, config)
```
![image](https://user-images.githubusercontent.com/12729184/157464332-e316f829-54aa-4c84-a5aa-9aef337b668d.png)
2021-04-14 14:56:52 +08:00
### Excel Column Name/Index/Ignore Attribute <a name="getstart4"></a>
#### 1. Specify the column name, column index, column ignore
Excel Example
![image](https://user-images.githubusercontent.com/12729184/114230869-3e163700-99ac-11eb-9a90-2039d4b4b313.png)
Code
2021-04-29 17:25:43 +08:00
```csharp
public class ExcelAttributeDemo
{
[ExcelColumnName("Column1")]
public string Test1 { get; set; }
[ExcelColumnName("Column2")]
public string Test2 { get; set; }
[ExcelIgnore]
public string Test3 { get; set; }
[ExcelColumnIndex("I")] // system will convert "I" to 8 index
public string Test4 { get; set; }
public string Test5 { get; } //wihout set will ignore
public string Test6 { get; private set; } //un-public set will ignore
[ExcelColumnIndex(3)] // start with 0
public string Test7 { get; set; }
}
var rows = MiniExcel.Query<ExcelAttributeDemo>(path).ToList();
Assert.Equal("Column1", rows[0].Test1);
Assert.Equal("Column2", rows[0].Test2);
Assert.Null(rows[0].Test3);
Assert.Equal("Test7", rows[0].Test4);
Assert.Null(rows[0].Test5);
Assert.Null(rows[0].Test6);
Assert.Equal("Test4", rows[0].Test7);
```
2021-04-14 14:37:55 +08:00
#### 2. Custom Format (ExcelFormatAttribute)
Since V0.21.0 support class which contains `ToString(string content)` method format
Class
```csharp
public class Dto
{
public string Name { get; set; }
[ExcelFormat("MMMM dd, yyyy")]
public DateTime InDate { get; set; }
}
```
Code
```csharp
var value = new Dto[] {
new Issue241Dto{ Name="Jack",InDate=new DateTime(2021,01,04)},
new Issue241Dto{ Name="Henry",InDate=new DateTime(2020,04,05)},
};
MiniExcel.SaveAs(path, value);
```
Result
![image](https://user-images.githubusercontent.com/12729184/118910788-ab2bcd80-b957-11eb-8d42-bfce36621b1b.png)
Query supports custom format conversion
![image](https://user-images.githubusercontent.com/12729184/118911286-87b55280-b958-11eb-9a88-c8ff403d240a.png)
#### 3. Set Column Width(ExcelColumnWidthAttribute)
```csharp
public class Dto
{
[ExcelColumnWidth(20)]
public int ID { get; set; }
[ExcelColumnWidth(15.50)]
public string Name { get; set; }
}
```
#### 4. Multiple column names mapping to the same property.
```csharp
public class Dto
{
[ExcelColumnName(excelColumnName:"EmployeeNo",aliases:new[] { "EmpNo","No" })]
public string Empno { get; set; }
public string Name { get; set; }
}
```
#### 5. System.ComponentModel.DisplayNameAttribute = ExcelColumnName.excelColumnNameAttribute
Since 1.24.0, system supports System.ComponentModel.DisplayNameAttribute = ExcelColumnName.excelColumnNameAttribute
```C#
public class TestIssueI4TXGTDto
{
public int ID { get; set; }
public string Name { get; set; }
[DisplayName("Specification")]
public string Spc { get; set; }
[DisplayName("Unit Price")]
public decimal Up { get; set; }
}
```
2021-04-14 14:56:52 +08:00
### Excel Type Auto Check <a name="getstart5"></a>
2021-04-14 14:37:55 +08:00
- MiniExcel will check whether it is xlsx or csv based on the `file extension` by default, but there may be inaccuracy, please specify it manually.
- Stream cannot be know from which excel, please specify it manually.
2021-04-29 17:25:43 +08:00
```csharp
2021-04-14 14:37:55 +08:00
stream.SaveAs(excelType:ExcelType.CSV);
//or
2021-04-14 14:37:55 +08:00
stream.SaveAs(excelType:ExcelType.XLSX);
//or
stream.Query(excelType:ExcelType.CSV);
//or
stream.Query(excelType:ExcelType.XLSX);
```
2021-06-08 09:35:09 +08:00
### CSV
#### Note
- Default return `string` type, and value will not be converted to numbers or datetime, unless the type is defined by strong typing generic.
#### Custom separator
The default is `,` as the separator, you can modify the `Seperator` property for customization
```csharp
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
Seperator=';'
};
MiniExcel.SaveAs(path, values,configuration: config);
```
#### Custom line break
The default is `\r\n` as the newline character, you can modify the `NewLine` property for customization
```csharp
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
NewLine='\n'
};
MiniExcel.SaveAs(path, values,configuration: config);
```
#### Custom coding
- The default encoding is "Detect Encoding From Byte Order Marks" (detectEncodingFromByteOrderMarks: true)
- f you have custom encoding requirements, please modify the StreamReaderFunc / StreamWriterFunc property
2021-06-08 09:35:09 +08:00
```csharp
// Read
2021-06-08 09:35:09 +08:00
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
StreamReaderFunc = (stream) => new StreamReader(stream,Encoding.GetEncoding("gb2312"))
2021-06-08 09:35:09 +08:00
};
var rows = MiniExcel.Query(path, true,excelType:ExcelType.CSV,configuration: config);
// Write
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
StreamWriterFunc = (stream) => new StreamWriter(stream, Encoding.GetEncoding("gb2312"))
};
MiniExcel.SaveAs(path, value,excelType:ExcelType.CSV, configuration: config);
2021-06-08 09:35:09 +08:00
```
### DataReader
#### 1. GetReader
Since 1.23.0, you can GetDataReader
```csharp
using (var reader = MiniExcel.GetReader(path,true))
{
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
var value = reader.GetValue(i);
}
}
}
```
2021-06-08 09:35:09 +08:00
### Async
v0.17.0 support Async (thanks isdaniel ( SHIH,BING-SIOU)](https://github.com/isdaniel))
```csharp
public static Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
public static Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null)
public static Task<IEnumerable<dynamic>> QueryAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
public static Task<IEnumerable<T>> QueryAsync<T>(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new()
public static Task<IEnumerable<T>> QueryAsync<T>(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new()
public static Task<IEnumerable<IDictionary<string, object>>> QueryAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
public static Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value)
public static Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value)
public static Task SaveAsByTemplateAsync(string path, string templatePath, object value)
public static Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value)
public static Task<DataTable> QueryAsDataTableAsync(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
```
### Others
#### 1. Enum
Be sure excel & property name same, system will auto mapping (case insensitive)
![image](https://user-images.githubusercontent.com/12729184/116210595-9784b100-a775-11eb-936f-8e7a8b435961.png)
Since V0.18.0 support Enum Description
```csharp
public class Dto
{
public string Name { get; set; }
public I49RYZUserType UserType { get; set; }
}
public enum Type
{
[Description("General User")]
V1,
[Description("General Administrator")]
V2,
[Description("Super Administrator")]
V3
}
```
![image](https://user-images.githubusercontent.com/12729184/133116630-27cc7161-099a-48b8-9784-cd1e443af3d1.png)
#### 2. Convert CSV to XLSX or Convert XLSX to CSV
```csharp
MiniExcel.ConvertXlsxToCsv(xlsxPath, csvPath);
MiniExcel.ConvertXlsxToCsv(xlsxStream, csvStream);
MiniExcel.ConvertXlsxToCsv(csvPath, xlsxPath);
MiniExcel.ConvertXlsxToCsv(csvStream, xlsxStream);
```
#### 3. Custom CultureInfo
Since 1.22.0, you can custom CultureInfo like below, system default `CultureInfo.InvariantCulture`.
```csharp
var config = new CsvConfiguration()
{
Culture = new CultureInfo("fr-FR"),
};
MiniExcel.SaveAs(path, value, configuration: config);
// or
MiniExcel.Query(path, configuration: config);
```
2021-04-14 14:37:55 +08:00
### Examples:
#### 1. SQLite & Dapper `Large Size File` SQL Insert Avoid OOM
2021-03-14 22:40:14 +08:00
2021-03-29 10:29:55 +08:00
note : please don't call ToList/ToArray methods after Query, it'll load all data into memory
2021-04-29 17:25:43 +08:00
```csharp
2021-03-14 22:40:14 +08:00
using (var connection = new SQLiteConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
using (var stream = File.OpenRead(path))
{
var rows = stream.Query();
foreach (var row in rows)
connection.Execute("insert into T (A,B) values (@A,@B)", new { row.A, row.B }, transaction: transaction);
transaction.Commit();
}
}
```
2021-03-14 22:41:21 +08:00
performance:
![image](https://user-images.githubusercontent.com/12729184/111072579-2dda7b80-8516-11eb-9843-c01a1edc88ec.png)
2021-04-26 16:34:08 +08:00
#### 2. ASP.NET Core 3.1 or MVC 5 Download/Upload Excel Xlsx API Demo [Try it](tests/MiniExcel.Tests.AspNetCore)
2021-04-29 17:25:43 +08:00
```csharp
2021-04-26 16:34:08 +08:00
public class ApiController : Controller
{
public IActionResult Index()
{
return new ContentResult
{
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
Content = @"<html><body>
2021-04-29 14:16:33 +08:00
<a href='api/DownloadExcel'>DownloadExcel</a><br>
<a href='api/DownloadExcelFromTemplatePath'>DownloadExcelFromTemplatePath</a><br>
<a href='api/DownloadExcelFromTemplateBytes'>DownloadExcelFromTemplateBytes</a><br>
2021-04-26 16:34:08 +08:00
<p>Upload Excel</p>
<form method='post' enctype='multipart/form-data' action='/api/uploadexcel'>
<input type='file' name='excel'> <br>
<input type='submit' >
</form>
</body></html>"
};
}
public IActionResult DownloadExcel()
{
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};
var memoryStream = new MemoryStream();
memoryStream.SaveAs(values);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
public IActionResult DownloadExcelFromTemplatePath()
{
string templatePath = "TestTemplateComplex.xlsx";
Dictionary<string, object> value = new Dictionary<string, object>()
{
["title"] = "FooCompany",
["managers"] = new[] {
new {name="Jack",department="HR"},
new {name="Loan",department="IT"}
},
["employees"] = new[] {
new {name="Wade",department="HR"},
new {name="Felix",department="HR"},
new {name="Eric",department="IT"},
new {name="Keaton",department="IT"}
}
};
MemoryStream memoryStream = new MemoryStream();
memoryStream.SaveAsByTemplate(templatePath, value);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
private static Dictionary<string, Byte[]> TemplateBytesCache = new Dictionary<string, byte[]>();
2021-04-26 16:34:08 +08:00
static ApiController()
{
string templatePath = "TestTemplateComplex.xlsx";
byte[] bytes = System.IO.File.ReadAllBytes(templatePath);
TemplateBytesCache.Add(templatePath, bytes);
}
public IActionResult DownloadExcelFromTemplateBytes()
{
byte[] bytes = TemplateBytesCache["TestTemplateComplex.xlsx"];
Dictionary<string, object> value = new Dictionary<string, object>()
{
["title"] = "FooCompany",
["managers"] = new[] {
new {name="Jack",department="HR"},
new {name="Loan",department="IT"}
},
["employees"] = new[] {
new {name="Wade",department="HR"},
new {name="Felix",department="HR"},
new {name="Eric",department="IT"},
new {name="Keaton",department="IT"}
}
};
MemoryStream memoryStream = new MemoryStream();
memoryStream.SaveAsByTemplate(bytes, value);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
2021-04-26 16:34:08 +08:00
public IActionResult UploadExcel(IFormFile excel)
{
var stream = new MemoryStream();
excel.CopyTo(stream);
foreach (var item in stream.Query(true))
{
// do your logic etc.
}
return Ok("File uploaded successfully");
}
}
```
2021-04-14 16:35:28 +08:00
#### 3. Paging Query
2021-04-29 17:25:43 +08:00
```csharp
2021-04-14 16:35:28 +08:00
void Main()
{
var rows = MiniExcel.Query(path);
Console.WriteLine("==== No.1 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:1));
Console.WriteLine("==== No.50 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:50));
Console.WriteLine("==== No.5000 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:5000));
}
public static IEnumerable<T> Page<T>(IEnumerable<T> en, int pageSize, int page)
{
return en.Skip(page * pageSize).Take(pageSize);
}
```
2021-03-24 17:11:46 +08:00
2021-04-14 16:35:28 +08:00
![20210419](https://user-images.githubusercontent.com/12729184/114679083-6ef4c400-9d3e-11eb-9f78-a86daa45fe46.gif)
2021-05-08 18:18:25 +08:00
#### 4. WebForm export Excel by memorystream
```csharp
var fileName = "Demo.xlsx";
var sheetName = "Sheet1";
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
response.AddHeader("Content-Disposition", $"attachment;filename=\"{fileName}\"");
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};
var memoryStream = new MemoryStream();
memoryStream.SaveAs(values, sheetName: sheetName);
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(Response.OutputStream);
response.End();
```
2021-06-02 16:56:33 +08:00
#### 5. Dynamic i18n multi-language and role authority management
Like the example, create a method to handle i18n and permission management, and use `yield return to return IEnumerable<Dictionary<string, object>>` to achieve dynamic and low-memory processing effects
```csharp
void Main()
{
var value = new Order[] {
new Order(){OrderNo = "SO01",CustomerID="C001",ProductID="P001",Qty=100,Amt=500},
new Order(){OrderNo = "SO02",CustomerID="C002",ProductID="P002",Qty=300,Amt=400},
};
Console.WriteLine("en-Us and Sales role");
{
var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx";
var lang = "en-US";
var role = "Sales";
MiniExcel.SaveAs(path, GetOrders(lang, role, value));
MiniExcel.Query(path, true).Dump();
}
Console.WriteLine("zh-CN and PMC role");
{
var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx";
var lang = "zh-CN";
var role = "PMC";
MiniExcel.SaveAs(path, GetOrders(lang, role, value));
MiniExcel.Query(path, true).Dump();
}
}
private IEnumerable<Dictionary<string, object>> GetOrders(string lang, string role, Order[] orders)
{
foreach (var order in orders)
{
var newOrder = new Dictionary<string, object>();
if (lang == "zh-CN")
{
2021-05-21 12:06:15 +08:00
newOrder.Add("客户编号", order.CustomerID);
newOrder.Add("订单编号", order.OrderNo);
newOrder.Add("产品编号", order.ProductID);
newOrder.Add("数量", order.Qty);
if (role == "Sales")
2021-05-21 12:06:15 +08:00
newOrder.Add("价格", order.Amt);
yield return newOrder;
}
else if (lang == "en-US")
{
newOrder.Add("Customer ID", order.CustomerID);
newOrder.Add("Order No", order.OrderNo);
newOrder.Add("Product ID", order.ProductID);
newOrder.Add("Quantity", order.Qty);
if (role == "Sales")
newOrder.Add("Amount", order.Amt);
yield return newOrder;
}
else
{
throw new InvalidDataException($"lang {lang} wrong");
}
}
}
public class Order
{
public string OrderNo { get; set; }
public string CustomerID { get; set; }
public decimal Qty { get; set; }
public string ProductID { get; set; }
public decimal Amt { get; set; }
}
```
![image](https://user-images.githubusercontent.com/12729184/118939964-d24bc480-b982-11eb-88dd-f06655f6121a.png)
2021-04-26 16:34:08 +08:00
### FAQ
2021-04-17 02:34:46 +08:00
#### Q: Excel header title not equal class property name, how to mapping?
A. Please use ExcelColumnName attribute
![image](https://user-images.githubusercontent.com/12729184/116020475-eac50980-a678-11eb-8804-129e87200e5e.png)
2021-04-29 17:25:43 +08:00
#### Q. How to query or export multiple-sheets?
A. `GetSheetNames` method with Query sheetName parameter.
```csharp
var sheets = MiniExcel.GetSheetNames(path);
foreach (var sheet in sheets)
{
Console.WriteLine($"sheet name : {sheet} ");
var rows = MiniExcel.Query(path,useHeaderRow:true,sheetName:sheet);
Console.WriteLine(rows);
}
```
![image](https://user-images.githubusercontent.com/12729184/116199841-2a1f5300-a76a-11eb-90a3-6710561cf6db.png)
#### Q. Whether to use Count will load all data into the memory?
2021-04-29 17:25:43 +08:00
No, the image test has 1 million rows*10 columns of data, the maximum memory usage is <60MB, and it takes 13.65 seconds
![image](https://user-images.githubusercontent.com/12729184/117118518-70586000-adc3-11eb-9ce3-2ba76cf8b5e5.png)
2021-04-29 17:25:43 +08:00
2021-06-02 16:56:33 +08:00
#### Q. How does Query use integer indexs?
2021-06-02 16:56:33 +08:00
The default index of Query is the string Key: A,B,C.... If you want to change to numeric index, please create the following method to convert
2021-06-02 16:56:33 +08:00
```csharp
void Main()
{
var path = @"D:\git\MiniExcel\samples\xlsx\TestTypeMapping.xlsx";
var rows = MiniExcel.Query(path,true);
foreach (var r in ConvertToIntIndexRows(rows))
{
Console.Write($"column 0 : {r[0]} ,column 1 : {r[1]}");
Console.WriteLine();
}
}
private IEnumerable<Dictionary<int, object>> ConvertToIntIndexRows(IEnumerable<object> rows)
{
ICollection<string> keys = null;
var isFirst = true;
foreach (IDictionary<string,object> r in rows)
{
if(isFirst)
{
keys = r.Keys;
isFirst = false;
}
var dic = new Dictionary<int, object>();
var index = 0;
foreach (var key in keys)
dic[index++] = r[key];
yield return dic;
}
}
```
#### Q. No title empty excel is generated when the value is empty when exporting Excel
Because MiniExcel uses a logic similar to JSON.NET to dynamically get type from values to simplify API operations, type cannot be knew without data. You can check [issue #133](https://github.com/shps951023/MiniExcel/issues/133) for understanding.
![image](https://user-images.githubusercontent.com/12729184/122639771-546c0c00-d12e-11eb-800c-498db27889ca.png)
> Strong type & DataTable will generate headers, but Dictionary are still empty Excel
#### Q. How to stop the foreach when blank row?
MiniExcel can be used with `LINQ TakeWhile` to stop foreach iterator.
![Image](https://user-images.githubusercontent.com/12729184/130209137-162621c2-f337-4479-9996-beeac65bc4d4.png)
2021-10-19 16:36:37 +08:00
#### Q. How to remove empty rows?
![image](https://user-images.githubusercontent.com/12729184/137873865-7107d8f5-eb59-42db-903a-44e80589f1b2.png)
IEnumerable :
```csharp
public static IEnumerable<dynamic> QueryWithoutEmptyRow(Stream stream, bool useHeaderRow, string sheetName, ExcelType excelType, string startCell, IConfiguration configuration)
{
var rows = stream.Query(useHeaderRow,sheetName,excelType,startCell,configuration);
foreach (IDictionary<string,object> row in rows)
{
if(row.Keys.Any(key=>row[key]!=null))
yield return row;
}
}
```
DataTable :
```csharp
public static DataTable QueryAsDataTableWithoutEmptyRow(Stream stream, bool useHeaderRow, string sheetName, ExcelType excelType, string startCell, IConfiguration configuration)
{
if (sheetName == null && excelType != ExcelType.CSV) /*Issue #279*/
sheetName = stream.GetSheetNames().First();
var dt = new DataTable(sheetName);
var first = true;
var rows = stream.Query(useHeaderRow,sheetName,excelType,startCell,configuration);
foreach (IDictionary<string, object> row in rows)
{
if (first)
{
foreach (var key in row.Keys)
{
var column = new DataColumn(key, typeof(object)) { Caption = key };
dt.Columns.Add(column);
}
dt.BeginLoadData();
first = false;
}
var newRow = dt.NewRow();
var isNull=true;
foreach (var key in row.Keys)
{
var _v = row[key];
if(_v!=null)
isNull = false;
newRow[key] = _v;
}
if(!isNull)
dt.Rows.Add(newRow);
}
dt.EndLoadData();
return dt;
}
```
#### Q. How SaveAs(path,value) to replace exists file and without throwing "The file ...xlsx already exists error"
Please use Stream class to custom file creating logic, e.g:
```C#
using (var stream = File.Create("Demo.xlsx"))
MiniExcel.SaveAs(stream,value);
```
2021-06-02 16:56:33 +08:00
or, since V1.25.0, SaveAs support overwriteFile parameter for enable/unable overwriting exist file
```csharp
MiniExcel.SaveAs(path, value, overwriteFile: true);
```
2021-06-02 16:56:33 +08:00
### Limitations and caveats
2021-03-29 10:29:55 +08:00
- Not support xls and encrypted file now
2021-05-10 15:14:58 +08:00
- xlsm only support Query
2021-03-14 23:11:54 +08:00
2021-03-24 17:11:46 +08:00
### Reference
2021-03-14 23:11:54 +08:00
[ExcelDataReader](https://github.com/ExcelDataReader/ExcelDataReader) / [ClosedXML](https://github.com/ClosedXML/ClosedXML) / [Dapper](https://github.com/DapperLib/Dapper) / [ExcelNumberFormat](https://github.com/andersnm/ExcelNumberFormat)
2021-04-03 12:51:03 +08:00
### Thanks
#### [Jetbrains](https://www.jetbrains.com/)
![jetbrains-variant-2](https://user-images.githubusercontent.com/12729184/123997015-8456c180-da02-11eb-829a-aec476fe8e94.png)
Thanks for providing a free All product IDE for this project ([License](https://user-images.githubusercontent.com/12729184/123988233-6ab17c00-d9fa-11eb-8739-2a08c6a4a263.png))
2021-04-14 14:37:55 +08:00
### Contributors
2021-04-03 12:51:03 +08:00
![](https://contrib.rocks/image?repo=shps951023/MiniExcel)