Web Analytics

MiniExcel

⭐ 3234 stars Korean by mini-software

NuGet Build status star GitHub stars version Ask DeepWiki


이 프로젝트는 .NET 재단의 일부이며, 해당 행동 강령을 준수합니다.


English | 简体中文 | 繁體中文 | 日本語 | 한국어 | हिन्दी | ไทย | Français | Deutsch | Español | Italiano | Русский | Português | Nederlands | Polski | العربية | فارسی | Türkçe | Tiếng Việt | Bahasa Indonesia


당신의 스타 또는 후원이 MiniExcel을 더 좋게 만들 수 있습니다


소개

MiniExcel은 .NET을 위한 간단하고 효율적인 Excel 처리 도구로, 메모리 사용량 최소화를 목표로 설계되었습니다.

현재 대부분의 인기 프레임워크는 Excel 문서의 모든 데이터를 메모리로 로드하여 작업을 수행하지만, 이로 인해 메모리 소비 문제가 발생할 수 있습니다. MiniExcel의 접근 방식은 다릅니다. 데이터를 스트리밍 방식으로 한 줄씩 처리하여 기존 수백 메가바이트까지 늘어날 수 있는 소비를 몇 메가바이트로 줄임으로써, 효과적으로 메모리 부족(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;

특징

버전 2.0 프리뷰

향후 MiniExcel 버전을 준비 중이며, 새로운 모듈식 및 집중적인 API, Core와 Csv 기능에 대한 별도의 NuGet 패키지, IAsyncEnumerable를 통한 비동기 스트림 쿼리 완전 지원, 그리고 곧 더 많은 기능들이 추가될 예정입니다! 패키지는 프리릴리즈로 제공될 예정이니, 자유롭게 확인하시고 피드백을 주세요!

사용하실 경우, 새 문서업그레이드 노트도 꼭 확인해 주세요.

시작하기

설치

패키지를 NuGet에서 설치할 수 있습니다

릴리즈 노트

릴리즈 노트를 확인해 주세요

TODO

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
최신 릴리스의 벤치마크 결과는 여기에서 확인할 수 있습니다.

엑셀 쿼리/가져오기

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

image

#### 2. 쿼리를 실행하고 head를 사용하지 않고 동적 객체 리스트에 매핑하기 [[실행해보기]](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. 첫 번째 헤더 행으로 쿼리 실행 [[실행해보기]](https://dotnetfiddle.net/w5WD1J)

참고 : 동일한 열 이름은 가장 오른쪽의 것을 사용함

입력 엑셀 :

| Column1 | Column2 | |-----------|---------| | 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 간의 성능 비교 queryfirst

#### 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. 동적 쿼리에서 행을 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. Excel 쿼리 결과를 DataTable로 반환

권장하지 않습니다. DataTable은 모든 데이터를 메모리에 로드하므로 MiniExcel의 낮은 메모리 소비 기능을 잃게 됩니다.

``C# var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);

image

#### 10. 데이터를 읽기 시작할 셀 지정

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

#### 11. 병합된 셀 채우기

참고: 병합 채우기 미사용에 비해 효율성이 떨어집니다

이유: OpenXml 표준은 mergeCells를 파일 맨 아래에 두기 때문에 sheetxml을 두 번 foreach 해야 합니다

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

가변 길이 및 너비의 다중 행과 열 채우기 지원

image

#### 12. 디스크 기반 캐시(Disk-Base Cache - SharedString)를 통한 대용량 파일 읽기

SharedStrings의 크기가 5MB를 초과하면, MiniExcel은 기본적으로 로컬 디스크 캐시를 사용합니다. 예를 들어, 10x100000.xlsx(백만 행 데이터)에서 디스크 캐시를 비활성화하면 최대 메모리 사용량이 195MB이지만, 디스크 캐시를 활성화하면 65MB만 필요합니다. 참고로, 이 최적화는 약간의 효율성 저하가 있으며, 이 경우 읽기 시간이 7.4초에서 27.2초로 증가합니다. 필요하지 않은 경우 아래 코드를 통해 디스크 캐시를 비활성화할 수 있습니다.

csharp var config = new OpenXmlConfiguration { EnableSharedStringCache = false }; MiniExcel.Query(path,configuration: config)
SharedStringCacheSize를 사용하여 디스크 캐싱을 위해 지정된 크기를 초과하는 sharedString 파일 크기를 변경할 수 있습니다.
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);
image

image

엑셀 생성/내보내기

  • 반드시 public 매개변수 없는 생성자가 있는 비추상형 타입이어야 합니다.
  • MiniExcel은 IEnumerable 지연 실행을 지원합니다. 메모리 사용을 최소화하려면 ToList와 같은 메서드를 호출하지 마세요.
예시 : ToList 사용 여부에 따른 메모리 사용 image

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

csharp var values = new List>() { new Dictionary{{ "Column1", "MiniExcel" }, { "Column2", 1 } }, new Dictionary{{ "Column1", "Github" }, { "Column2", 2 } } }; MiniExcel.SaveAs(path, values);
파일 생성 결과 :

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

