English | 简体中文 | 繁體中文 | 日本語 | 한국어 | हिन्दी | ไทย | Français | Deutsch | Español | Italiano | Русский | Português | Nederlands | Polski | العربية | فارسی | Türkçe | Tiếng Việt | Bahasa Indonesia
簡介
MiniExcel 是一款簡單且高效的 .NET Excel 處理工具,專為降低記憶體使用量而設計。
目前,大多數主流框架需要將 Excel 文件中的所有資料載入記憶體以便操作,但這可能會造成記憶體消耗問題。MiniExcel 採用不同的方法:以串流方式逐行處理資料,將原本可能高達數百 MB 的消耗降至僅需數 MB,有效防止記憶體不足(OOM)問題。
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"])
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;
class A1,A2,A3,A4,A5 analysis;
class B1,B2,B3,B4,B5 others;
class C1,C2,C3,C4,C5 miniexcel;
功能特色
- 最小化記憶體消耗,防止記憶體不足(OOM)錯誤並避免完整垃圾回收
- 支援即時、逐列資料操作,在大型資料集上獲得更佳效能
- 支援 LINQ 延遲執行,實現快速且省記憶體的分頁及複雜查詢
- 輕量級,無需安裝 Microsoft Office 或 COM+ 元件,且 DLL 檔案小於 500KB
- 簡單直觀的 API 風格,用於讀取/寫入/填充 Excel
2.0 版預覽
我們正在開發下一代 MiniExcel 版本,將會有全新模組化且聚焦的 API、
核心與 Csv 功能分開的 nuget 套件、完整支援 IAsyncEnumerable 非同步串流查詢,
以及更多即將推出的新功能!這些套件將以預覽版發佈,歡迎大家試用並給予回饋!
開始使用
安裝方式
您可以從 NuGet 安裝套件
發行說明
請參閱發行說明
待辦事項
請檢查 TODO
效能
基準測試的程式碼可在 MiniExcel.Benchmarks 找到。
用於效能測試的檔案是 Test1,000,000x10.xlsx,這是一份 32MB 的文件,包含 1,000,000 行 * 10 列,所有儲存格皆填入字串 "HelloWorld"。
要執行所有基準測試,請使用:
dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
您可以在這裡找到最新版本的基準測試結果。Excel 查詢/匯入
#### 1. 執行查詢並將結果對應到強型別的 IEnumerable [[試試看]](https://dotnetfiddle.net/w5WD1J)
建議使用 Stream.Query,因為效率更佳。
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; }
}var rows = MiniExcel.Query(path);
// or
using (var stream = File.OpenRead(path))
var rows = stream.Query();

#### 2. 執行查詢並將其對應到動態物件清單(不使用 head)[[試試看]](https://dotnetfiddle.net/w5WD1J)
- dynamic key 是
A.B.C.D..
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);
}
#### 3. 執行查詢並以第一列作為標題 [[試試看]](https://dotnetfiddle.net/w5WD1J)注意:相同欄位名稱會使用最右邊的那一個
輸入的 Excel:
| 欄位1 | 欄位2 | |-----------|---------| | MiniExcel | 1 | | Github | 2 |
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);
}
#### 4. 查詢支援 LINQ 擴充方法 First/Take/Skip ...等查詢 First
var row = MiniExcel.Query(path).First();
Assert.Equal("HelloWorld", row.A);// or
using (var stream = File.OpenRead(path))
{
var row = stream.Query().First();
Assert.Equal("HelloWorld", row.A);
}
MiniExcel/ExcelDataReader/ClosedXML/EPPlus 之間的效能比較

