Web Analytics

MiniExcel

⭐ 3374 stars Polish by mini-software

NuGet Build status star GitHub stars version Ask DeepWiki


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


Twoje gwiazdki lub darowizny mogą uczynić MiniExcel lepszym


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

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

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();

image

#### 2. Wykonaj zapytanie i zmapuj je na listę dynamicznych obiektów bez użycia head [[Wypróbuj]](https://dotnetfiddle.net/w5WD1J)

| MiniExcel | 1 | |-----------|---| | Github | 2 |


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 queryfirst

#### 5. Zapytanie według nazwy arkusza

MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
#### 6. Zapytanie o wszystkie nazwy arkuszy i wiersze

var sheetNames = MiniExcel.GetSheetNames(path);
foreach (var sheetName in sheetNames)
{
    var rows = MiniExcel.Query(path, sheetName: sheetName);
}
#### 7. Pobierz kolumny

var columns = MiniExcel.GetColumns(path); // e.g result : ["A","B"...]

var cnt = columns.Count; // get column count

#### 8. Dynamic Query rzutuje wiersz na IDictionary

foreach(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 DataTable

Nie 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);

image

#### 10. Określ komórkę, od której rozpocząć odczyt danych

csharp MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
image

#### 11. Wypełnianie scalonych komórek

Uwaga: Wydajność jest niższa w porównaniu do niewykorzystywania wypełniania scalenia

Powód: Standard OpenXml umieszcza mergeCells na końcu pliku, co powoduje konieczność dwukrotnego przechodzenia przez sheetxml

csharp var config = new OpenXmlConfiguration() { FillMergedCells = true }; var rows = MiniExcel.Query(path, configuration: config);
image

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

image

#### 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)
Możesz użyć SharedStringCacheSize, aby zmienić rozmiar pliku sharedString powyżej określonego rozmiaru dla buforowania na dysku
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);
image

image

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 image

#### 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} });
#### 2. IEnumerable>

csharp var values = new List>() { new Dictionary{{ "Column1", "MiniExcel" }, { "Column2", 1 } }, new Dictionary{{ "Column1", "Github" }, { "Column2", 2 } } }; MiniExcel.SaveAs(path, values);
Wynik tworzenia pliku :

| Kolumna1 | Kolumna2 | |-----------|----------| | MiniExcel | 1 | | Github | 2 |

#### 3. IDataReader

  • Zalecane, pozwala uniknąć ładowania wszystkich danych do pamięci
csharp MiniExcel.SaveAs(path, reader);
image

DataReader eksportuje wiele arkuszy (zalecane przez Dapper ExecuteReader)

csharp using (var cnn = Connection) { cnn.Open(); var sheets = new Dictionary(); sheets.Add("sheet1", cnn.ExecuteReader("select 1 id")); sheets.Add("sheet2", cnn.ExecuteReader("select 2 id")); MiniExcel.SaveAs("Demo.xlsx", sheets); }
#### 4. Datatable

  • Nie zalecane, załaduje wszystkie dane do pamięci
  • DataTable używa najpierw Caption jako nazwy kolumny, następnie nazwy kolumny
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); }

MiniExcel.SaveAs(path, table);

#### 5. Zapytanie Dapper

Dzięki @shaofing #552, proszę użyć CommandDefinition + CommandFlags.NoCache

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); }
Poniższy kod załaduje wszystkie dane do pamięci

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 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 excela

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. Tworzenie wielu arkuszy

csharp // 1. Dictionary 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 { ["users"] = users, ["department"] = department }; MiniExcel.SaveAs(path, sheets);

// 2. DataSet var sheets = new DataSet(); sheets.Add(UsersDataTable); sheets.Add(DepartmentDataTable); //.. MiniExcel.SaveAs(path, sheets);

image

#### 8. Opcje TableStyles

Styl domyślny

image

Bez konfiguracji stylu

csharp var config = new OpenXmlConfiguration() { TableStyles = TableStyles.None }; MiniExcel.SaveAs(path, value,configuration:config);
image

#### 9. AutoFilter