#### 3. IDataReader

  • 권장, 모든 데이터를 메모리에 로드하지 않아도 됩니다
csharp MiniExcel.SaveAs(path, reader);
image

DataReader 다중 시트 내보내기(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. 데이터테이블

  • 권장하지 않음, 모든 데이터를 메모리로 로드하게 됨
  • 데이터테이블은 먼저 캡션을 열 이름으로 사용하고, 그다음 열 이름을 사용함
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. Dapper 쿼리

@shaofing #552 감사합니다. 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); }
아래 코드는 모든 데이터를 메모리로 불러옵니다.

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. MemoryStream으로 SaveAs [[직접 해보기]](https://dotnetfiddle.net/JOen0e)

csharp using (var stream = new MemoryStream()) //support FileStream,MemoryStream ect. { stream.SaveAs(values); }
예: 엑셀 내보내기 API

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. 여러 시트 만들기

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. TableStyles 옵션

기본 스타일

image

스타일 설정 없이

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

#### 9. 자동 필터(AutoFilter)

v0.19.0부터 OpenXmlConfiguration.AutoFilter로 자동 필터(AutoFilter)를 활성화/비활성화할 수 있으며, 기본값은 true입니다. 자동 필터를 설정하는 방법은 다음과 같습니다:

csharp MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration() { AutoFilter = false });

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

#### 11. 바이트 배열 파일 내보내기

1.22.0부터 값 타입이 byte[]인 경우, 시스템은 기본적으로 셀에 파일 경로를 저장하고, 가져올 때 이를 byte[]로 변환할 수 있습니다. 만약 이 기능을 사용하고 싶지 않다면, OpenXmlConfiguration.EnableConvertByteArrayfalse로 설정하면 시스템 효율성이 향상됩니다.

image

1.22.0부터 값 타입이 byte[]인 경우, 시스템은 기본적으로 셀에 파일 경로를 저장하고, 가져올 때 이를 byte[]로 변환할 수 있습니다. 만약 이 기능을 사용하고 싶지 않다면, OpenXmlConfiguration.EnableConvertByteArrayfalse로 설정하면 시스템 효율성이 향상됩니다.

image

#### 12. 동일한 셀 세로 병합

이 기능은 xlsx 형식에서만 지원되며, @merge와 @endmerge 태그 사이에서 셀을 세로로 병합합니다. @mergelimit을 사용하면 세로 병합 셀의 범위를 제한할 수 있습니다.

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

병합 전후 파일 내용:

병합 제한 없이:

Screenshot 2023-08-07 at 11 59 24

Screenshot 2023-08-07 at 11 59 57

병합 제한 적용:

Screenshot 2023-08-08 at 18 21 00

Screenshot 2023-08-08 at 18 21 40

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

image

xml Somebody once told me.
이전 동작:

csharp / ... /

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

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

image

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

image

엑셀 템플릿에 데이터 채우기

  • 선언 방식은 Vue 템플릿의 {{변수명}} 또는 컬렉션 렌더링 {{컬렉션명.필드명}}과 유사합니다.
  • 컬렉션 렌더링은 IEnumerable/DataTable/DapperRow를 지원합니다.
#### 1. 기본 채우기

템플릿: image

결과: image

코드:

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. IEnumerable 데이터 채우기

참고1: 동일한 열의 첫 번째 IEnumerable을 리스트 채우기의 기준으로 사용

템플릿: image

결과: image

코드:

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. 복잡한 데이터 채우기

참고: 다중 시트 지원 및 동일 변수 사용

템플릿:

image

결과:

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. 대용량 데이터 성능

참고: MiniExcel에서 IEnumerable 지연 실행을 사용하고 ToList를 사용하지 않으면 최대 메모리 사용량을 절약할 수 있습니다.

image

#### 5. 셀 값 자동 매핑 타입

템플릿

image

결과

image

클래스

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 프로젝트 목록화

템플릿

image

결과

image

코드

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 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. @group 태그와 @header 태그가 모두 있을 때

Before

before_with_header

After

after_with_header

##### 2. @group 태그만 있고 @header 태그가 없을 때

Before

before_without_header

After

after_without_header

##### 3. @group 태그 없이

Before

without_group

After

without_group_after

#### 8. 셀 내의 If/ElseIf/Else 문

규칙:

  • DateTime, Double, Int 타입에 대해 ==, !=, >, >=, <, <= 연산자 지원.
  • String 타입에 대해 ==, != 연산자 지원.
  • 각 문장은 새로운 줄에 작성해야 함.
  • 연산자 앞뒤에 공백 한 칸을 추가해야 함.
  • 문장 내부에는 줄바꿈이 없어야 함.
  • 셀은 아래와 같은 정확한 형식이어야 함.
csharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif
이전

if_before

이후

if_after

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

##### 1. 예시 수식 앞에 $를 붙이고, $enumrowstart$enumrowend를 사용하여 반복 가능한 시작 및 끝 행을 참조하십시오:

image

템플릿이 렌더링될 때 $ 접두사는 제거되고, $enumrowstart$enumrowend는 반복 가능한 시작 및 끝 행 번호로 대체됩니다:

image

##### 2. 기타 예시 수식:

| | | |--------------|-------------------------------------------------------------------------------------------| | 합계 | $=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) | | 대안 평균 | $=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) / COUNT(C{{$enumrowstart}}:C{{$enumrowend}}) | | 범위 | $=MAX(C{{$enumrowstart}}:C{{$enumrowend}}) - MIN(C{{$enumrowstart}}:C{{$enumrowend}}) |

