Ten projekt jest częścią .NET Foundation i działa zgodnie z ich kodeksem postępowania.
English | 简体中文 | 繁體中文 | 日本語 | 한국어 | हिन्दी | ไทย | Français | Deutsch | Español | Italiano | Русский | Português | Nederlands | Polski | العربية | فارسی | Türkçe | Tiếng Việt | Bahasa Indonesia
Wprowadzenie
MiniExcel to proste i wydajne narzędzie do obsługi Excela dla .NET, zaprojektowane specjalnie z myślą o minimalnym zużyciu pamięci.
Obecnie większość popularnych frameworków musi załadować wszystkie dane z dokumentu Excel do pamięci, aby umożliwić operacje, co może powodować problemy z nadmiernym zużyciem pamięci. Podejście MiniExcel jest inne: dane są przetwarzane wiersz po wierszu w sposób strumieniowy, co pozwala zredukować pierwotne zużycie z potencjalnie setek megabajtów do zaledwie kilku megabajtów, skutecznie zapobiegając problemom z brakiem pamięci (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;
Funkcje
- Minimalizuje zużycie pamięci, zapobiegając błędom braku pamięci (OOM) i unikając pełnych cykli odśmiecania pamięci
- Umożliwia operacje na danych w czasie rzeczywistym na poziomie wiersza, co zapewnia lepszą wydajność przy dużych zbiorach danych
- Obsługuje LINQ z odroczonym wykonaniem, umożliwiając szybkie, wydajne pod względem pamięci stronicowanie i złożone zapytania
- Lekka biblioteka, nie wymaga Microsoft Office ani komponentów COM+, a rozmiar DLL poniżej 500KB
- Prosty i intuicyjny styl API do odczytu/zapisu/wypełniania plików Excel
Wersja 2.0 – zapowiedź
Pracujemy nad przyszłą wersją MiniExcel, z nowym modułowym i ukierunkowanym API,
oddzielnymi paczkami nuget dla funkcjonalności Core i Csv, pełnym wsparciem dla asynchronicznego strumieniowania zapytań poprzez IAsyncEnumerable,
i wieloma innymi nowościami wkrótce! Paczki będą dostępne w wersji pre-release, więc zachęcamy do wypróbowania i przesłania nam opinii!
Jeśli to zrobisz, koniecznie sprawdź także nową dokumentację oraz notatki dotyczące aktualizacji.
Pierwsze kroki
- Importuj/Zapytaj Excel
- Eksportuj/Utwórz Excel
- Szablon Excel
- Nazwa/Indeks kolumny Excel/Atrybut Ignoruj
- Przykłady
Instalacja
Możesz zainstalować paczkę z NuGet
Informacje o wydaniach
Sprawdź informacje o wydaniach
DO ZROBIENIA
Proszę sprawdzić TODO
Wydajność
Kod używany do testów wydajności znajduje się w MiniExcel.Benchmarks.
Plik używany do testów wydajności to Test1,000,000x10.xlsx, dokument 32MB zawierający 1 000 000 wierszy * 10 kolumn, których komórki są wypełnione napisem "HelloWorld".
Aby uruchomić wszystkie testy wydajności, użyj:
dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
Możesz znaleźć wyniki testów wydajnościowych dla najnowszego wydania tutaj.Zapytanie/Import do Excela
#### 1. Wykonaj zapytanie i zmapuj wyniki na silnie typizowaną kolekcję IEnumerable [[Wypróbuj]](https://dotnetfiddle.net/w5WD1J)
Zaleca się użycie Stream.Query ze względu na lepszą wydajność.
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. Wykonaj zapytanie i zmapuj je na listę dynamicznych obiektów bez użycia head [[Wypróbuj]](https://dotnetfiddle.net/w5WD1J)
- dynamiczny klucz to
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. Wykonaj zapytanie z pierwszym wierszem nagłówka [[Wypróbuj]](https://dotnetfiddle.net/w5WD1J)uwaga: w przypadku takich samych nazw kolumn używana jest ostatnia z prawej
Wejściowy Excel:
| Kolumna1 | Kolumna2 | |-----------|----------| | 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. Wsparcie dla zapytań LINQ Extension First/Take/Skip ...itd.Zapytanie 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);
}
Wydajność pomiędzy MiniExcel/ExcelDataReader/ClosedXML/EPPlus

#### 5. Zapytanie według nazwy arkusza
MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
#### 6. Zapytanie o wszystkie nazwy arkuszy i wierszevar sheetNames = MiniExcel.GetSheetNames(path);
foreach (var sheetName in sheetNames)
{
var rows = MiniExcel.Query(path, sheetName: sheetName);
}
#### 7. Pobierz kolumnyvar columns = MiniExcel.GetColumns(path); // e.g result : ["A","B"...]var cnt = columns.Count; // get column count
#### 8. Dynamic Query rzutuje wiersz na 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. Zapytanie Excel zwraca DataTableNie jest zalecane, ponieważ DataTable załaduje wszystkie dane do pamięci i utraci cechę niskiego zużycia pamięci przez MiniExcel.
``C#
var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);

