Web Analytics

MiniExcel

⭐ 3234 stars Spanish by mini-software

NuGet Estado de compilación estrella Estrellas de GitHub versión Preguntar en DeepWiki


Este proyecto es parte de la .NET Foundation y opera bajo su código de conducta.


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


Tus Estrellas o Donaciones pueden hacer que MiniExcel sea mejor


Introducción

MiniExcel es una herramienta sencilla y eficiente para procesar archivos Excel en .NET, diseñada específicamente para minimizar el uso de memoria.

Actualmente, la mayoría de los frameworks populares necesitan cargar todos los datos de un documento Excel en memoria para facilitar las operaciones, pero esto puede provocar problemas de consumo de memoria. El enfoque de MiniExcel es diferente: los datos se procesan fila por fila de manera secuencial, reduciendo el consumo original de potencialmente cientos de megabytes a solo unos pocos megabytes, previniendo eficazmente los problemas de falta de memoria (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;

Características

Vista previa de la versión 2.0

Estamos trabajando en una futura versión de MiniExcel, con una nueva API modular y enfocada, paquetes nuget separados para funcionalidades Core y Csv, soporte completo para consultas transmitidas asincrónicamente a través de IAsyncEnumerable, ¡y mucho más próximamente! Los paquetes estarán disponibles en pre-lanzamiento, así que siéntete libre de probarlos y darnos tu opinión.

Si lo haces, asegúrate también de revisar la nueva documentación y las notas de actualización.

Primeros pasos

Instalación

Puedes instalar el paquete desde NuGet

Notas de Lanzamiento

Por favor revisa las Notas de Lanzamiento

PENDIENTE

Por favor, consulte TODO

Rendimiento

El código para los benchmarks se puede encontrar en MiniExcel.Benchmarks.

El archivo utilizado para probar el rendimiento es Test1,000,000x10.xlsx, un documento de 32 MB que contiene 1,000,000 filas * 10 columnas cuyas celdas están llenas con la cadena "HelloWorld".

Para ejecutar todos los benchmarks use:

dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
Puede encontrar los resultados de los benchmarks para la última versión aquí.

Consulta/Importación de Excel

#### 1. Ejecutar una consulta y mapear los resultados a un IEnumerable fuertemente tipado [[Pruébalo]](https://dotnetfiddle.net/w5WD1J)

Se recomienda usar Stream.Query debido a una mejor eficiencia.

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. Ejecutar una consulta y mapearla a una lista de objetos dinámicos sin usar head [[Pruébalo]](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. Ejecutar una consulta con la primera fila como encabezado [[Pruébalo]](https://dotnetfiddle.net/w5WD1J)

nota : si hay columnas con el mismo nombre, se usa la última de la derecha

Excel de entrada :

| 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. Soporte de consultas Extensión LINQ First/Take/Skip ...etc

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

Rendimiento entre MiniExcel/ExcelDataReader/ClosedXML/EPPlus queryfirst

#### 5. Consultar por nombre de hoja

MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
#### 6. Consultar todos los nombres de hoja y filas

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

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

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

#### 8. Consulta dinámica convierte la fila a 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. Consultar Excel y devolver DataTable

No se recomienda, porque DataTable cargará todos los datos en la memoria y se perderá la característica de bajo consumo de memoria de MiniExcel.

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

image

#### 10. Especifique la celda para comenzar a leer los datos

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

#### 11. Rellenar celdas combinadas

Nota: La eficiencia es menor en comparación con no usar relleno combinado

Razón: El estándar OpenXml coloca mergeCells al final del archivo, lo que obliga a recorrer el sheetxml dos veces

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

soporta llenado de múltiples filas y columnas de longitud y ancho variables

image

#### 12. Lectura de archivos grandes mediante caché basada en disco (Disk-Base Cache - SharedString)

Si el tamaño de SharedStrings supera los 5 MB, MiniExcel por defecto usará caché en disco local, por ejemplo, 10x100000.xlsx (un millón de filas de datos). Cuando se desactiva la caché en disco, el uso máximo de memoria es de 195 MB, pero al activar la caché en disco solo necesita 65 MB. Nota: esta optimización implica cierto coste de eficiencia, por lo que en este caso el tiempo de lectura aumentará de 7,4 segundos a 27,2 segundos. Si no lo necesita, puede desactivar la caché en disco con el siguiente código:

csharp var config = new OpenXmlConfiguration { EnableSharedStringCache = false }; MiniExcel.Query(path,configuration: config)
Puede usar SharedStringCacheSize para cambiar el tamaño del archivo sharedString más allá del tamaño especificado para el almacenamiento en caché en disco.
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);
image

image

Crear/Exportar Excel

  • Debe ser un tipo no abstracto con un constructor público sin parámetros.
  • MiniExcel soporta la ejecución diferida de IEnumerable como parámetro. Si desea usar la menor memoria posible, por favor no llame a métodos como ToList.
ejemplo: ToList o no uso de memoria image

#### 1. Anónimo o fuertemente tipado [[Pruébalo]](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);
Crear resultado de archivo :

| Columna1 | Columna2 | |------------|----------| | MiniExcel | 1 | | Github | 2 |

#### 3. IDataReader

  • Recomendado, puede evitar cargar todos los datos en la memoria
csharp MiniExcel.SaveAs(path, reader);
image

Exportación de varias hojas con DataReader (recomendado por 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

  • No recomendado, cargará todos los datos en la memoria
  • DataTable usa Caption para el nombre de la columna primero, luego usa el nombre de la columna
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. Consulta Dapper

Gracias @shaofing #552, por favor usa 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); }
El siguiente código cargará todos los datos en la memoria

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. GuardarComo en MemoryStream  [[Pruébalo]](https://dotnetfiddle.net/JOen0e)

csharp using (var stream = new MemoryStream()) //support FileStream,MemoryStream ect. { stream.SaveAs(values); }
por ejemplo: api de exportación a Excel

csharp public IActionResult DownloadExcel() { var values = new[] { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} };

var memoryStream = new MemoryStream(); memoryStream.SaveAs(values); memoryStream.Seek(0, SeekOrigin.Begin); return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = "demo.xlsx" }; }

#### 7. Crear varias hojas

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. Opciones de TableStyles

Estilo predeterminado

image

Sin configuración de estilo

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

#### 9. AutoFiltro

Desde la versión v0.19.0, OpenXmlConfiguration.AutoFilter puede habilitar o deshabilitar el AutoFiltro, el valor predeterminado es true, y la forma de configurar el AutoFiltro es:

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

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. Exportación de archivo de matriz de bytes

Desde la versión 1.22.0, cuando el tipo de valor es byte[] el sistema guardará la ruta del archivo en la celda por defecto, y al importar el sistema puede convertirlo a byte[]. Y si no desea usarlo, puede establecer OpenXmlConfiguration.EnableConvertByteArray en false, lo cual puede mejorar la eficiencia del sistema.

image

Desde la versión 1.22.0, cuando el tipo de valor es byte[] el sistema guardará la ruta del archivo en la celda por defecto, y al importar el sistema puede convertirlo a byte[]. Y si no desea usarlo, puede establecer OpenXmlConfiguration.EnableConvertByteArray en false, lo cual puede mejorar la eficiencia del sistema.

image

#### 12. Combinar mismas celdas verticalmente

Esta funcionalidad solo es compatible con el formato xlsx y combina celdas verticalmente entre las etiquetas @merge y @endmerge. Puede usar @mergelimit para limitar los límites de combinación de celdas verticalmente.

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

Contenido del archivo antes y después de la fusión:

Sin límite de fusión:

Screenshot 2023-08-07 at 11 59 24

Screenshot 2023-08-07 at 11 59 57

Con límite de fusión:

Screenshot 2023-08-08 at 18 21 00

Screenshot 2023-08-08 at 18 21 40

#### 13. Omitir valores nulos

Nueva opción explícita para escribir celdas vacías para valores nulos:

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

imagen

xml Somebody once told me.

Comportamiento anterior:

csharp / ... /

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

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

imagen

xml Somebody once told me.
Funciona para valores nulos y DBNull.

#### 14. Inmovilizar paneles

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

Rellenar datos en plantilla de Excel

  • La declaración es similar a la plantilla de Vue {{nombre de variable}}, o el renderizado de colecciones {{nombre de colección.nombre de campo}}
  • El renderizado de colecciones soporta IEnumerable/DataTable/DapperRow
#### 1. Rellenado básico

Plantilla: image

Resultado: image

Código:

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. Relleno de Datos IEnumerable

Nota1: Utilice el primer IEnumerable de la misma columna como base para rellenar la lista

Plantilla: image

Resultado: image

Código:

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. Relleno de Datos Complejos

Nota: Soporta múltiples hojas y uso de la misma variable

Plantilla:

image

Resultado:

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. Rellenar rendimiento de Big Data

NOTA: Usar la ejecución diferida de IEnumerable en lugar de ToList puede ahorrar el máximo uso de memoria en MiniExcel

image

#### 5. Asignación automática del tipo de valor de celda

Plantilla

image

Resultado

image

Clase

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; } }
Código

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. Ejemplo :  Listar Proyectos de Github