#### 5. 依工作表名稱查詢
MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
#### 6. 查詢所有工作表名稱及行數var sheetNames = MiniExcel.GetSheetNames(path);
foreach (var sheetName in sheetNames)
{
var rows = MiniExcel.Query(path, sheetName: sheetName);
}
#### 7. 取得欄位var columns = MiniExcel.GetColumns(path); // e.g result : ["A","B"...]var cnt = columns.Count; // get column count
#### 8. 動態查詢將資料列轉型為 IDictionaryforeach(IDictionary row in MiniExcel.Query(path))
{
//..
}// or
var rows = MiniExcel.Query(path).Cast>();
// or Query specified ranges (capitalized)
// A2 represents the second row of column A, C3 represents the third row of column C
// If you don't want to restrict rows, just don't include numbers
var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3").Cast>();
#### 9. 查詢 Excel 並返回 DataTable
不建議這麼做,因為 DataTable 會將所有資料載入記憶體,這樣會失去 MiniExcel 低記憶體消耗的特性。
``C#
var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);

#### 10. 指定開始讀取資料的儲存格
csharp MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")

#### 11. 填充合併儲存格
注意:相較於「不使用合併填充」效率較慢
原因:OpenXml 標準將 mergeCells 放在檔案底部,導致需要對 sheetxml 進行兩次遍歷
csharp
var config = new OpenXmlConfiguration()
{
FillMergedCells = true
};
var rows = MiniExcel.Query(path, configuration: config);

支援變長與變寬的多列多欄填充

#### 12. 透過磁碟快取讀取大型檔案(磁碟快取 - SharedString)
如果 SharedStrings 的大小超過 5 MB,MiniExcel 預設會使用本地磁碟快取,例如,10x100000.xlsx(一百萬筆資料),當停用磁碟快取時,最大記憶體用量為 195MB,但啟用磁碟快取只需 65MB。注意,此優化會有一些效能成本,因此在這種情況下,讀取時間會從 7.4 秒增加到 27.2 秒。如果你不需要這個功能,可以用下列程式碼關閉磁碟快取:
csharp
var config = new OpenXmlConfiguration { EnableSharedStringCache = false };
MiniExcel.Query(path,configuration: config)
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);您可以使用SharedStringCacheSize來更改 sharedString 檔案大小,超過指定大小時將進行磁碟快取


建立/匯出 Excel
- 必須是具有公有無參數建構子的非抽象型別。
- MiniExcel 支援 IEnumerable 參數的延遲執行,如果您想使用最少的記憶體,請不要呼叫如 ToList 等方法
例如:ToList 或不使用記憶體

#### 1. 匿名型別或強型別 [[試試看]](https://dotnetfiddle.net/w5WD1J)
csharp
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
MiniExcel.SaveAs(path, new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
});
csharp var values = new List#### 2.IEnumerable>
csharp MiniExcel.SaveAs(path, reader);建立檔案結果:建議使用| 欄位1 | 欄位2 | |-----------|---------| | MiniExcel | 1 | | Github | 2 |
#### 3. IDataReader
,可以避免將所有資料載入記憶體

DataReader 匯出多個工作表(建議使用 Dapper ExecuteReader)
csharp
using (var cnn = Connection)
{
cnn.Open();
var sheets = new Dictionarycsharp 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); }不建議使用#### 4. 資料表格
,因為它會將所有資料載入記憶體DataTable 會優先使用 Caption 作為欄位名稱,然後才使用欄位名稱
MiniExcel.SaveAs(path, table);
csharp using (var connection = GetConnection(connectionString)) { var rows = connection.Query( new CommandDefinition( @"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2" , flags: CommandFlags.NoCache) ); // Note: QueryAsync will throw close connection exception MiniExcel.SaveAs(path, rows); }#### 5. Dapper 查詢CommandDefinition + CommandFlags.NoCache感謝 @shaofing #552,請使用
以下程式碼將把所有資料載入記憶體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);
}
#### 6. SaveAs 到 MemoryStream [[試試看]](https://dotnetfiddle.net/JOen0e)
csharp
using (var stream = new MemoryStream()) //support FileStream,MemoryStream ect.
{
stream.SaveAs(values);
}
例如:導出 Excel 的 APIcsharp
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. 建立多個工作表
csharp
// 1. Dictionary// 2. DataSet var sheets = new DataSet(); sheets.Add(UsersDataTable); sheets.Add(DepartmentDataTable); //.. MiniExcel.SaveAs(path, sheets);