Od wersji v0.19.0 OpenXmlConfiguration.AutoFilter może włączać/wyłączać AutoFilter, domyślna wartość to true, a sposób ustawiania AutoFilter jest następujący:

csharp MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration() { AutoFilter = false });
#### 10. Utwórz obraz

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

#### 11. Eksport pliku jako tablica bajtów

Od wersji 1.22.0, gdy typ wartości to byte[], system domyślnie zapisuje w komórce ścieżkę do pliku, a przy imporcie może zostać przekonwertowany na byte[]. Jeśli nie chcesz korzystać z tej funkcji, możesz ustawić OpenXmlConfiguration.EnableConvertByteArray na false, co może poprawić wydajność systemu.

image

Od wersji 1.22.0, gdy typ wartości to byte[], system domyślnie zapisuje w komórce ścieżkę do pliku, a przy imporcie może zostać przekonwertowany na byte[]. Jeśli nie chcesz korzystać z tej funkcji, możesz ustawić OpenXmlConfiguration.EnableConvertByteArray na false, co może poprawić wydajność systemu.

image

#### 12. Scalanie tych samych komórek pionowo

Funkcjonalność ta jest obsługiwana tylko w formacie xlsx i scala komórki pionowo pomiędzy tagami @merge i @endmerge. Możesz użyć @mergelimit, aby ograniczyć zakres scalania komórek pionowo.

csharp var mergedFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx");

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:

Screenshot 2023-08-07 at 11 59 24

Screenshot 2023-08-07 at 11 59 57

Z limitem scalania:

Screenshot 2023-08-08 at 18 21 00

Screenshot 2023-08-08 at 18 21 40

#### 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);

obraz

xml Somebody once told me.
Poprzednie zachowanie:

csharp / ... /

OpenXmlConfiguration configuration = new OpenXmlConfiguration() { EnableWriteNullValueCell = false // Default value is true. };

MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);

obraz

xml Somebody once told me.
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);

image

Wypełnianie danych do szablonu Excela

  • Deklaracja jest podobna do szablonu Vue {{nazwa zmiennej}} lub renderowania kolekcji {{nazwa kolekcji.nazwa pola}}
  • Renderowanie kolekcji obsługuje IEnumerable/DataTable/DapperRow
#### 1. Podstawowe wypełnianie

Szablon: image

Wynik: image

Kod:

csharp // 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() { ["Name"] = "Jack", ["CreateDate"] = new DateTime(2021, 01, 01), ["VIP"] = true, ["Points"] = 123 }; MiniExcel.SaveAsByTemplate(path, templatePath, value);

#### 2. Wypełnianie danych IEnumerable

Uwaga1: Użyj pierwszego IEnumerable tej samej kolumny jako podstawy do wypełnienia listy

Szablon: image

Wynik: image

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() { ["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);


#### 3. Złożone wypełnianie danych

Uwaga: Obsługa wielu arkuszy oraz używanie tej samej zmiennej

Szablon:

image

Wynik:

image

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() { ["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. 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

image

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

Szablon

image

Wynik

image

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; } }
Kod

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. Przykład :  Lista projektów Github

Szablon

obraz

Wynik

obraz

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 pogrupowanych

csharp var value = new Dictionary() { ["employees"] = new[] { new {name="Jack",department="HR"}, new {name="Jack",department="HR"}, new {name="John",department="HR"}, new {name="John",department="IT"}, new {name="Neo",department="IT"}, new {name="Loan",department="IT"} } }; await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value);
##### 1. Z tagiem @group i z tagiem @header

Przed

before_with_header

Po

after_with_header

##### 2. Z tagiem @group i bez tagu @header

Przed

before_without_header

Po

after_without_header

##### 3. Bez tagu @group

Przed

without_group

Po

without_group_after

#### 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.
csharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif
Przed

if_before

Po

if_after

#### 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 Dictionary() { ["title"] = "FooCompany", ["managers"] = managers, }; MiniExcel.SaveAsByTemplate(path, templatePath, value);
#### 10. Formuły

##### 1. Przykład Poprzedź swoją formułę znakiem $ i użyj $enumrowstart oraz $enumrowend, aby oznaczyć odniesienia do początkowego i końcowego wiersza enumerowalnego:

image

Podczas renderowania szablonu prefiks $ zostanie usunięty, a $enumrowstart i $enumrowend zostaną zastąpione numerami początkowego i końcowego wiersza enumerowalnego:

image

##### 2. Inne przykładowe formuły:

| | | |--------------|-------------------------------------------------------------------------------------------| | Suma | $=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) | | Alt. Średnia | $=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) / COUNT(C{{$enumrowstart}}:C{{$enumrowend}}) | | Zakres | $=MAX(C{{$enumrowstart}}:C{{$enumrowend}}) - MIN(C{{$enumrowstart}}:C{{$enumrowend}}) |