Plantilla

image

Resultado

image

Código

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. Relleno de Datos Agrupados

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. Con la etiqueta @group y con la etiqueta @header

Antes

before_with_header

Después

after_with_header

##### 2. Con la etiqueta @group y sin la etiqueta @header

Antes

before_without_header

Después

after_without_header

##### 3. Sin la etiqueta @group

Antes

without_group

Después

without_group_after

#### 8. Sentencias If/ElseIf/Else dentro de la celda

Reglas:

  • Soporta DateTime, Double, Int con los operadores ==, !=, >, >=, <, <=.
  • Soporta String con los operadores ==, !=.
  • Cada sentencia debe ir en una línea nueva.
  • Se debe agregar un solo espacio antes y después de los operadores.
  • No debe haber una nueva línea dentro de las sentencias.
  • La celda debe estar exactamente en el formato que se muestra a continuación.
csharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif
Antes

if_before

Después

if_after

#### 9. DataTable como parámetro

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. Fórmulas

##### 1. Ejemplo Antepon tu fórmula con $ y usa $enumrowstart y $enumrowend para marcar las referencias al inicio y fin de filas enumerables:

image

Cuando se renderice la plantilla, el prefijo $ será eliminado y $enumrowstart y $enumrowend serán reemplazados por los números de fila de inicio y fin de la enumerable:

image

##### 2. Otras fórmulas de ejemplo:

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

#### 11. Otros

##### 1. Comprobación de clave de parámetro de plantilla

Desde la versión V1.24.0, por defecto se ignora la clave de parámetro faltante en la plantilla y se reemplaza con una cadena vacía, IgnoreTemplateParameterMissing puede controlar si lanzar una excepción o no.

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

Nombre de columna de Excel/Índice/Atributo de ignorar

#### 1. Especificar el nombre de la columna, el índice de columna, ignorar columna

Ejemplo de Excel

image

Código

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. Formato personalizado (ExcelFormatAttribute)

Desde la versión V0.21.0 se admite la clase que contiene el formato de método ToString(string content)

Clase

csharp public class Dto { public string Name { get; set; }

[ExcelFormat("MMMM dd, yyyy")] public DateTime InDate { get; set; } }

Código

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

image

La consulta admite conversión de formato personalizada

image

#### 3. Establecer el ancho de columna (ExcelColumnWidthAttribute)

csharp public class Dto { [ExcelColumnWidth(20)] public int ID { get; set; } [ExcelColumnWidth(15.50)] public string Name { get; set; } }
#### 4. Varios nombres de columnas que se asignan a la misma propiedad.

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

Desde la versión 1.24.0, el sistema admite 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

Desde la versión V1.26.0, se pueden simplificar múltiples atributos así:

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

Desde la versión V1.26.0, podemos establecer los atributos de Column de forma dinámica

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

Desde la versión V1.31.4 podemos establecer los atributos de la hoja de manera dinámica. Podemos configurar el nombre y el estado (visibilidad) de la hoja.

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

También podemos usar el nuevo atributo 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; } }
### Añadir, Eliminar, Actualizar

#### Añadir

v1.28.0 admite la inserción de N filas de datos CSV después de la última fila

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 soporta insertar una nueva hoja de Excel en un libro existente

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"); }
#### Eliminar(esperando)

#### Actualizar(esperando)

Comprobación automática del tipo de Excel

  • MiniExcel comprobará si es xlsx o csv basándose en la extensión de archivo por defecto, pero puede haber inexactitudes, por favor especifíquelo manualmente.
  • No se puede saber de qué excel proviene un Stream, por favor especifíquelo manualmente.
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

#### Nota

  • Por defecto devuelve el tipo string, y el valor no se convertirá a números o fecha/hora, a menos que el tipo esté definido mediante tipado fuerte genérico.
#### Separador personalizado

El valor predeterminado es , como separador, puedes modificar la propiedad Seperator para personalizarlo

csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { Seperator=';' }; MiniExcel.SaveAs(path, values,configuration: config);
Desde la versión V1.30.1 se admite la función para personalizar el separador (gracias @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();
#### Salto de línea personalizado

El valor predeterminado es \r\n como el carácter de nueva línea, puedes modificar la propiedad NewLine para personalizarlo

csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { NewLine='\n' }; MiniExcel.SaveAs(path, values,configuration: config);
#### Codificación personalizada

  • La codificación predeterminada es "Detectar la codificación a partir de las marcas de orden de bytes" (detectEncodingFromByteOrderMarks: true)
  • Si tienes requisitos de codificación personalizados, por favor modifica la propiedad 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);

#### Leer cadena vacía como nulo

De forma predeterminada, los valores vacíos se asignan a string.Empty. Puede modificar este comportamiento

csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { ReadEmptyStringAsNull = true };
### DataReader

#### 1. GetReader Desde la versión 1.23.0, puedes usar 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 soporta Async (gracias a 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 soporta cancellationToken

Otros

#### 1. Enum

Asegúrese de que el nombre en Excel y la propiedad sean iguales, el sistema hará el mapeo automático (no distingue mayúsculas y minúsculas)