#### 8. 表格樣式選項
預設樣式

無樣式設定
csharp
var config = new OpenXmlConfiguration()
{
TableStyles = TableStyles.None
};
MiniExcel.SaveAs(path, value,configuration:config);
csharp MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration() { AutoFilter = false });OpenXmlConfiguration.AutoFilter#### 9. 自動篩選
從 v0.19.0 開始,
可以啟用或停用自動篩選,預設值為true,設定自動篩選的方法如下:
#### 10. 建立映像檔
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);
csharp var mergedFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx");byte[]#### 11. 位元組陣列檔案導出
自 1.22.0 起,當值類型為
時,系統會預設在儲存格中保存檔案路徑,匯入時系統可自動轉換為byte[]。若不想啟用此功能,可將OpenXmlConfiguration.EnableConvertByteArray設為false,這樣可以提升系統效能。byte[]
自 1.22.0 起,當值類型為
時,系統會預設在儲存格中保存檔案路徑,匯入時系統可自動轉換為byte[]。若不想啟用此功能,可將OpenXmlConfiguration.EnableConvertByteArray設為false,這樣可以提升系統效能。xlsx
#### 12. 垂直合併相同儲存格
此功能僅支援
格式,並會在 @merge 和 @endmerge 標記之間將儲存格垂直合併。 你可以使用 @mergelimit 來限制垂直合併儲存格的範圍。
var path = @"../../../../../samples/xlsx/TestMergeWithTag.xlsx";
MiniExcel.MergeSameCells(mergedFilePath, path);
csharp
var memoryStream = new MemoryStream();var path = @"../../../../../samples/xlsx/TestMergeWithTag.xlsx";
memoryStream.MergeSameCells(path);
合併前後的檔案內容:無合併限制:


有合併限制:


#### 13. 跳過 null 值
新增明確選項以針對 null 值寫入空白儲存格:
csharp
DataTable dt = new DataTable();/ ... /
DataRow dr = dt.NewRow();
dr["Name1"] = "Somebody once"; dr["Name2"] = null; dr["Name3"] = "told me.";
dt.Rows.Add(dr);
OpenXmlConfiguration configuration = new OpenXmlConfiguration() { EnableWriteNullValueCell = true // Default value. };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);

xml
先前行為:
csharp
/ ... /OpenXmlConfiguration configuration = new OpenXmlConfiguration() { EnableWriteNullValueCell = false // Default value is true. };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);

xml
適用於 null 和 DBNull 值。#### 14. 凍結窗格
csharp
/ ... /OpenXmlConfiguration configuration = new OpenXmlConfiguration() { FreezeRowCount = 1, // default is 1 FreezeColumnCount = 2 // default is 0 };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);
csharp // 1. By POCO var value = new { Name = "Jack", CreateDate = new DateTime(2021, 01, 01), VIP = true, Points = 123 }; MiniExcel.SaveAsByTemplate(path, templatePath, value);{{變數名稱}}填充資料到 Excel 範本
- 宣告方式類似於 Vue 模板的
,或集合渲染{{集合名稱.欄位名稱}}集合渲染支援 IEnumerable/DataTable/DapperRow #### 1. 基本填充
範本:
結果:
程式碼:
// 2. By Dictionary
var value = new Dictionary#### 2. IEnumerable 資料填充
注意1:使用相同欄位的第一個 IEnumerable 作為填充列表的依據
範本:

結果:

程式碼:
csharp //1. By POCO var value = new { 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#### 3. 複雜資料填充
注意:支援多工作表並使用相同變數
範本:

結果:

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#### 4. 填充大數據效能
注意:在 MiniExcel 中,使用 IEnumerable 延遲執行而非 ToList,可以節省最大記憶體用量

#### 5. 儲存格值自動對應型別
範本

結果

類別
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; } }
程式碼
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. 範例:列出 Github 專案範本

結果

程式碼
csharp
var projects = new[]
{
new {Name = "MiniExcel",Link="https://github.com/mini-software/MiniExcel",Star=146, CreateTime=new DateTime(2021,03,01)},
new {Name = "HtmlTableHelper",Link="https://github.com/mini-software/HtmlTableHelper",Star=16, CreateTime=new DateTime(2020,02,01)},
new {Name = "PocoClassGenerator",Link="https://github.com/mini-software/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. 分組資料填充csharp
var value = new Dictionarycsharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif##### 1. 同時使用@group標籤與@header標籤前
後
##### 2. 僅使用 @group 標籤,未使用 @header 標籤
前
後
##### 3. 未使用 @group 標籤
前
後
#### 8. 在儲存格中使用 If/ElseIf/Else 條件語句
規則:
- 支援 DateTime、Double、Int 型別與 ==、!=、>、>=、<、<= 運算子。
- 支援 String 型別與 ==、!= 運算子。
- 每個語句應該位於新的一行。
- 運算子前後應加一個空格。
- 語句內不應有換行。
- 儲存格必須遵循如下的精確格式。
在此之前
之後

#### 9. 以 DataTable 作為參數
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 Dictionarycsharp var config = new OpenXmlConfiguration() { IgnoreTemplateParameterMissing = false, }; MiniExcel.SaveAsByTemplate(path, templatePath, value, config)#### 10. 公式$##### 1. 範例 在你的公式前加上
,並使用$enumrowstart和$enumrowend來標記可列舉的開始與結束行:$
當模板渲染時,
前綴會被移除,$enumrowstart和$enumrowend會被替換為可列舉的開始與結束行號:$=SUM(C{{$enumrowstart}}:C{{$enumrowend}})
##### 2. 其他範例公式:
| | | |--------------|-------------------------------------------------------------------------------------------| | 總和 |
| | 另類平均 |$=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) / COUNT(C{{$enumrowstart}}:C{{$enumrowend}})| | 範圍 |$=MAX(C{{$enumrowstart}}:C{{$enumrowend}}) - MIN(C{{$enumrowstart}}:C{{$enumrowend}})|IgnoreTemplateParameterMissing#### 11. 其他
##### 1. 檢查模板參數鍵
自 V1.24.0 起,預設會忽略模板中缺失的參數鍵並以空字串取代,
可控制是否拋出異常。

Excel 欄位名稱/索引/忽略屬性
#### 1. 指定欄位名稱、欄位索引、欄位忽略
Excel 範例

程式碼
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
#### 2. 自訂格式(ExcelFormatAttribute)
自 V0.21.0 起支援包含 ToString(string content) 方法格式的類別
類別
csharp public class Dto { public string Name { get; set; }
[ExcelFormat("MMMM dd, yyyy")] public DateTime InDate { get; set; } }
程式碼
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);
結果
查詢支援自訂格式轉換

#### 3. 設定欄寬(ExcelColumnWidthAttribute)
csharp
public class Dto
{
[ExcelColumnWidth(20)]
public int ID { get; set; }
[ExcelColumnWidth(15.50)]
public string Name { get; set; }
}
#### 4. 多個欄位名稱對應到同一個屬性。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
自 1.24.0 版本起,系統支援 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; }
}
#### 6. ExcelColumnAttribute
自 V1.26.0 版本起,多個屬性可以簡化如下:
csharp
public class TestIssueI4ZYUUDto
{
[ExcelColumn(Name = "ID",Index =0)]
public string MyProperty { get; set; }
[ExcelColumn(Name = "CreateDate", Index = 1,Format ="yyyy-MM",Width =100)]
public DateTime MyProperty2 { get; set; }
}
#### 7. DynamicColumnAttribute自從 V1.26.0 起,我們可以動態設定 Column 的屬性
csharp
var config = new OpenXmlConfiguration
{
DynamicColumns = new DynamicExcelColumn[] {
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"},
}
};
var path = PathHelper.GetTempPath();
var value = new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12) ,point = 123.456} };
MiniExcel.SaveAs(path, value, configuration: config);