#### 10. Określ komórkę, od której rozpocząć odczyt danych
csharp MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
csharp var config = new OpenXmlConfiguration() { FillMergedCells = true }; var rows = MiniExcel.Query(path, configuration: config);niewykorzystywania wypełniania scalenia#### 11. Wypełnianie scalonych komórek
Uwaga: Wydajność jest niższa w porównaniu do
Powód: Standard OpenXml umieszcza mergeCells na końcu pliku, co powoduje konieczność dwukrotnego przechodzenia przez sheetxml

obsługa zmiennych długości i szerokości, wypełnianie wielorzędowe i wielokolumnowe

#### 12. Odczyt dużych plików przez buforowanie na dysku (Disk-Base Cache - SharedString)
Jeśli rozmiar SharedStrings przekroczy 5 MB, MiniExcel domyślnie użyje lokalnego buforowania na dysku, np. 10x100000.xlsx (milion wierszy danych), gdy buforowanie na dysku jest wyłączone, maksymalne zużycie pamięci wynosi 195MB, ale z włączonym buforowaniem na dysku potrzeba tylko 65MB. Uwaga, ta optymalizacja wiąże się z pewnym kosztem wydajności, więc w tym przypadku czas odczytu wzrasta z 7,4 sekundy do 27,2 sekundy. Jeśli tego nie potrzebujesz, możesz wyłączyć buforowanie na dysku za pomocą poniższego kodu:
csharp
var config = new OpenXmlConfiguration { EnableSharedStringCache = false };
MiniExcel.Query(path,configuration: config)
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);Możesz użyćSharedStringCacheSize, aby zmienić rozmiar pliku sharedString powyżej określonego rozmiaru dla buforowania na dysku


Tworzenie/Eksportowanie Excela
- Musi być typem nieabstrakcyjnym z publicznym konstruktorem bezparametrowym.
- MiniExcel obsługuje parametry IEnumerable Deferred Execution. Jeśli chcesz używać jak najmniej pamięci, nie wywołuj metod takich jak ToList
np.: ToList lub nie, użycie pamięci

#### 1. Typ anonimowy lub silnie typowany [[Wypróbuj]](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);Wynik tworzenia pliku :Zalecane| Kolumna1 | Kolumna2 | |-----------|----------| | MiniExcel | 1 | | Github | 2 |
#### 3. IDataReader
, pozwala uniknąć ładowania wszystkich danych do pamięci

