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
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
- Minimiza el consumo de memoria, previniendo errores de falta de memoria (OOM) y evitando recolecciones completas de basura
- Permite operaciones de datos en tiempo real a nivel de fila para un mejor rendimiento en conjuntos de datos grandes
- Soporta LINQ con ejecución diferida, permitiendo paginación rápida y eficiente en memoria y consultas complejas
- Ligero, sin necesidad de Microsoft Office ni componentes COM+, y un tamaño de DLL inferior a 500KB
- API simple e intuitiva para leer/escribir/llenar excel
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
- Importar/Consultar Excel
- Exportar/Crear Excel
- Plantilla de Excel
- Nombre de columna de Excel/Índice/Atributo de ignorar
- Ejemplos
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();

#### 2. Ejecutar una consulta y mapearla a una lista de objetos dinámicos sin usar head [[Pruébalo]](https://dotnetfiddle.net/w5WD1J)
- la clave dinámica es
A.B.C.D..
var rows = MiniExcel.Query(path).ToList();// or
using (var stream = File.OpenRead(path))
{
var rows = stream.Query().ToList();
Assert.Equal("MiniExcel", rows[0].A);
Assert.Equal(1, rows[0].B);
Assert.Equal("Github", rows[1].A);
Assert.Equal(2, rows[1].B);
}
#### 3. 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 ...etcConsulta 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

#### 5. Consultar por nombre de hoja
MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
#### 6. Consultar todos los nombres de hoja y filasvar sheetNames = MiniExcel.GetSheetNames(path);
foreach (var sheetName in sheetNames)
{
var rows = MiniExcel.Query(path, sheetName: sheetName);
}
#### 7. Obtener columnasvar columns = MiniExcel.GetColumns(path); // e.g result : ["A","B"...]var cnt = columns.Count; // get column count
#### 8. Consulta dinámica convierte la fila a IDictionaryforeach(IDictionary row in MiniExcel.Query(path))
{
//..
}// or
var rows = MiniExcel.Query(path).Cast>();
// or Query specified ranges (capitalized)
// A2 represents the second row of column A, C3 represents the third row of column C
// If you don't want to restrict rows, just don't include numbers
var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3").Cast>();
#### 9. Consultar Excel y devolver DataTableNo 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);

#### 10. Especifique la celda para comenzar a leer los datos
csharp MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
csharp var config = new OpenXmlConfiguration() { FillMergedCells = true }; var rows = MiniExcel.Query(path, configuration: config);no usar relleno combinado#### 11. Rellenar celdas combinadas
Nota: La eficiencia es menor en comparación con
Razón: El estándar OpenXml coloca mergeCells al final del archivo, lo que obliga a recorrer el sheetxml dos veces

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

#### 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)
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);Puede usarSharedStringCacheSizepara cambiar el tamaño del archivo sharedString más allá del tamaño especificado para el almacenamiento en caché en disco.


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

#### 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}
});
csharp var values = new List#### 2.IEnumerable>
csharp MiniExcel.SaveAs(path, reader);Crear resultado de archivo :Recomendado| Columna1 | Columna2 | |------------|----------| | MiniExcel | 1 | | Github | 2 |
#### 3. IDataReader
, puede evitar cargar todos los datos en la memoria