#### 8. 動態工作表屬性
自從 V1.31.4 版本開始,我們可以動態設定工作表的屬性。我們可以設定工作表名稱和狀態(可見性)。
csharp
var configuration = new OpenXmlConfiguration
{
DynamicSheets = new DynamicExcelSheet[] {
new DynamicExcelSheet("usersSheet") { Name = "Users", State = SheetState.Visible },
new DynamicExcelSheet("departmentSheet") { Name = "Departments", State = SheetState.Hidden }
}
}; 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
var path = PathHelper.GetTempPath(); MiniExcel.SaveAs(path, sheets, configuration: configuration);
我們也可以使用新的屬性 ExcelSheetAttribute:C#
[ExcelSheet(Name = "Departments", State = SheetState.Hidden)]
private class DepartmentDto
{
[ExcelColumn(Name = "ID",Index = 0)]
public string ID { get; set; }
[ExcelColumn(Name = "Name",Index = 1)]
public string Name { get; set; }
}
### 新增、刪除、更新#### 新增
v1.28.0 支援在最後一行後插入 N 行 CSV 資料
csharp
// Origin
{
var value = new[] {
new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)},
new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)},
};
MiniExcel.SaveAs(path, value);
}
// Insert 1 rows after last
{
var value = new { ID=3,Name = "Mike", InDate = new DateTime(2021, 04, 23) };
MiniExcel.Insert(path, value);
}
// Insert N rows after last
{
var value = new[] {
new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)},
new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)},
};
MiniExcel.Insert(path, value);
}