DataReader eksportuje wiele arkuszy (zalecane przez 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. DatatableNie zalecane, załaduje wszystkie dane do pamięciDataTable używa najpierw Caption jako nazwy kolumny, następnie nazwy kolumny
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. Zapytanie DapperCommandDefinition + CommandFlags.NoCacheDzięki @shaofing #552, proszę użyć
Poniższy kod załaduje wszystkie dane do pamięcicsharp
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 do MemoryStream [[Wypróbuj]](https://dotnetfiddle.net/JOen0e)
csharp
using (var stream = new MemoryStream()) //support FileStream,MemoryStream ect.
{
stream.SaveAs(values);
}
np. : api eksportu do excelacsharp
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. Tworzenie wielu arkuszy
csharp
// 1. Dictionary// 2. DataSet var sheets = new DataSet(); sheets.Add(UsersDataTable); sheets.Add(DepartmentDataTable); //.. MiniExcel.SaveAs(path, sheets);

#### 8. Opcje TableStyles
Styl domyślny

Bez konfiguracji stylu
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. AutoFilter
Od wersji v0.19.0
może włączać/wyłączać AutoFilter, domyślna wartość totrue, a sposób ustawiania AutoFilter jest następujący:
#### 10. Utwórz obrazcsharp
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. Eksport pliku jako tablica bajtów
Od wersji 1.22.0, gdy typ wartości to
, system domyślnie zapisuje w komórce ścieżkę do pliku, a przy imporcie może zostać przekonwertowany nabyte[]. Jeśli nie chcesz korzystać z tej funkcji, możesz ustawićOpenXmlConfiguration.EnableConvertByteArraynafalse, co może poprawić wydajność systemu.byte[]
Od wersji 1.22.0, gdy typ wartości to
, system domyślnie zapisuje w komórce ścieżkę do pliku, a przy imporcie może zostać przekonwertowany nabyte[]. Jeśli nie chcesz korzystać z tej funkcji, możesz ustawićOpenXmlConfiguration.EnableConvertByteArraynafalse, co może poprawić wydajność systemu.xlsx
#### 12. Scalanie tych samych komórek pionowo
Funkcjonalność ta jest obsługiwana tylko w formacie
i scala komórki pionowo pomiędzy tagami @merge i @endmerge. Możesz użyć @mergelimit, aby ograniczyć zakres scalania komórek pionowo.
var path = @"../../../../../samples/xlsx/TestMergeWithTag.xlsx";
MiniExcel.MergeSameCells(mergedFilePath, path);
csharp
var memoryStream = new MemoryStream();var path = @"../../../../../samples/xlsx/TestMergeWithTag.xlsx";
memoryStream.MergeSameCells(path);
Zawartość pliku przed i po scaleniu:Bez limitu scalania:


Z limitem scalania:


#### 13. Pomijanie wartości null
Nowa jawna opcja zapisu pustych komórek dla wartości 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
Poprzednie zachowanie:csharp
/ ... /OpenXmlConfiguration configuration = new OpenXmlConfiguration() { EnableWriteNullValueCell = false // Default value is true. };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);

xml
Działa dla wartości null i DBNull.#### 14. Zamrażanie okienek
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);{{nazwa zmiennej}}Wypełnianie danych do szablonu Excela
- Deklaracja jest podobna do szablonu Vue
lub renderowania kolekcji{{nazwa kolekcji.nazwa pola}}Renderowanie kolekcji obsługuje IEnumerable/DataTable/DapperRow #### 1. Podstawowe wypełnianie
Szablon:
Wynik:
Kod:
// 2. By Dictionary
var value = new Dictionary#### 2. Wypełnianie danych IEnumerable
Uwaga1: Użyj pierwszego IEnumerable tej samej kolumny jako podstawy do wypełnienia listy
Szablon:

Wynik:

Kod:
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. Złożone wypełnianie danych
Uwaga: Obsługa wielu arkuszy oraz używanie tej samej zmiennej
Szablon:

Wynik:
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. Wydajność wypełniania dużych zbiorów danych
UWAGA: Używanie IEnumerable z odroczonym wykonaniem zamiast ToList pozwala zminimalizować użycie pamięci w MiniExcel

#### 5. Automatyczne mapowanie typu wartości komórki
Szablon

Wynik

Klasa
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; } }
Kodcsharp
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. Przykład : Lista projektów GithubSzablon

Wynik

Kod
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. Wypełnianie danych pogrupowanychcsharp
var value = new Dictionarycsharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif##### 1. Z tagiem@groupi z tagiem@headerPrzed
Po
##### 2. Z tagiem @group i bez tagu @header
Przed
Po
##### 3. Bez tagu @group
Przed
Po
#### 8. Instrukcje If/ElseIf/Else w komórce
Zasady:
- Obsługuje DateTime, Double, Int z operatorami ==, !=, >, >=, <, <=.
- Obsługuje String z operatorami ==, !=.
- Każda instrukcja powinna być w nowej linii.
- Przed i po operatorach powinien być pojedynczy odstęp.
- Wewnątrz instrukcji nie powinno być nowej linii.
- Komórka powinna mieć dokładnie taki format jak poniżej.
Przed
Po