image

Desde la versión V0.18.0 se soporta la Descripción de Enum

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

Desde la versión 1.30.0 se admite la conversión de la descripción de Excel a Enum, gracias a @KaneLeung

#### 2. Convertir CSV a XLSX o Convertir XLSX a 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 Personalizado

Desde la versión 1.22.0, puedes personalizar CultureInfo como se muestra a continuación, el valor predeterminado del sistema es CultureInfo.InvariantCulture.

var config = new CsvConfiguration()
{
    Culture = new CultureInfo("fr-FR"),
};
MiniExcel.SaveAs(path, value, configuration: config);

// or MiniExcel.Query(path, configuration: config);

#### 4. Tamaño de Búfer Personalizado

    public abstract class Configuration : IConfiguration
    {
        public int BufferSize { get; set; } = 1024 * 512;
    }
#### 5. ModoRápido

El sistema no controlará la memoria, pero puedes obtener una velocidad de guardado más rápida.

var config = new OpenXmlConfiguration() { FastMode = true };
MiniExcel.SaveAs(path, reader,configuration:config);
#### 6. Añadir imágenes en lote (MiniExcel.AddPicture)

Por favor, añada las imágenes antes de generar los datos de las filas en lote, o el sistema usará mucha memoria al llamar a 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);
Imagen

#### 7. Obtener dimensiones de las hojas

var dim = MiniExcel.GetSheetDimensions(path);

Ejemplos:

#### 1. SQLite y Dapper Archivo de gran tamaño Insertar SQL para evitar OOM

nota: por favor, no llames a los métodos ToList/ToArray después de Query, ya que cargará todos los datos en memoria

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

#### 2. ASP.NET Core 3.1 o MVC 5 Descargar/Subir Excel Xlsx API Demo Pruébalo

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. Consulta de paginación

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. Exportar Excel en WebForm usando 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. Gestión dinámica de i18n multilenguaje y de autoridad por roles

Como en el ejemplo, cree un método para manejar la gestión de i18n y permisos, y use yield return para devolver IEnumerable> para lograr efectos de procesamiento dinámicos y de bajo consumo de memoria

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

Preguntas frecuentes

#### P: El título del encabezado de Excel no es igual al nombre de la propiedad de la clase, ¿cómo hacer el mapeo?

R. Por favor, utilice el atributo ExcelColumnName

image

#### P. ¿Cómo consultar o exportar múltiples hojas?

R. Método GetSheetNames con el parámetro de hoja 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

#### P. ¿Cómo consultar o exportar información sobre la visibilidad de las hojas?

R. Método 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. ¿Usar Count cargará todos los datos en la memoria?

No, la prueba de imagen tiene 1 millón de filas*10 columnas de datos, el uso máximo de memoria es <60MB, y tarda 13.65 segundos

image

#### P. ¿Cómo usa Query los índices enteros?

El índice predeterminado de Query es la clave de cadena: A,B,C.... Si desea cambiar a un índice numérico, cree el siguiente método para convertir

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. ¿Por qué se genera un archivo Excel sin título y vacío cuando el valor está vacío al exportar a Excel?

Debido a que MiniExcel utiliza una lógica similar a JSON.NET para obtener dinámicamente el tipo a partir de los valores y así simplificar las operaciones de la API, el tipo no se puede conocer sin datos. Puedes consultar el issue #133 para entenderlo.

image

Los tipos fuertes y DataTable generarán encabezados, pero Dictionary seguirá generando un Excel vacío

#### P. ¿Cómo detener el foreach cuando hay una fila en blanco?

MiniExcel se puede utilizar con LINQ TakeWhile para detener el iterador foreach.

Image

#### P. ¿Cómo eliminar filas vacías?

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. ¿Cómo usar SaveAs(path,value) para reemplazar un archivo existente sin que aparezca el error "El archivo ...xlsx ya existe"?

Por favor, utilice la clase Stream para personalizar la lógica de creación de archivos, por ejemplo:

`C# using (var stream = File.Create("Demo.xlsx")) MiniExcel.SaveAs(stream,value);

o, desde la versión V1.25.0, SaveAs admite el parámetro overwriteFile para habilitar/deshabilitar la sobrescritura de archivos existentes

csharp MiniExcel.SaveAs(path, value, overwriteFile: true); ``

Limitaciones y advertencias

  • Actualmente no se admite xls ni archivos cifrados
  • xlsm solo admite Consulta

Referencias

ExcelDataReader / ClosedXML / Dapper / ExcelNumberFormat

Agradecimientos

#### Jetbrains

jetbrains-variant-2

Gracias por proporcionar un IDE All Products gratuito para este proyecto (Licencia)

Donaciones compartidas de contribución

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

Contribuidores

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