背景介绍
报表绕不开 Excel。传统方案用 Microsoft.Office.Interop,需要安装 Office,且进程管理复杂。MiniExcel 是一个轻量级库(< 1MB),通过直接操作 ZIP 压缩包(.xlsx 本质是 ZIP)实现读写,无需 Office 环境,支持 .NET Core / .NET Framework。
本篇覆盖:
- 基本读写(DataTable / List ⇄ Excel)
- 写入优化(避免内存膨胀)
- 样式设置(列宽、行高、背景色)
代码实现
1. 安装与基础读写
bash
dotnet add package MiniExcel
csharp
using MiniExcelLibs;
// ===== 读 Excel =====
public void ReadExcel()
{
// 读取整个 sheet
var rows = MiniExcel.Query("report.xlsx").ToList();
// 读取指定 sheet
var rows2 = MiniExcel.Query("report.xlsx", sheetName: "Sheet2").ToList();
// 读取为 DataTable(便于筛选)
var dt = MiniExcel.QueryAsDataTable("report.xlsx");
foreach (DataRow row in dt.Rows)
{
Console.WriteLine($"{row["DeviceId"]} - {row["Value"]}");
}
}
// ===== 写 Excel =====
public void WriteSimpleExcel()
{
var devices = new[]
{
new { DeviceId = "INJ001", Temperature = 85.5, Pressure = 1.2 },
new { DeviceId = "INJ002", Temperature = 82.3, Pressure = 1.1 }
};
// 最简单写法:List 直接写
MiniExcel.SaveAs("output.xlsx", devices);
// 指定 sheet 名
MiniExcel.SaveAs("output.xlsx", devices, sheetName: "生产数据");
}
2. 写入优化:分批写入大文件
csharp
public void WriteLargeFile(string filePath, IEnumerable<ReportRow> rows)
{
// MiniExcel 默认会把所有数据加载到内存
// 大数据量时需要分批处理
var batchSize = 5000;
var batch = new List<ReportRow>();
using (var stream = File.Create(filePath))
{
bool firstBatch = true;
foreach (var row in rows)
{
batch.Add(row);
if (batch.Count >= batchSize)
{
if (firstBatch)
{
// 第一批:创建文件 + 写入表头
stream.Seek(0, SeekOrigin.Begin);
MiniExcel.SaveAs(stream, batch, printHeader: true);
firstBatch = false;
}
else
{
// 后续批次:追加到已有 sheet(通过 sheetName)
MiniExcel.AppendExcel(stream, batch, sheetName: "Data");
}
batch.Clear();
}
}
// 处理剩余数据
if (batch.Count > 0)
{
if (firstBatch)
MiniExcel.SaveAs(stream, batch, printHeader: true);
else
MiniExcel.AppendExcel(stream, batch, sheetName: "Data");
}
}
}
public class ReportRow
{
public string DeviceId { get; set; }
public DateTime Timestamp { get; set; }
public double Temperature { get; set; }
public double Pressure { get; set; }
}
3. 使用模板生成报表
csharp
// 报表模板(template.xlsx)包含:
// - A1: 标题(已合并单元格)
// - A3: 列头(DeviceId, Timestamp, Temperature...)
// - A4 以下:数据区(空着,等我们填充)
public void GenerateFromTemplate()
{
string templatePath = "template.xlsx";
string outputPath = "report_20240421.xlsx";
// 1. 复制模板
File.Copy(templatePath, outputPath, overwrite: true);
// 2. 读取模板内容(不覆盖格式)
var template = MiniExcel.QueryAsDataTable(outputPath);
// 3. 准备数据
var data = GetProductionData(); // List<ReportRow>
// 4. 写入数据(从 A4 开始)
MiniExcel.SaveAsByTemplate(outputPath, new
{
Title = "2024年4月21日 生产报表",
GenerateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm"),
Data = data // 这个 key 会对应模板中的 Data 区域
});
}
4. 样式设置
csharp
public void WriteWithStyle()
{
var rows = new List<Dictionary<string, object>>
{
new Dictionary<string, object> { ["DeviceId"] = "INJ001", ["Value"] = 85.5 },
new Dictionary<string, object> { ["DeviceId"] = "INJ002", ["Value"] = 82.3 }
};
// 设置列配置
var columns = new Dictionary<string, MiniExcelColumnAttribute>
{
["DeviceId"] = new MiniExcelColumnAttribute { Name = "设备编号", Width = 15 },
["Value"] = new MiniExcelColumnAttribute { Name = "测量值", Width = 12, Format = "0.00" }
};
// 写入并设置列宽
MiniExcel.SaveAs("styled.xlsx", rows, configurations: columns);
}
// 自定义样式(需要底层操作)
public void WriteWithCustomStyle(string filePath)
{
var config = new MiniExcelConfiguration
{
SheetName = "Report"
};
using var stream = File.Create(filePath);
stream.Seek(0, SeekOrigin.Begin);
// MiniExcel 支持通过 .xlsx 的 shared strings 和 styles.xml
// 完整样式控制建议用 ClosedXML 或 EPPlus
}
5. 读取时处理合并单元格
csharp
public void ReadMergedCells()
{
// MiniExcel 默认会返回合并单元格的值到每一行
// 如果需要识别合并区域,手动解析
var cells = MiniExcel.GetCells("merged.xlsx").ToList();
var mergedRanges = cells
.Where(c => c.MergeCount > 0)
.Select(c => new
{
c.Value,
StartRow = c.Row,
EndRow = c.Row + c.MergeCount - 1,
StartCol = c.Column,
EndCol = c.Column + 1 // 简化:默认横向合并
})
.ToList();
foreach (var m in mergedRanges)
{
Console.WriteLine($"合并区域: {m.Value} ({m.StartRow}-{m.EndRow})");
}
}