#### 9. DataTable jako parametr
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. Formuły$##### 1. Przykład Poprzedź swoją formułę znakiem
i użyj$enumrowstartoraz$enumrowend, aby oznaczyć odniesienia do początkowego i końcowego wiersza enumerowalnego:$
Podczas renderowania szablonu prefiks
zostanie usunięty, a$enumrowstarti$enumrowendzostaną zastąpione numerami początkowego i końcowego wiersza enumerowalnego:$=SUM(C{{$enumrowstart}}:C{{$enumrowend}})
##### 2. Inne przykładowe formuły:
| | | |--------------|-------------------------------------------------------------------------------------------| | Suma |
| | Alt. Średnia |$=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) / COUNT(C{{$enumrowstart}}:C{{$enumrowend}})| | Zakres |$=MAX(C{{$enumrowstart}}:C{{$enumrowend}}) - MIN(C{{$enumrowstart}}:C{{$enumrowend}})|IgnoreTemplateParameterMissing#### 11. Inne
##### 1. Sprawdzanie klucza parametru szablonu
Od wersji V1.24.0 domyślnie ignorowany jest brakujący klucz parametru szablonu i zastępowany pustym ciągiem,
pozwala kontrolować, czy wyjątek ma być zgłaszany, czy nie.

Atrybut Nazwa kolumny/Indeks/Ignore w Excelu
#### 1. Określenie nazwy kolumny, indeksu kolumny, ignorowania kolumny
Przykład Excela

Kod
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. Niestandardowy format (ExcelFormatAttribute)
Od wersji V0.21.0 obsługa klasy zawierającej metodę ToString(string content) do formatowania
Klasa
csharp public class Dto { public string Name { get; set; }
[ExcelFormat("MMMM dd, yyyy")] public DateTime InDate { get; set; } }
Kodcsharp
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);
Wynik
Zapytanie obsługuje niestandardową konwersję formatów

#### 3. Ustawianie szerokości kolumny (ExcelColumnWidthAttribute)
csharp
public class Dto
{
[ExcelColumnWidth(20)]
public int ID { get; set; }
[ExcelColumnWidth(15.50)]
public string Name { get; set; }
}
#### 4. Mapowanie wielu nazw kolumn do tej samej właściwości.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
Od wersji 1.24.0, system obsługuje 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. ExcelColumnAttributeOd wersji V1.26.0, wiele atrybutów można uprościć w następujący sposób:
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. DynamicColumnAttributeOd wersji V1.26.0 możemy dynamicznie ustawiać atrybuty kolumny
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. DynamicSheetAttribute
Od wersji V1.31.4 możemy dynamicznie ustawiać atrybuty arkusza. Możemy ustawić nazwę arkusza oraz stan (widoczność).
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);
Możemy również użyć nowego atrybutu 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; }
}
### Dodaj, Usuń, Aktualizuj#### Dodaj
v1.28.0 obsługuje wstawianie N wierszy danych do CSV po ostatnim wierszu
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 obsługuje wstawianie nowego arkusza do istniejącego skoroszytu w Excelu
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);#### Usuń (oczekuje)rozszerzenia pliku#### Aktualizuj (oczekuje)
Automatyczne sprawdzanie typu Excela
- MiniExcel domyślnie sprawdza, czy plik jest xlsx czy csv na podstawie
, ale może to być niedokładne, proszę określić to ręcznie.Nie można określić typu Excela z samego strumienia, proszę określić go ręcznie.
csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { Seperator=';' }; MiniExcel.SaveAs(path, values,configuration: config);stringCSV
#### Uwaga
- Domyślnie zwracany jest typ
, a wartość nie zostanie przekonwertowana na liczbę ani datę/czas, chyba że typ zostanie określony przez silne typowanie generyczne.,#### Własny separator
Domyślnie separatorem jest
, możesz dostosować separator, modyfikując właściwośćSeperator
Od wersji V1.30.1 obsługiwana jest funkcja własnego separatora (podziękowania dla @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);#### Niestandardowy znak nowej linii\r\nDomyślnie jako znak nowej linii używany jest
, możesz dostosować to poprzez modyfikację właściwościNewLine
#### Niestandardowe kodowanie- Domyślne kodowanie to "Wykryj kodowanie na podstawie znaczników kolejności bajtów" (detectEncodingFromByteOrderMarks: true)
- Jeśli masz niestandardowe wymagania dotyczące kodowania, zmodyfikuj właściwość 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);
#### Odczytaj pusty ciąg jako nullDomyślnie puste wartości są mapowane na string.Empty. Możesz zmienić to zachowanie
csharp
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
ReadEmptyStringAsNull = true
};
### DataReader#### 1. GetReader
Od wersji 1.23.0 możesz użyć 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);
}
}
}
### Async- v0.17.0 obsługuje Async (dzięki 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 obsługujecancellationToken。Inne
#### 1. Enum
Upewnij się, że nazwa w Excelu i nazwa właściwości są takie same, system sam zmapuje je automatycznie (ignorując wielkość liter)
Od wersji V0.18.0 obsługiwany jest Enum Description
public enum Type { [Description("General User")] V1, [Description("General Administrator")] V2, [Description("Super Administrator")] V3 }