#### 11. 기타

##### 1. 템플릿 파라미터 키 확인

V1.24.0부터 기본적으로 템플릿의 누락된 파라미터 키를 무시하고 빈 문자열로 대체하며, IgnoreTemplateParameterMissing 옵션으로 예외 발생 여부를 제어할 수 있습니다.

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

Excel 열 이름/인덱스/무시 속성

#### 1. 열 이름, 열 인덱스, 열 무시 지정

엑셀 예시

image

코드

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. 사용자 지정 형식 (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);
결과

image

쿼리는 사용자 지정 형식 변환을 지원합니다

image

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

#### 8. DynamicSheetAttribute

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

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

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"); }
#### 삭제(대기 중)

#### 업데이트(대기 중)

Excel 유형 자동 확인

  • MiniExcel은 기본적으로 파일 확장자를 기반으로 xlsx 또는 csv 여부를 확인하지만, 부정확할 수 있으므로 수동으로 지정해 주시기 바랍니다.
  • 스트림은 어떤 엑셀 파일에서 왔는지 알 수 없으므로, 수동으로 지정해 주시기 바랍니다.
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

#### 참고

  • 기본적으로 string 타입으로 반환되며, 값은 숫자나 날짜/시간으로 변환되지 않습니다. 단, 강한 타입 제네릭으로 타입이 정의된 경우는 예외입니다.
#### 커스텀 구분자

기본 구분자는 , 이며, 커스터마이징을 위해 Seperator 속성을 수정할 수 있습니다.

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

#### 사용자 지정 줄 바꿈

기본값은 줄 바꿈 문자로 \r\n 이며, NewLine 속성을 수정하여 사용자 정의할 수 있습니다.

csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { NewLine='\n' }; MiniExcel.SaveAs(path, values,configuration: config);
#### 사용자 지정 인코딩

  • 기본 인코딩은 "바이트 순서 표시에서 인코딩 감지"입니다 (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 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에서 cancellationToken을 지원합니다。

기타

#### 1. 열거형(Enum)

엑셀과 속성 이름이 반드시 동일해야 하며, 시스템이 자동으로 매핑합니다(대소문자 구분 없음).

image

V0.18.0부터 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

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

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

시스템이 메모리를 제어하지 않지만, 더 빠른 저장 속도를 얻을 수 있습니다.

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

#### 7. 시트 크기 가져오기

var dim = MiniExcel.GetSheetDimensions(path);

예시:

#### 1. SQLite & Dapper 대용량 파일 SQL Insert 시 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();
    }
}
성능: image

#### 2. ASP.NET Core 3.1 또는 MVC 5 엑셀 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); }

20210419

#### 4. WebForm에서 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. 동적 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; } }

image

자주 묻는 질문(FAQ)

#### Q: 엑셀 헤더 제목이 클래스 속성 이름과 다를 때, 어떻게 매핑하나요?

A. ExcelColumnName 속성을 사용해 주세요.

image

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

#### Q. 시트 가시성에 대한 정보를 어떻게 쿼리하거나 내보낼 수 있나요?

A. 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초입니다.

image

#### 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 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; } } #### Q. 값이 비어 있을 때 Excel 내보내기 시 제목 없는 빈 엑셀이 생성되는 이유

MiniExcel은 API 작업을 단순화하기 위해 JSON.NET과 유사한 논리를 사용하여 값에서 동적으로 타입을 가져오기 때문에, 데이터가 없으면 타입을 알 수 없습니다. 자세한 이해를 위해 이슈 #133을 참고하세요.

image

강한 타입 & DataTable은 헤더를 생성하지만, Dictionary는 여전히 빈 엑셀을 생성합니다.

#### Q. 빈 행에서 foreach를 중단하는 방법은?

MiniExcel은 LINQ TakeWhile과 함께 사용하여 foreach 반복자를 중단할 수 있습니다.

Image

#### Q. 빈 행을 제거하는 방법은?

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

데이터테이블 :

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

#### Q. SaveAs(path,value)를 사용하여 기존 파일을 덮어쓰고 "The file ...xlsx already exists error" 오류 없이 저장하려면 어떻게 해야 하나요?

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은 Query만 지원합니다

참고자료

ExcelDataReader / ClosedXML / Dapper / ExcelNumberFormat

감사의 말씀

#### Jetbrains

jetbrains-variant-2

이 프로젝트를 위해 All product IDE를 무료로 제공해주셔서 감사합니다 (라이선스)

기여 및 후원 공유

링크 https://github.com/orgs/mini-software/discussions/754

기여자

--- Tranlated By Open Ai Tx | Last indexed: 2025-10-09 ---