#### 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, IgnoreTemplateParameterMissing pozwala kontrolować, czy wyjątek ma być zgłaszany, czy nie.

csharp var config = new OpenXmlConfiguration() { IgnoreTemplateParameterMissing = false, }; MiniExcel.SaveAsByTemplate(path, templatePath, value, config)
image

Atrybut Nazwa kolumny/Indeks/Ignore w Excelu

#### 1. Określenie nazwy kolumny, indeksu kolumny, ignorowania kolumny

Przykład Excela

image

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(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);

#### 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; } }

Kod

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);
Wynik

image

Zapytanie obsługuje niestandardową konwersję formatów

image

#### 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. ExcelColumnAttribute

Od 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. DynamicColumnAttribute

Od 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);
image

#### 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 { ["usersSheet"] = users, ["departmentSheet"] = department };

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); }
image

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"); }
#### Usuń (oczekuje)

#### Aktualizuj (oczekuje)

Automatyczne sprawdzanie typu Excela

  • MiniExcel domyślnie sprawdza, czy plik jest xlsx czy csv na podstawie rozszerzenia pliku, 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 stream.SaveAs(excelType:ExcelType.CSV); //or stream.SaveAs(excelType:ExcelType.XLSX); //or stream.Query(excelType:ExcelType.CSV); //or stream.Query(excelType:ExcelType.XLSX);

CSV

#### Uwaga

  • Domyślnie zwracany jest typ string, 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

csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { Seperator=';' }; MiniExcel.SaveAs(path, values,configuration: config);
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();
#### Niestandardowy znak nowej linii

Domyślnie jako znak nowej linii używany jest \r\n, możesz dostosować to poprzez modyfikację właściwości NewLine

csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { NewLine='\n' }; MiniExcel.SaveAs(path, values,configuration: config);
#### 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 null

Domyś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 Task> QueryAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) public static Task> QueryAsync(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new() public static Task> QueryAsync(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new() public static Task>> 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 QueryAsDataTableAsync(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
-  v1.25.0 obsługuje cancellationToken

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)

image

Od wersji V0.18.0 obsługiwany jest 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

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);
`csharp
using (var excelStream = new FileStream(path: filePath, FileMode.Open, FileAccess.Read))
using (var csvStream = new MemoryStream())
{
   MiniExcel.ConvertXlsxToCsv(excelStream, csvStream);
}
#### 3. Niestandardowy CultureInfo

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 Szybki

System 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);
Obraz

#### 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ść: image

#### 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ące

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); }

20210419

#### 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ól

Jak w przykładzie, utwórz metodę do obsługi i18n oraz zarządzania uprawnieniami, i użyj yield return, aby zwracać IEnumerable> dla uzyskania dynamicznych efektów przetwarzania i niskiego zużycia pamięci

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; } }

image

FAQ

#### P: Nagłówek w Excelu nie jest równy nazwie właściwości klasy, jak mapować?

O. Proszę użyć atrybutu ExcelColumnName

image

#### 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);
}
image

#### 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

image

#### 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 rows) { ICollection keys = null; var isFirst = true; foreach (IDictionary r in rows) { if(isFirst) { keys = r.Keys; isFirst = false; }

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ć.

image

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.

Image

#### P. Jak usunąć puste wiersze?

image

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

jetbrains-variant-2

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/754

Współtwórcy

--- Tranlated By Open Ai Tx | Last indexed: 2026-03-01 ---