Od wersji 1.30.0 obsługiwany jest opis Excela do Enum, podziękowania dla @KaneLeung
#### 2. Konwersja CSV do XLSX lub konwersja XLSX do CSV
csharp
MiniExcel.ConvertXlsxToCsv(xlsxPath, csvPath);
MiniExcel.ConvertXlsxToCsv(xlsxStream, csvStream);
MiniExcel.ConvertCsvToXlsx(csvPath, xlsxPath);
MiniExcel.ConvertCsvToXlsx(csvStream, xlsxStream);
#### 3. Niestandardowy CultureInfo`csharp using (var excelStream = new FileStream(path: filePath, FileMode.Open, FileAccess.Read)) using (var csvStream = new MemoryStream()) { MiniExcel.ConvertXlsxToCsv(excelStream, csvStream); }
Od wersji 1.22.0 możesz dostosować CultureInfo jak poniżej, domyślnie system używa CultureInfo.InvariantCulture.
var config = new CsvConfiguration()
{
Culture = new CultureInfo("fr-FR"),
};
MiniExcel.SaveAs(path, value, configuration: config);// or
MiniExcel.Query(path, configuration: config);
#### 4. Niestandardowy rozmiar bufora public abstract class Configuration : IConfiguration
{
public int BufferSize { get; set; } = 1024 * 512;
}
#### 5. Tryb SzybkiSystem nie będzie kontrolował pamięci, ale możesz uzyskać szybszą prędkość zapisu.
var config = new OpenXmlConfiguration() { FastMode = true };
MiniExcel.SaveAs(path, reader,configuration:config);
#### 6. Dodawanie obrazów zbiorczo (MiniExcel.AddPicture)Proszę dodać obrazy przed zbiorczym generowaniem danych wierszy, w przeciwnym razie system zużyje dużo pamięci podczas wywoływania 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. Pobierz wymiary arkuszy
var dim = MiniExcel.GetSheetDimensions(path);
Przykłady:
#### 1. SQLite & Dapper Duży plik Wstawianie SQL Unikanie OOM
uwaga: proszę nie wywoływać metod ToList/ToArray po Query, załaduje to wszystkie dane do pamięci
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();
}
}
wydajność:

#### 2. ASP.NET Core 3.1 lub MVC 5 Pobieranie/Wysyłanie Excel Xlsx API Demo Wypróbuj
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. Zapytanie stronicującevoid 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 eksportuje Excel za pomocą MemoryStream
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. Dynamiczne zarządzanie wielojęzycznością (i18n) i uprawnieniami rólJak w przykładzie, utwórz metodę do obsługi i18n oraz zarządzania uprawnieniami, i użyj yield return, aby zwracać 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; }
}

FAQ
#### P: Nagłówek w Excelu nie jest równy nazwie właściwości klasy, jak mapować?
O. Proszę użyć atrybutu ExcelColumnName

#### P: Jak zapytac lub eksportować wiele arkuszy?
O. Metoda GetSheetNames z parametrem sheetName w Query.
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);
}

