Magicodes.IE.IO 的导入和导出 API

Magicodes.IE.IO 是独立的 XLSX 读写实现,不依赖 EPPlus 运行时。它更适合按行读取,以及新建工作簿后的顺序写入;模板渲染、已有文件编辑和完整 Excel 对象模型不在这条路径的范围内。

先安装

bash 复制代码
dotnet add package Magicodes.IE.IO

最简单的模型不需要额外配置:

csharp 复制代码
public sealed class Foo
{
    public string Name { get; set; } = "";
    public decimal Value { get; set; }
    public DateTime CreatedAt { get; set; }
}

var foos = new[]
{
    new Foo { Name = "foo-1", Value = 1.25m, CreatedAt = new DateTime(2026, 1, 1) },
};

先写一个文件

数据已经在内存中,目标又是本地文件时,直接传路径即可:

csharp 复制代码
Xlsx.Write("foo.xlsx", foos);

路径重载会创建并释放内部 FileStream。如果输出目标由调用方管理,例如文件流或 HTTP 响应流,就使用 Stream 重载:

csharp 复制代码
using var stream = File.Create("foo.xlsx");
Xlsx.Write(stream, foos);

Stream 重载不负责释放传入的流。HTTP 响应写出一部分 XLSX 后,后续发生异常也无法把已发送的字节收回;不要再往同一个响应写 JSON 错误信息。

如果需要"成功后才覆盖旧文件",在外层先写临时文件,成功后再替换目标文件。

返回 byte\[\]:适合小结果,不适合大文件

如果 API 必须返回字节数组,可以使用:

csharp 复制代码
byte[] bytes = Xlsx.ToBytes(foos);

这个入口会物化整个工作簿和最终的 byte[]。小文件、测试或确实需要字节数组的 API 可以使用它;下载接口和大数据导出通常应直接写入输出流。

可以这样理解:

场景 建议 API
小文件、单元测试、需要 byte\[\] Xlsx.ToBytes
写本地文件 Xlsx.Write(path, data)
已有文件流或 HTTP 响应流 Xlsx.Write(stream, data)
调用方已有缓冲区 Xlsx.Write(IBufferWriter<byte>, data)
数据源本身异步、不能全量物化 Xlsx.WriteAsync(stream, IAsyncEnumerable<T>)
导入并同步遍历 Xlsx.Read<T>(stream)
导入并异步遍历 Xlsx.ReadAsync<T>(stream)

再看一个最小导入

读取 API 返回惰性枚举,数据会在遍历时映射为行模型:

csharp 复制代码
using var stream = File.OpenRead("foo.xlsx");
var rows = Xlsx.Read<Foo>(stream).ToList();

读取完成时,Reader 默认会关闭输入流。如果同一个流后面还要继续使用,传入 leaveOpen: true。这和导出 Stream 重载的所有权规则正好相反。

配置列名、格式和过滤

默认情况下,列名和顺序由模型属性推断。需要改列名、格式或过滤行时,再配置 ExportProfile<T>

csharp 复制代码
Xlsx.Write("foo.xlsx", foos, p =>
{
    p.Column(x => x.Name, c => c.WithName("Name"));
    p.Column(x => x.Value, c => c.WithFormat("0.00"));
    p.Where(x => x.Value > 0);
});

ExportProfile<T> 还负责列顺序、隐藏列、样式、冻结表头、合并单元格和超链接。配置会在写入前冻结,后续每一行复用同一份列计划。

异步数据源

数据库查询已经返回 IAsyncEnumerable<T> 时,可以直接交给异步写入:

csharp 复制代码
await Xlsx.WriteAsync(
    response.Body,
    GetFoosAsync(cancellationToken),
    cancellationToken: cancellationToken);

这条路径不要求先把全部记录放进 List<T>。不过输出缓冲、压缩状态、SST 和数据源自己的预取仍会占用内存;"流式"只表示不必由调用方一次性物化全部行。

什么时候打开 AutoSst

字符串重复率高时,可以尝试启用 Shared String Table(SST):

csharp 复制代码
Xlsx.Write("foo.xlsx", foos, p => p.WithAutoSst(true));

实现会抽样判断是否启用 SST。它能减少重复文本,但也要维护全量唯一字符串表。状态、地区、产品名这类重复短文本可以尝试;订单号、时间戳和随机 ID 这类高基数字段默认保持 Inline String 往往更稳。具体规则见第 05 篇。

从旧导出入口迁移

如果项目之前使用 Magicodes.IE 的旧导出抽象,可以先从这些常见入口迁移:

旧思路 Magicodes.IE.IO
导出到路径 Xlsx.Write(path, data)
导出到 byte\[\] Xlsx.ToBytes(data)
导出到响应流 Xlsx.Write(stream, data)
异步数据源 Xlsx.WriteAsync(stream, asyncData)
列和格式配置 ExportProfile<T>

迁移前需要重新确认:

  1. 原来的调用方是否依赖完整工作簿一次性驻留内存;
  2. 输出流的所有权由谁负责释放;
  3. 原来使用的高级 Excel 特性是否在当前 IO 路径中有对应实现。

大文件优先考虑 StreamIAsyncEnumerable<T>,不要把所有导出都改成 ToBytes