Exportación de varias hojas con DataReader (recomendado por Dapper ExecuteReader)
csharp
using (var cnn = Connection)
{
cnn.Open();
var sheets = new Dictionarycsharp var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); var table = new DataTable(); { table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column2", typeof(decimal)); table.Rows.Add("MiniExcel", 1); table.Rows.Add("Github", 2); }#### 4. DatatableNo recomendado, cargará todos los datos en la memoriaDataTable usa Caption para el nombre de la columna primero, luego usa el nombre de la columna
MiniExcel.SaveAs(path, table);
csharp using (var connection = GetConnection(connectionString)) { var rows = connection.Query( new CommandDefinition( @"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2" , flags: CommandFlags.NoCache) ); // Note: QueryAsync will throw close connection exception MiniExcel.SaveAs(path, rows); }#### 5. Consulta DapperCommandDefinition + CommandFlags.NoCacheGracias @shaofing #552, por favor usa
El siguiente código cargará todos los datos en la memoriacsharp
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 Excelcsharp
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// 2. DataSet var sheets = new DataSet(); sheets.Add(UsersDataTable); sheets.Add(DepartmentDataTable); //.. MiniExcel.SaveAs(path, sheets);

#### 8. Opciones de TableStyles
Estilo predeterminado

Sin configuración de estilo
csharp
var config = new OpenXmlConfiguration()
{
TableStyles = TableStyles.None
};
MiniExcel.SaveAs(path, value,configuration:config);
csharp MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration() { AutoFilter = false });OpenXmlConfiguration.AutoFilter#### 9. AutoFiltro
Desde la versión v0.19.0,
puede habilitar o deshabilitar el AutoFiltro, el valor predeterminado estrue, y la forma de configurar el AutoFiltro es:
#### 10. Crear imagencsharp
var value = new[] {
new { Name="github",Image=File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png"))},
new { Name="google",Image=File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png"))},
new { Name="microsoft",Image=File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png"))},
new { Name="reddit",Image=File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png"))},
new { Name="statck_overflow",Image=File.ReadAllBytes(PathHelper.GetFile("images/statck_overflow_logo.png"))},
};
MiniExcel.SaveAs(path, value);
csharp var mergedFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx");byte[]#### 11. Exportación de archivo de matriz de bytes
Desde la versión 1.22.0, cuando el tipo de valor es
el sistema guardará la ruta del archivo en la celda por defecto, y al importar el sistema puede convertirlo abyte[]. Y si no desea usarlo, puede establecerOpenXmlConfiguration.EnableConvertByteArrayenfalse, lo cual puede mejorar la eficiencia del sistema.byte[]
Desde la versión 1.22.0, cuando el tipo de valor es
el sistema guardará la ruta del archivo en la celda por defecto, y al importar el sistema puede convertirlo abyte[]. Y si no desea usarlo, puede establecerOpenXmlConfiguration.EnableConvertByteArrayenfalse, lo cual puede mejorar la eficiencia del sistema.xlsx
#### 12. Combinar mismas celdas verticalmente
Esta funcionalidad solo es compatible con el formato
y combina celdas verticalmente entre las etiquetas @merge y @endmerge. Puede usar @mergelimit para limitar los límites de combinación de celdas verticalmente.
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:


Con límite de fusión:


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

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

xml
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);
csharp // 1. By POCO var value = new { Name = "Jack", CreateDate = new DateTime(2021, 01, 01), VIP = true, Points = 123 }; MiniExcel.SaveAsByTemplate(path, templatePath, value);{{nombre de variable}}Rellenar datos en plantilla de Excel
- La declaración es similar a la plantilla de Vue
, 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:
Resultado:
Código:
// 2. By Dictionary
var value = new Dictionary#### 2. Relleno de Datos IEnumerable
Nota1: Utilice el primer IEnumerable de la misma columna como base para rellenar la lista
Plantilla:

Resultado:

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#### 3. Relleno de Datos Complejos
Nota: Soporta múltiples hojas y uso de la misma variable
Plantilla:

Resultado:

csharp // 1. By POCO var value = new { title = "FooCompany", managers = new[] { new {name="Jack",department="HR"}, new {name="Loan",department="IT"} }, employees = new[] { new {name="Wade",department="HR"}, new {name="Felix",department="HR"}, new {name="Eric",department="IT"}, new {name="Keaton",department="IT"} } }; MiniExcel.SaveAsByTemplate(path, templatePath, value);
// 2. By Dictionary
var value = new Dictionary#### 4. 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

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

Resultado

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ódigocsharp
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 GithubPlantilla

Resultado

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 Agrupadoscsharp
var value = new Dictionarycsharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif##### 1. Con la etiqueta@groupy con la etiqueta@headerAntes
Después
##### 2. Con la etiqueta @group y sin la etiqueta @header
Antes
Después
##### 3. Sin la etiqueta @group
Antes
Después
#### 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.
Antes
Después

#### 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 Dictionarycsharp var config = new OpenXmlConfiguration() { IgnoreTemplateParameterMissing = false, }; MiniExcel.SaveAsByTemplate(path, templatePath, value, config)#### 10. Fórmulas$##### 1. Ejemplo Antepon tu fórmula con
y usa$enumrowstarty$enumrowendpara marcar las referencias al inicio y fin de filas enumerables:$
Cuando se renderice la plantilla, el prefijo
será eliminado y$enumrowstarty$enumrowendserán reemplazados por los números de fila de inicio y fin de la enumerable:$=SUM(C{{$enumrowstart}}:C{{$enumrowend}})
##### 2. Otras fórmulas de ejemplo:
| | | |--------------|-------------------------------------------------------------------------------------------| | Suma |
| | Promedio Alt.|$=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) / COUNT(C{{$enumrowstart}}:C{{$enumrowend}})| | Rango |$=MAX(C{{$enumrowstart}}:C{{$enumrowend}}) - MIN(C{{$enumrowstart}}:C{{$enumrowend}})|IgnoreTemplateParameterMissing#### 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,
puede controlar si lanzar una excepción o no.

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

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
#### 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ódigocsharp
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
La consulta admite conversión de formato personalizada

#### 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. ExcelColumnAttributeDesde 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. DynamicColumnAttributeDesde 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);

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

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");
}
csharp stream.SaveAs(excelType:ExcelType.CSV); //or stream.SaveAs(excelType:ExcelType.XLSX); //or stream.Query(excelType:ExcelType.CSV); //or stream.Query(excelType:ExcelType.XLSX);#### Eliminar(esperando)extensión de archivo#### Actualizar(esperando)
Comprobación automática del tipo de Excel
- MiniExcel comprobará si es xlsx o csv basándose en la
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 var config = new MiniExcelLibs.Csv.CsvConfiguration() { Seperator=';' }; MiniExcel.SaveAs(path, values,configuration: config);stringCSV
#### Nota
- Por defecto devuelve el tipo
, 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 propiedadSeperatorpara personalizarlo
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();
csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { NewLine='\n' }; MiniExcel.SaveAs(path, values,configuration: config);#### Salto de línea personalizado\r\nEl valor predeterminado es
como el carácter de nueva línea, puedes modificar la propiedadNewLinepara personalizarlo
#### 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 nuloDe 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 Taskcsharp public class Dto { public string Name { get; set; } public I49RYZUserType UserType { get; set; } }- v1.25.0 soportacancellationToken。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)
Desde la versión V0.18.0 se soporta la Descripción de Enum
public enum Type { [Description("General User")] V1, [Description("General Administrator")] V2, [Description("Super Administrator")] V3 }

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

#### 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ónvoid Main()
{
var rows = MiniExcel.Query(path); Console.WriteLine("==== No.1 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:1));
Console.WriteLine("==== No.50 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:50));
Console.WriteLine("==== No.5000 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:5000));
}
public static IEnumerable Page(IEnumerable en, int pageSize, int page)
{
return en.Skip(page * pageSize).Take(pageSize);
}

#### 4. 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 rolesComo 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; }
}

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

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

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

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

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.

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

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

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/754Contribuidores
--- Tranlated By Open Ai Tx | Last indexed: 2025-10-09 ---