#### P. Jak zapytać lub wyeksportować informacje o widoczności arkusza?
O. Metoda 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
}#### P. Jak wypełniać dane poziomo (z lewej do prawej) za pomocą szablonów?
O. Renderowanie kolekcji szablonów MiniExcel rozszerza się pionowo (z góry na dół). Wypełnianie poziome (z lewej do prawej) nie jest jeszcze obsługiwane (zobacz https://github.com/mini-software/MiniExcel/issues/619).
Jeśli potrzebujesz tylko końcowego układu, przekształć swoje dane w macierz i wyeksportuj je z printHeader: false:
var employees = new[]
{
new { Name = "Name1", Department = "Department1", City = "City1", Country = "Country1" },
new { Name = "Name2", Department = "Department2", City = "City2", Country = "Country2" },
new { Name = "Name3", Department = "Department3", City = "City3", Country = "Country3" },
};var table = new DataTable();
table.Columns.Add("A");
for (var i = 0; i < employees.Length; i++)
table.Columns.Add($"B{i + 1}");
table.Rows.Add(new object[] { "Name" }.Concat(employees.Select(e => (object)e.Name)).ToArray());
table.Rows.Add(new object[] { "Department" }.Concat(employees.Select(e => (object)e.Department)).ToArray());
table.Rows.Add(new object[] { "City" }.Concat(employees.Select(e => (object)e.City)).ToArray());
table.Rows.Add(new object[] { "Country" }.Concat(employees.Select(e => (object)e.Country)).ToArray());
MiniExcel.SaveAs(path, table, printHeader: false);
Jeśli musisz użyć szablonu do stylowania, jedną z opcji jest użycie skalarnych zastępników (np. {{Name_1}}, {{Name_2}} ...) i wypełnienie słownika (wymaga to ustalonej maksymalnej liczby kolumn).#### P. Czy użycie Count załaduje wszystkie dane do pamięci?
Nie, test obrazu obejmuje 1 milion wierszy*10 kolumn danych, maksymalne użycie pamięci to <60MB, a czas trwania to 13,65 sekundy

#### P. Jak Query używa indeksów liczbowych?
Domyślny indeks Query to string Key: A,B,C... Jeśli chcesz zmienić na indeksy liczbowe, utwórz poniższą metodę do konwersji
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;
}
}
#### P. Dlaczego podczas eksportu Excela bez tytułu, gdy wartość jest pusta, generowany jest pusty plik excel?Ponieważ MiniExcel używa logiki podobnej do JSON.NET, aby dynamicznie pobierać typ z wartości w celu uproszczenia obsługi API, typ nie może być znany bez danych. Możesz sprawdzić problem #133, aby lepiej zrozumieć.

Silnie typowane & DataTable wygenerują nagłówki, ale Dictionary nadal wygeneruje pusty plik Excel
#### P. Jak zatrzymać foreach przy pustym wierszu?
MiniExcel można użyć razem z
LINQ TakeWhile, aby zatrzymać iterator foreach.
#### P. Jak usunąć puste wiersze?

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;
}
}
DataTable :
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;
}
#### P. Jak użyć SaveAs(path,value), aby zastąpić istniejący plik bez wywoływania błędu "Plik ...xlsx już istnieje"
Proszę użyć klasy Stream, aby dostosować logikę tworzenia pliku, np.:
`C#
using (var stream = File.Create("Demo.xlsx"))
MiniExcel.SaveAs(stream,value);
lub, od wersji V1.25.0, SaveAs obsługuje parametr overwriteFile, aby włączyć/wyłączyć nadpisywanie istniejącego pliku
csharp
MiniExcel.SaveAs(path, value, overwriteFile: true);
``Ograniczenia i zastrzeżenia
- Obecnie brak wsparcia dla plików xls oraz plików zaszyfrowanych
- xlsm wspiera tylko zapytania
Odnośniki
ExcelDataReader / ClosedXML / Dapper / ExcelNumberFormat
Podziękowania
#### Jetbrains

Dziękujemy za udostępnienie darmowego pakietu All product IDE dla tego projektu (Licencja)
Wsparcie i darowizny
Link https://github.com/orgs/mini-software/discussions/754Współtwórcy
--- Tranlated By Open Ai Tx | Last indexed: 2026-03-01 ---