v1.37.0 支援在現有工作簿中插入新的工作表
csharp
// Origin excel
{
var value = new[] {
new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)},
new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)},
};
MiniExcel.SaveAs(path, value, sheetName: "Sheet1");
}
// Insert a new sheet
{
var value = new { ID=3,Name = "Mike", InDate = new DateTime(2021, 04, 23) };
MiniExcel.Insert(path, table, sheetName: "Sheet2");
}
csharp stream.SaveAs(excelType:ExcelType.CSV); //or stream.SaveAs(excelType:ExcelType.XLSX); //or stream.Query(excelType:ExcelType.CSV); //or stream.Query(excelType:ExcelType.XLSX);#### 刪除(等待中)檔案副檔名#### 更新(等待中)
Excel 類型自動檢查
- MiniExcel 會根據
自動判斷是 xlsx 或 csv,但這種方式可能不準確,請手動指定。Stream 無法判斷來源為哪種 excel,請手動指定。
csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { Seperator=';' }; MiniExcel.SaveAs(path, values,configuration: config);stringCSV
#### 注意
- 預設回傳為
類型,且數值不會自動轉換為數字或日期時間,除非透過強型別泛型定義類型。,#### 自訂分隔符
預設分隔符為
,你可以修改Seperator屬性來自訂分隔符
自 V1.30.1 起支援自訂分隔符功能(感謝 @hyzx86)csharp
var config = new CsvConfiguration()
{
SplitFn = (row) => Regex.Split(row, $"\"" target="_blank" rel="noopener noreferrer">\t,$)")
.Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")).ToArray()
};
var rows = MiniExcel.Query(path, configuration: config).ToList();
csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { NewLine='\n' }; MiniExcel.SaveAs(path, values,configuration: config);\r\n#### 自訂換行符
預設為
作為換行字元,您可以修改NewLine屬性來自訂
#### 自訂編碼- 預設編碼為「從位元組順序標記偵測編碼」(detectEncodingFromByteOrderMarks: true)
- 如果您有自訂編碼需求,請修改 StreamReaderFunc / StreamWriterFunc 屬性
csharp
// Read
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
StreamReaderFunc = (stream) => new StreamReader(stream,Encoding.GetEncoding("gb2312"))
};
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);
#### 將空字串讀取為 null預設情況下,空值會對應為 string.Empty。你可以修改這個行為
csharp
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
ReadEmptyStringAsNull = true
};
### DataReader#### 1. GetReader
自 1.23.0 起,您可以使用 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);
}
}
}
非同步
- v0.17.0 支援非同步(感謝 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 Taskcsharp public class Dto { public string Name { get; set; } public I49RYZUserType UserType { get; set; } }- v1.25.0 支援cancellationToken。其他
#### 1. 列舉型別
請確保 excel 與屬性名稱相同,系統會自動對應(不區分大小寫)
自 V0.18.0 起支援列舉型別描述(Enum Description)
public enum Type { [Description("General User")] V1, [Description("General Administrator")] V2, [Description("Super Administrator")] V3 }

自 1.30.0 版本起支援將 Excel Description 轉換為 Enum,感謝 @KaneLeung
#### 2. 將 CSV 轉換為 XLSX 或將 XLSX 轉換為 CSV
csharp
MiniExcel.ConvertXlsxToCsv(xlsxPath, csvPath);
MiniExcel.ConvertXlsxToCsv(xlsxStream, csvStream);
MiniExcel.ConvertCsvToXlsx(csvPath, xlsxPath);
MiniExcel.ConvertCsvToXlsx(csvStream, xlsxStream);
#### 3. 自訂 CultureInfo`csharp using (var excelStream = new FileStream(path: filePath, FileMode.Open, FileAccess.Read)) using (var csvStream = new MemoryStream()) { MiniExcel.ConvertXlsxToCsv(excelStream, csvStream); }
自從 1.22.0 版本起,您可以像下面這樣自訂 CultureInfo,系統預設為 CultureInfo.InvariantCulture。
var config = new CsvConfiguration()
{
Culture = new CultureInfo("fr-FR"),
};
MiniExcel.SaveAs(path, value, configuration: config);// or
MiniExcel.Query(path, configuration: config);
#### 4. 自訂緩衝區大小 public abstract class Configuration : IConfiguration
{
public int BufferSize { get; set; } = 1024 * 512;
}
#### 5. 快速模式系統不會控制記憶體,但你可以獲得更快的儲存速度。
var config = new OpenXmlConfiguration() { FastMode = true };
MiniExcel.SaveAs(path, reader,configuration:config);
#### 6. 批次新增圖片 (MiniExcel.AddPicture)請在批次產生資料列之前新增圖片,否則在呼叫 AddPicture 時系統將會佔用大量記憶體。
var images = new[]
{
new MiniExcelPicture
{
ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")),
SheetName = null, // default null is first sheet
CellAddress = "C3", // required
},
new MiniExcelPicture
{
ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")),
PictureType = "image/png", // default PictureType = image/png
SheetName = "Demo",
CellAddress = "C9", // required
WidthPx = 100,
HeightPx = 100,
},
};
MiniExcel.AddPicture(path, images);
#### 7. 取得工作表尺寸
var dim = MiniExcel.GetSheetDimensions(path);
範例:
#### 1. SQLite & Dapper 大檔案 SQL 插入避免 OOM
注意:請不要在 Query 之後呼叫 ToList/ToArray 方法,這會將所有資料載入記憶體
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();
}
}
效能表現:

#### 2. ASP.NET Core 3.1 或 MVC 5 下載/上傳 Excel Xlsx API 範例 試用
public class ApiController : Controller
{
public IActionResult Index()
{
return new ContentResult
{
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
Content = @"
DownloadExcel
DownloadExcelFromTemplatePath
DownloadExcelFromTemplateBytes
Upload Excel
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 value = new Dictionary()
{
["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 TemplateBytesCache = new Dictionary();
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 value = new Dictionary()
{
["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"
};
}
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");
}
}
#### 3. 分頁查詢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 Page(IEnumerable en, int pageSize, int page)
{
return en.Skip(page * pageSize).Take(pageSize);
}

#### 4. WebForm 透過 Memorystream 匯出 Excel
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();
#### 5. 動態 i18n 多語言與角色權限管理如範例所示,建立一個方法來處理 i18n 和權限管理,並使用 yield return 返回 IEnumerable
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> GetOrders(string lang, string role, Order[] orders)
{
foreach (var order in orders)
{
var newOrder = new Dictionary();
if (lang == "zh-CN")
{
newOrder.Add("客户编号", order.CustomerID);
newOrder.Add("订单编号", order.OrderNo);
newOrder.Add("产品编号", order.ProductID);
newOrder.Add("数量", order.Qty);
if (role == "Sales")
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; }
}

常見問答
#### Q: Excel 標題欄名稱與類別屬性名稱不一致時,如何對應?
A. 請使用 ExcelColumnName 屬性

#### Q. 如何查詢或匯出多個工作表?
A. 使用 GetSheetNames 方法,並搭配 Query 的 sheetName 參數。
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);
}

#### 問:如何查詢或匯出有關工作表可見性的資訊?
答:使用 GetSheetInformations 方法。
var sheets = MiniExcel.GetSheetInformations(path);
foreach (var sheetInfo in sheets)
{
Console.WriteLine($"sheet index : {sheetInfo.Index} "); // next sheet index - numbered from 0
Console.WriteLine($"sheet name : {sheetInfo.Name} "); // sheet name
Console.WriteLine($"sheet state : {sheetInfo.State} "); // sheet visibility state - visible / hidden
}
#### Q. 使用 Count 會將所有資料載入記憶體嗎?不會,影像測試有 1 百萬行*10 列的資料,最大記憶體用量小於 60MB,耗時 13.65 秒

#### Q. Query 如何使用整數索引?
Query 的預設索引是字串 Key: A,B,C.... 如果要改為數字索引,請建立以下方法進行轉換
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> ConvertToIntIndexRows(IEnumerable
var dic = new Dictionary();
var index = 0;
foreach (var key in keys)
dic[index++] = r[key];
yield return dic;
}
}
#### 問:為什麼當匯出 Excel 且值為空時,會產生沒有標題的空白 Excel?因為 MiniExcel 採用類似於 JSON.NET 的邏輯,從值動態取得型別以簡化 API 操作,沒有資料時無法得知型別。你可以參考 issue #133 來了解。

強型別 & DataTable 會產生標題,但 Dictionary 仍然會產生空白的 Excel
#### 問:如何在遇到空白列時停止 foreach?
MiniExcel 可以搭配 LINQ TakeWhile 用來停止 foreach 的迭代器。

#### 問:如何移除空白列?

IEnumerable :
public static IEnumerable QueryWithoutEmptyRow(Stream stream, bool useHeaderRow, string sheetName, ExcelType excelType, string startCell, IConfiguration configuration)
{
var rows = stream.Query(useHeaderRow,sheetName,excelType,startCell,configuration);
foreach (IDictionary row in rows)
{
if(row.Keys.Any(key=>row[key]!=null))
yield return row;
}
} 資料表:
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 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;
}
#### 問:如何使用 SaveAs(path,value) 來取代已存在的檔案並且不拋出 "The file ...xlsx already exists" 的錯誤
請使用 Stream 類別來自訂檔案建立邏輯,例如:
`C#
using (var stream = File.Create("Demo.xlsx"))
MiniExcel.SaveAs(stream,value);
或者,從 V1.25.0 起,SaveAs 支援 overwriteFile 參數以啟用/禁用覆蓋現有檔案
csharp MiniExcel.SaveAs(path, value, overwriteFile: true); ``
限制與注意事項
- 目前不支援 xls 及加密檔案
- xlsm 只支援查詢
參考資料
ExcelDataReader / ClosedXML / Dapper / ExcelNumberFormat
感謝
#### Jetbrains

感謝為本專案免費提供 All product IDE (授權)
貢獻分享捐贈
連結 https://github.com/orgs/mini-software/discussions/754貢獻者
--- Tranlated By Open Ai Tx | Last indexed: 2025-10-09 ---









