VassasCo.Utility.ExcelMapper 2.3.0

dotnet add package VassasCo.Utility.ExcelMapper --version 2.3.0
                    
NuGet\Install-Package VassasCo.Utility.ExcelMapper -Version 2.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="VassasCo.Utility.ExcelMapper" Version="2.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="VassasCo.Utility.ExcelMapper" Version="2.3.0" />
                    
Directory.Packages.props
<PackageReference Include="VassasCo.Utility.ExcelMapper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add VassasCo.Utility.ExcelMapper --version 2.3.0
                    
#r "nuget: VassasCo.Utility.ExcelMapper, 2.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package VassasCo.Utility.ExcelMapper@2.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=VassasCo.Utility.ExcelMapper&version=2.3.0
                    
Install as a Cake Addin
#tool nuget:?package=VassasCo.Utility.ExcelMapper&version=2.3.0
                    
Install as a Cake Tool

VassasCo.Utility.ExcelMapper

原生对象到 Excel/CSV 的映射引擎(桌面端/服务端通用,无需 COM/VSTO)。
支持 侵入式特性 与 非侵入式 Fluent 两套并行配置方式,覆盖导出、导入、分页、异步、条件格式、大数据流式等场景。

MIT


目录


功能总览

类别 能力
配置方式 侵入式特性(贴在实体上) + 非侵入式 Fluent(EntityMap<T> 外部声明),可混用
导出格式 Excel (.xlsx) 与 CSV,共用同一套列配置
嵌套结构 自动递归展开嵌套对象,生成多层合并表头;循环引用自动标记不递归
集合处理 子 Sheet(默认)/ 原地摊平 / 仅占位文本 / JSON 单行存储 四种模式
分页 多 Sheet 分页 / 多文件分页,可按特性或选项配置
异步 ToFileAsync / ToStreamAsync,支持 CancellationToken 与 IProgress<int>
条件格式 大于/小于/等于/区间等运算符 + 字体色/背景色/加粗,支持 Excel 原生 CF 与服务端求值双路径
安全 长数字自动转文本、身份证/手机号敏感字段识别、CSV 公式注入防护、超长截断、路径校验
导入 Excel/CSV → 对象,安全类型转换 + 逐行错误收集
引擎 ClosedXML DOM(默认,样式最全)/ OpenXML SAX 流式(大数据低内存)自动切换
字典导出 IDictionary 宽表/竖表两种布局
图片嵌入 数据列渲染为嵌入式图片(文件路径 / byte[] / Stream),或整表固定位置图片(Logo / 水印),自动适配行高列宽
报表布局 数据表之外写入标题、表前/表后自由排版区,支持跨列合并、字号与对齐,用于单据与检测报告
值转换 导出前改写单元格值:Fluent lambda 或可复用特性转换器(支持构造器实参),内置判定文案、脱敏、空值占位
报表布局 数据表之外写入标题与元信息行:WithTitle 跨列大标题、WithPreamble / WithPostamble 自由排版行,支持横向合并、字号、加粗、对齐与配色

目标框架与依赖

目标框架 ClosedXML DocumentFormat.OpenXml System.Text.Json
net472 0.102.3 2.19.0 8.0.5
netstandard2.0 0.102.3 2.19.0 8.0.5
net6.0 0.105.0 3.1.1 —
net8.0 0.105.0 3.1.1 —

新增 net472:供 .NET Framework 4.8 / 4.7.2 等传统桌面项目(如 WinForms 上位机)直接引用。 关键收益:net472 会命中 ClosedXML 自带的 net46 原生资产,避免回落到 netstandard2.0 资产, 从而不再拖入 System.Memory / System.Buffers / System.Runtime.CompilerServices.Unsafe 等一长串传递依赖, 也省去在 packages.config 旧式项目里手写大量 binding redirect 的麻烦。 构建期通过 Microsoft.NETFramework.ReferenceAssemblies(PrivateAssets=all)提供目标包,不会作为依赖传递给使用方。

DataAnnotations([DisplayName]/[Display]/[DataType])通过反射全名识别,无硬依赖,已有特性自动生效。


快速开始

using VassasCo.Utility;

// 一行导出(配置全部来自实体特性/约定)
ExcelMapper.SaveToFile(people, "report.xlsx");

// Fluent 配置导出
ExcelMapper.Build(people)
    .WithSheetName("员工表")
    .WithStyle(s => s.HeaderBackgroundColor = "#2E75B6")
    .ToFile("report.xlsx");

// 导入
var result = ExcelMapper.Import<Person>("report.xlsx");
foreach (var p in result.Items) Console.WriteLine(p.Name);
foreach (var err in result.Errors) Console.WriteLine(err);

侵入式:特性配置

在实体类上贴特性,调用方零配置:

[ExcelSheet(Name = "员工表", PageSize = 1000, AutoFilter = true)]
public class Person
{
    [ExcelColumn(Name = "编号", Order = 1, Width = 10)]
    public int Id { get; set; }

    [ExcelColumn(Name = "姓名", Order = 2, Width = 15)]
    public string Name { get; set; } = "";

    // 身份证号强制文本,防止科学计数法丢精度
    [ExcelColumn(Name = "身份证号", ForceText = true, Width = 22)]
    public string IdCard { get; set; } = "";

    [ExcelColumn(Name = "入职日期", Format = "yyyy-MM-dd")]
    public DateTime HireDate { get; set; }

    [ExcelColumn(Name = "绩效", Order = 3, Format = "N2")]
    [ConditionalFormat(ConditionOperator.GreaterThan, 90, FillColor = "#00B050", Bold = true)]
    [ConditionalFormat(ConditionOperator.LessThan, 60, FontColor = "#FF0000")]
    public double Score { get; set; }

    [ExcelIgnore]
    public string InternalNote { get; set; } = "";

    // byte[] 默认输出 Base64
    public byte[]? Avatar { get; set; }
}

// 导出
ExcelMapper.SaveToFile(people, "employees.xlsx");

可用特性

特性 级别 说明
[ExcelSheet] 类型 Sheet 名称、PageSize、ArrayRender、AutoFilter、UseTable
[ExcelDisplay] 属性 仅设列标题(轻量)
[ExcelColumn] 属性 Name/Order/Format/ForceText/Width/WrapText/AsHyperlink
[ExcelIgnore] 属性 不参与导出/导入
[ConditionalFormat] 属性 条件格式(AllowMultiple,可多条)
[ExcelConverter] 属性 自定义值转换器(实现 IExcelValueConverter),可附加构造器实参

配置优先级

Fluent EntityMap > 本包特性 > DataAnnotations 约定 > 属性名

非侵入式:Fluent 配置

不修改业务类,在外部用 Lambda 声明映射:

// 注意:Property() 返回的是 PropertyMapping(单列配置器),不能再链式调用下一个 Property(),
// 必须逐条声明(或用 Builder 的 WithMap(Action<EntityMap<T>>) 形式)。
var map = new EntityMap<Person>();
map.Property(p => p.Id).HasName("编号").Order(1).Width(10);
map.Property(p => p.Name).HasName("姓名").Order(2).Width(15);
map.Property(p => p.IdCard).HasName("身份证号").AsText().Width(22);
map.Property(p => p.HireDate).Format("yyyy-MM-dd");
map.Property(p => p.Score).HasName("绩效").Format("N2")
    .Rule(ConditionOperator.GreaterThan, 90, s => s.Fill("#00B050").Bold())
    .Rule(ConditionOperator.LessThan, 60, s => s.Font("#FF0000"));
map.Ignore(p => p.InternalNote);

ExcelMapper.Build(people)
    .WithMap(map)
    .ToFile("employees.xlsx");

支持嵌套属性路径:

.Property(p => p.Department.Manager.Name).HasName("部门经理")

注意:上面的写法只改了叶子 Name 的标题;中间节点 Department、Manager 作为分组表头,默认显示属性名。要自定义分组表头,需分别对中间节点命名(或用 [ExcelDisplay] 特性):

.Property(p => p.Department).HasName("部门")                // 分组表头
.Property(p => p.Department.Manager).HasName("经理")        // 分组表头
.Property(p => p.Department.Manager.Name).HasName("部门经理"); // 叶子

生成表头:部门 → 经理 → 部门经理。同理 .Ignore(p => p.Department) 会忽略整个子树。

PropertyMapping 链式方法

方法 说明
HasName(string) 列标题
Order(int) 列顺序
AsText() 强制文本(身份证/手机号/长 ID)
Format(string) 数字/日期格式
Width(double) 固定列宽
WrapText() 自动换行
Hyperlink() 作为超链接写入
Ignore() 忽略该属性
ConvertUsing(Func<TProp, object?>) 自定义值转换
Rule(op, value, Action<ConditionalStyleBuilder>) 条件格式

行级规则

var map = new EntityMap<Order>()
    .WhenRow(o => o.IsOverdue, s => s.Font("#FF0000").Bold())
    .WhenRow(o => o.Amount > 10000, s => s.Fill("#FFF2CC"));

行级规则始终服务端求值,写为静态样式。


嵌套对象与集合

嵌套对象自动递归展开,生成多层合并表头:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public Department? Department { get; set; }   // 嵌套 → 合并表头
    public List<Address> Addresses { get; set; }   // 集合
}

public class Department
{
    public string DeptName { get; set; } = "";
    public Person? Manager { get; set; }  // 循环引用 → 自动标记"(循环引用)"
}

生成的表头:

| Id | Name | Department         | Addresses           |
|    |      | DeptName | Manager | (→ 子 Sheet "Addresses") |

集合渲染模式(ArrayRender)

模式 行为
ChildSheet(默认) 集合元素写入独立子 Sheet,主表显示 [n items → Sheet "Addresses"]
FlattenInPlace 摊平到当前 Sheet,父列重复(仅允许单个集合属性,多集合抛配置异常)
PlaceholderOnly 仅显示 [n items] 占位文本
Json 序列化为 JSON 字符串存入单元格(保留数据、单行展示、可导入还原)

Json 模式把整个集合写进单个单元格,不产生子 Sheet/多行,适合 List<string>/List<int> 等简单集合或需要整行紧凑展示的场景;复杂对象集合的 JSON 可能超过单元格 32767 字符上限而被截断。


分页

// 方式一:选项配置
ExcelMapper.Build(people)
    .WithOptions(o =>
    {
        o.PageSize = 5000;               // 每页 5000 行
        o.PageMode = PageMode.MultipleSheets; // 多 Sheet(默认)
    })
    .ToFile("report.xlsx");
// → Sheet: Person, Person_2, Person_3 ...

// 方式二:特性配置
[ExcelSheet(PageSize = 5000)]

// 多文件分页
ExcelMapper.Build(people)
    .WithOptions(o =>
    {
        o.PageSize = 5000;
        o.PageMode = PageMode.MultipleFiles; // 每页一个文件
    })
    .ToFile("report.xlsx");
// → report_1.xlsx, report_2.xlsx, report_3.xlsx ...
PageMode 行为
None 不分页(超单 Sheet 上限抛异常)
MultipleSheets 同一工作簿多 Sheet(默认)
MultipleFiles 每页一个 .xlsx 文件

异步导出(取消 / 进度)

面向桌面端,不阻塞 UI:

using var cts = new CancellationTokenSource();
var progress = new Progress<int>(rows => {
    Dispatcher.Invoke(() => ProgressBar.Value = rows);
});

await ExcelMapper.SaveToFileAsync(people, "report.xlsx", cts.Token, progress);

// 取消
cts.Cancel();

所有导出方法均有异步重载:SaveToFileAsync / SaveToStreamAsync / WriteCsvAsync / DictionaryToFileAsync。


导出诊断信息

所有 Excel 导出方法返回 ExportResult,桌面端可据此展示结果:

var result = ExcelMapper.SaveToFile(people, "report.xlsx");

Console.WriteLine($"导出 {result.RowCount} 条记录,共 {result.SheetCount} 个工作表");
Console.WriteLine($"引擎:{result.Engine},耗时 {result.Elapsed.TotalSeconds:F2} 秒");
Console.WriteLine($"文件数:{result.FileCount}");
属性 说明
RowCount 数据记录总数(不含表头、不含集合子 Sheet 展开)
SheetCount 工作表总数(含集合子 Sheet)
Engine 实际使用的导出引擎(ClosedXml / OpenXmlStreaming)
Elapsed 导出耗时
FileCount 生成的文件数(多文件分页时 >1)
FilePaths 生成的文件路径列表(导出到流时为空)

条件格式

侵入式

[ConditionalFormat(ConditionOperator.GreaterThan, 90, FillColor = "#00B050", Bold = true)]
[ConditionalFormat(ConditionOperator.Between, 60, 90, FontColor = "#FFC000")]
[ConditionalFormat(ConditionOperator.LessThan, 60, FontColor = "#FF0000")]
public double Score { get; set; }

非侵入式

.Property(p => p.Score)
    .Rule(ConditionOperator.GreaterThan, 90, s => s.Fill("#00B050").Bold())
    .Rule(ConditionOperator.LessThan, 60, s => s.Font("#FF0000"))

运算符

GreaterThan / GreaterOrEqual / LessThan / LessOrEqual / Equal / NotEqual / Contains / Between

简单规则(除 Contains 外)默认翻译为 Excel 原生条件格式(PreferNativeConditionalFormat=true),可在应用端动态变色。流式引擎或关闭此选项时改为服务端求值写入静态样式。


图片插入

支持两类图片:数据列图片(每行一张,数据驱动)与 固定位置图片(整表一张,如 Logo / 水印)。

侵入式:特性

public class TestResult
{
    [ExcelColumn(Name = "产品图", Order = 10, Width = 30)]
    [ExcelImage(Width = 120, Height = 90, SizeMode = ImageSizeMode.Fit)]
    public string PhotoPath { get; set; } = "";
}

非侵入式:Fluent

var map = new EntityMap<TestResult>()
    .Property(x => x.PhotoPath).HasName("产品图")
        .AsImage(img => { img.Width = 120; img.Height = 90; img.SizeMode = ImageSizeMode.Fit; });

固定位置图片

ExcelMapper.Build(results)
    .WithMap(map)
    .WithPicture(p => p.FromFile(@"C:\res\logo.png").At("A1").Size(140, 45))
    .WithPicture(p => p.FromBytes(watermarkBytes).At(2, 8).Placement(ImagePlacement.Floating))
    .ToFile("report.xlsx");

图片来源优先级

  1. FromBytes / 属性值为 byte[](最优先,不触碰磁盘)
  2. FromFile / 属性值为 string 路径 —— 相对路径以程序所在目录为基准,不受当前工作目录被 OpenFileDialog 等改动的影响

缺失兜底

策略 行为
MissingImagePolicy.Skip(默认) 跳过,单元格留空,不影响整个导出
MissingImagePolicy.WriteSourceText 把来源(通常是文件路径)写入单元格,便于事后排查
MissingImagePolicy.Throw 抛 ExcelMappingException

引擎限制

图片只能在 DOM 引擎(ClosedXML) 下写入。 Engine = Auto(默认)时,检测到文档含图片会自动强制使用 DOM 引擎; 若显式指定 Engine = OpenXmlStreaming,图片会被跳过(流式 SAX 只能顺序写入,无法回填图片部件)。


报表布局

业务单据类报表通常不是一张干净的二维表:标题要跨列贯通,表头上方要放单据编号、制表日期、负责人等元信息, 表尾还要留合计或签名栏。这类内容不属于任何数据列,因此用列映射表达不了。

本库把一张工作表划分为四个区域:

区域 位置 数据来源
表前区 第 1 行起 WithTitle / WithPreamble(自由排版)
表头 紧随表前区 列映射自动生成(含多层合并)
数据区 紧随表头 对象集合
表后区 数据区之后 WithPostamble(自由排版)

未使用任何布局方法时,表头仍从第 1 行开始,行为与旧版本完全一致。

标题与元信息行

ExcelMapper.Build(orders)
    .WithSheetName("订单汇总")
    .WithTitle("月度订单汇总表", fontSize: 20)
    .WithPreamble(r => r
        .Cell("客户").Span(2).Cell(customerName).Span(2).NextRow()
        .Cell("制表日期").Span(2).Cell(DateTime.Today.ToString("yyyy-MM-dd")).Span(2).NextRow()
        .Cell("备注").Span(2).Cell("含税金额,单位:元").Span(2))
    .WithPostamble(r => r
        .Cell("合计").Span(2).Bold().Align(CellAlign.Right)
        .Cell(total.ToString("N2")).Span(2).Align(CellAlign.Right))
    .ToFile("report.xlsx");

Span(n) 为当前单元格横向合并的列数;写 Span(0) 表示从当前列一直合并到表格最后一列, 因此标题类内容无需预知表格有多少列。超出表格宽度的部分会被自动收敛,不会写到数据区之外。

ReportRowBuilder 链式方法

方法 说明
Cell(text) 追加一个单元格,后续样式设置作用于该单元格
Span(columns) 横向合并列数;<= 0 表示跨到表格最后一列
FontSize(size) 字号(磅)
Bold(bool) 加粗,默认 true
Align(CellAlign) 水平对齐:Default / Left / Center / Right
FontColor(hex) / FillColor(hex) 字体色 / 填充底色(如 "#C00000")
NextRow() 结束当前行;省略时最后一行会自动提交

WithPreamble 与 WithPostamble 均可多次调用,按调用顺序排列;WithTitle 声明的标题始终排在表前区最顶部。

工作表级字体

ExcelMapper.Build(orders)
    .WithStyle(s => { s.FontName = "等线"; s.FontSize = 11; s.HeaderFontSize = 12; })
    .ToFile("report.xlsx");

引擎限制

布局行依赖随机写入与单元格合并,只能在 DOM 引擎(ClosedXML) 下写入。 Engine = Auto(默认)时,检测到文档含布局行会自动强制使用 DOM 引擎; 若显式指定 Engine = OpenXmlStreaming,会在写入前抛 ExcelPlanValidationException, 避免导出一份缺少标题与表尾的文件。


值转换器

在导出前改写最终写入单元格的值。用于把内部表示换成人类可读文案,避免为了展示目的额外增加只读计算属性。

两种写法

Fluent(推荐,非侵入) —— 强类型 lambda,实体类保持干净:

var map = new EntityMap<Result>();

map.Property(r => r.Passed)
   .HasName("判定")
   .ConvertUsing(v => v ? "OK" : "NG");

ExcelMapper.Build(results).WithMap(map).ToFile(path);

特性(侵入式) —— 复用性好,转换器与实体一起走:

public sealed class TrendConverter : IExcelValueConverter
{
    public object? Convert(object? value) => value is int n && n > 0 ? "上升" : "下降";
}

public sealed class Result
{
    [ExcelConverter(typeof(TrendConverter))]
    public int Delta { get; set; }
}

两者同时存在时 Fluent 覆盖特性。

构造器实参复用

同一种转换器可通过不同的构造器实参产生不同输出,无需为每种文案新建类:

public sealed class Verdict
{
    [ExcelConverter(typeof(BooleanTextConverter), "合格", "不合格")]
    public bool OpticalJudge { get; set; }

    [ExcelConverter(typeof(BooleanTextConverter), "是", "否")]
    public bool ElectricJudge { get; set; }

    [ExcelConverter(typeof(MaskConverter), 3, 4)]
    public string? Phone { get; set; }
}

实参个数或类型与任何公共构造函数都不匹配时抛 ExcelMappingException;不传实参时要求转换器具备公共无参构造函数。

内置转换器

转换器 可用构造器 说明
BooleanTextConverter ()、(trueText, falseText) 布尔判定文案;不传实参时默认 OK / NG。非布尔值原样返回
MaskConverter ()、(head, tail)、(head, tail, maskChar) 文本脱敏:保留首尾字符,其余替换为掩码(默认 *)
NullPlaceholderConverter ()、(placeholder) null 与空白统一替换为占位文本,不传实参时默认 -
new MaskConverter(3, 4).Convert("13812345678");   // 138****5678
new NullPlaceholderConverter("-").Convert(null);  // -
new BooleanTextConverter("是", "否").Convert(true); // 是

MaskConverter 在首尾保留长度之和覆盖整串时全掩码;长度为 0 的输入原样返回。 转换结果为字符串时按文本写入单元格,不需要额外调用 AsText()。


CSV 导出

// 快速导出
ExcelMapper.WriteCsv(people, "report.csv");

// 配置选项
ExcelMapper.BuildCsv(people)
    .Configure(o =>
    {
        o.Delimiter = ';';              // 欧洲格式
        o.UseUtf8Bom = true;            // Excel 打开中文不乱码
        o.FormulaInjectionGuard = true; // 公式注入防护
        o.QuoteAllFields = false;       // 仅必要时加引号
    })
    .ToFile("report.csv");

// 异步
await ExcelMapper.WriteCsvAsync(people, "report.csv", cts.Token, progress);

CSV 导出复用侵入式特性 / 非侵入式 EntityMap<T> 的列配置。集合属性在 CSV 中无法表达,对应列输出空字符串。


字典导出

动态 IDictionary 无需实体类:

var dict = new Dictionary<string, object>
{
    ["报告标题"] = "2024年销售统计",
    ["生成时间"] = DateTime.Now,
    ["总金额"] = 123456.78
};

// 键 ≤16 → 宽表(键作列头,单行值)
ExcelMapper.DictionaryToFile(dict, "summary.xlsx");

// 键 >16 → 竖表(Key/Value 两列)

Excel / CSV 导入

// 按扩展名自动选择
var result = ExcelMapper.Import<Person>("data.xlsx");

// 指定 Sheet
var r2 = ExcelMapper.ImportFromExcel<Person>("data.xlsx", sheetName: "员工表");

// CSV 导入(可指定分隔符)
var r3 = ExcelMapper.ImportFromCsv<Person>("data.csv", delimiter: ',');

// 从流导入
var r4 = ExcelMapper.ImportFromExcel<Person>(stream);

// 使用非侵入式映射绑定列名
var map = new EntityMap<Person>()
    .Property(p => p.Name).HasName("姓名");
var r5 = ExcelMapper.Import("data.xlsx", map);

导入结果

public class ImportResult<T>
{
    public List<T> Items { get; }      // 成功导入的实体
    public List<ImportError> Errors { get; }  // 逐字段错误
    public bool HasErrors { get; }
}

// 错误不阻断整批导入,便于桌面端展示给用户修正
foreach (var err in result.Errors)
    Console.WriteLine(err); // "第 5 行 [Score]:无法转为 Double(原始值:N/A)"

支持的转换

string / char / bool(1/true/yes/是)/ 枚举(数值/名称/Description)/ DateTime(11 种格式)/ DateTimeOffset / TimeSpan / Guid / byte[](Base64)/ 各数值类型(不丢精度)/ 可空类型 / 集合(JSON 反序列化,与 ArrayRender.Json 往返)。


引擎选择

// 自动选择(默认):行数 ≥ StreamingThreshold 时切换流式
ExcelMapper.Build(people)
    .WithOptions(o => o.StreamingThreshold = 100000)
    .ToFile("large.xlsx");

// 强制流式
ExcelMapper.Build(people)
    .WithOptions(o => o.Engine = ExportEngineKind.OpenXmlStreaming)
    .ToFile("large.xlsx");
引擎 特点
ClosedXml(DOM) 样式能力最全,支持原生条件格式、合并表头、Table 等;整个工作簿驻留内存
OpenXmlStreaming(SAX) 内存占用低,适合超大数据量;不支持原生条件格式(规则改为服务端求值)
Auto(默认) 行数 ≥ 阈值(默认 10 万)或无法预知行数时用流式,否则用 DOM

安全策略

策略 说明
长数字自动转文本 AutoForceText=Both(默认):按属性名敏感词(手机/电话/身份证/银行卡/邮编等)+ long 等大整数类型自动识别
CSV 公式注入防护 首字符为 = + - @ Tab CR 时前置单引号,防止 Excel 执行恶意公式
超长文本截断 单元格文本 >32767 字符自动截断(TruncateLongText=true)
非法字符清理 自动移除 XML 1.0 非法控制字符(\x00-\x08、\x0B、\x0C、\x0E-\x1F),防止 Office/WPS 无法打开文件
循环引用保护 递归到已知类型时标记"(循环引用)",不再继续展开
路径校验 写入前校验路径合法性,AutoCreateDirectory=true 时自动创建目录
异常不吞 类型转换/写入失败抛出具体异常类型,不留空 catch
全局格式不污染 ID 列 全局 NumberFormat 仅作用于数值列,ID/编号列保持文本

配置参考

ExcelStyle(视觉)

属性 默认值 说明
HeaderBold true 表头加粗
HeaderBackgroundColor #4472C4 表头背景色
HeaderFontColor #FFFFFF 表头字体色
AutoFitColumns true 自动列宽(同时兼顾表头与数据区,全角字符按两个半角宽计)
MinColumnWidth / MaxColumnWidth null / 50 列宽上下限
ShowBorder true 显示边框
BandedRows false 隔行底色
FreezeTopRow true 冻结表头行
FreezeFirstColumn false 冻结首列
NumberFormat null 全局数字格式(建议用列级 Format)
DateTimeFormat null 日期格式
TrueText / FalseText 是 / 否 布尔显示文本
UseEnumDescription true 枚举优先读 [Description]
ChildSheetParentProperty null 子 Sheet 父关联列属性名(null 自动查找 Id/Uid 等)

ExcelExportOptions(行为)

属性 默认值 说明
PageSize null 每页行数
PageMode MultipleSheets 分页方式
SheetPageNameFormat {0}_{1} 分页 Sheet 命名模板
ArrayRender ChildSheet 集合渲染方式(ChildSheet/FlattenInPlace/PlaceholderOnly/Json)
Engine Auto 导出引擎
StreamingThreshold 100000 自动切换流式的行数阈值
AutoCreateDirectory true 自动创建目录
AutoForceText Both 长数字自动转文本策略
TruncateLongText true 超长截断
BinaryRender Base64 byte[] 渲染方式
PreferNativeConditionalFormat true 优先原生条件格式
ValidatePlan Throw 计划校验模式

ExcelImageOptions(图片)

属性 默认值 说明
Width 0 显示宽度(像素);<=0 使用原图宽度
Height 0 显示高度(像素);<=0 使用原图高度
SizeMode Original Original 原尺寸 / Fit 等比缩放放入 / Stretch 拉伸(可能变形)
Placement Cell Cell 随单元格移动缩放 / Floating 自由浮动
OnMissing Skip 缺失策略:Skip / WriteSourceText / Throw
AdjustRowHeight true 自动调高行高以容纳图片
AdjustColumnWidth true 自动加宽列宽以容纳图片
RowPadding 4 行高额外留白(像素)
Name null 图片名称(同一 Sheet 内需唯一);为空时自动生成
BaseDirectory null 相对路径基准目录;默认程序所在目录

CsvExportOptions

属性 默认值 说明
Delimiter , 字段分隔符
UseUtf8Bom true UTF-8 BOM
QuoteAllFields false 全字段加引号
FormulaInjectionGuard true 公式注入防护
AutoCreateDirectory true 自动创建目录
IncludeHeader true 写入表头

API 速查

ExcelMapper(静态门面)

方法 说明
Build<T>(data) 创建 Excel 导出 Builder
SaveToFile<T>(data, path) 快速导出 Excel 到文件,返回 ExportResult
SaveToStream<T>(data, stream) 快速导出 Excel 到流,返回 ExportResult
SaveToFileAsync<T>(data, path, ct, progress) 异步导出 Excel,返回 Task<ExportResult>
BuildCsv<T>(data) 创建 CSV 导出 Builder
WriteCsv<T>(data, path) 快速导出 CSV
WriteCsvAsync<T>(data, path, ct, progress) 异步导出 CSV
DictionaryToFile(dict, path, sheetName) 字典导出
DictionaryToFileAsync(dict, path, sheetName, ct) 异步字典导出
Import<T>(path, map, ct) 按扩展名自动导入
ImportFromExcel<T>(path/sream, sheetName, sheetIndex, map, ct) Excel 导入
ImportFromCsv<T>(path/stream, map, delimiter, ct) CSV 导入

ExcelMapperBuilder<T>

方法 说明
WithSheetName(name) 设置 Sheet 名
WithStyle(style) / WithStyle(Action) 样式配置
WithOptions(options) / WithOptions(Action) 选项配置
WithMap(map) / WithMap(Action) 非侵入式映射
AddSheet<TOther>(data, name, map) 追加数据集为独立 Sheet
WithPicture(configure) 在主 Sheet 插入固定位置图片(Logo / 水印),可多次调用
WithTitle(text, fontSize, mergeAcross) 在表前区顶部插入跨列大标题,重复调用以最后一次为准
WithPreamble(configure) 在表头之上追加自由排版行,可多次调用
WithPostamble(configure) 在数据区之下追加自由排版行,可多次调用
ToFile(path, ct, progress) 导出文件,返回 ExportResult
ToStream(stream, ct, progress) 导出流,返回 ExportResult
ToFileAsync(path, ct, progress) 异步导出文件,返回 Task<ExportResult>
ToStreamAsync(stream, ct, progress) 异步导出流,返回 Task<ExportResult>

异常类型

异常 说明
ExcelMappingException 基类,所有本库异常
ExcelPlanValidationException 计划校验失败(列重名、多集合摊平等),含 Errors 列表
ExcelFileAccessException 文件路径/权限问题
ExcelImportException 导入过程错误
try
{
    ExcelMapper.SaveToFile(people, "report.xlsx");
}
catch (ExcelPlanValidationException ex)
{
    foreach (var err in ex.Errors)
        Console.WriteLine(err);
}
catch (ExcelFileAccessException ex)
{
    Console.WriteLine($"文件访问失败:{ex.Message}");
}

测试

cd VassasCo.Utility
dotnet test

69 个单元测试覆盖:敏感字段文本、长数字精度、布尔/枚举/byte[] 渲染、嵌套表头、集合子 Sheet、循环引用、分页(多 Sheet / 多文件 / 空数据)、条件格式、异步进度、CSV 安全、报表布局、值转换器(构造器实参与内置转换器)、导入类型转换与错误收集等场景。


更新说明

2.3.0(当前)

修复

  • 自动列宽不再忽略表头:此前仅按数据区评估列宽,中文表头常比数据更宽而显示不全。 现在同时评估叶子表头行与数据区并取较宽者。
  • 自动列宽按字符类别估算:此前依赖底层库的字体度量,该实现会将 CJK 字符算成接近零宽, 导致 5 个汉字的表头只得到约 4 的列宽。改为全角字符按 2、半角按 1 估算,并按字号缩放, 同时跳过跨列合并的单元格,避免布局区的长文本把单列撑开。 两个引擎改用同一套估算逻辑,列宽结果一致。
  • float 写入不再带精度噪声:float 只有约 7 位有效精度,此前直接放大为 double 会带出 一长串无意义尾数(0.3351f 写成 0.335099995136261),既难读又会把自动列宽撑到 20 个字符以上。 现在取最短往返表示写入,即能还原为同一个 float 的最短文本。 该处理仅作用于 float;double 保持原样,仍按 Excel 的约 15 位有效数字写入。
  • 非有限数不再导致写入失败:NaN 与正负无穷无法写入数值单元格(底层会直接抛异常), 现统一降级为文本(NaN / Infinity / -Infinity)。

兼容性

  • 仅修复渲染行为,未改动任何公开 API 签名,可原地升级。

2.2.0

新增

  • 报表布局能力:在数据表之外写入自由排版内容,覆盖单据、台账、质检报告等表格加抬头/落款的场景。
    • 新增类型:ReportRow、ReportCell、ReportRowBuilder、CellAlign。
    • 新增 API:WithTitle(...)、WithPreamble(...)、WithPostamble(...)。
    • 单元格支持横向合并(Span(0) 表示跨到表格最后一列,超出表格宽度的部分自动收敛)、 字号、加粗、水平对齐、字体色与填充色。
    • WithTitle 声明的标题恒位于表前区顶部;多个 WithPreamble / WithPostamble 按调用顺序排列。
    • 未使用任何布局方法时,表头仍从第 1 行开始,与旧版本行为完全一致(向后兼容)。
  • 工作表级字体:ExcelStyle.FontName / FontSize / HeaderFontSize。
  • Engine = Auto 时检测到布局行自动选用 DOM 引擎;显式指定流式引擎时抛 ExcelPlanValidationException, 避免静默丢标题。
  • 值转换器构造器实参:[ExcelConverter(typeof(T), params object?[])],同一种转换器可用不同实参 产生不同输出;实参不匹配或无可用构造函数时抛 ExcelMappingException。
  • 内置转换器:BooleanTextConverter、MaskConverter、NullPlaceholderConverter,覆盖判定文案、 文本脱敏、空值占位三类常见场景。转换器在两个引擎下均生效。

2.1.0

新增

  • Excel 图片插入能力
    • 新增类型:ExcelImageOptions、ExcelImageAttribute([ExcelImage])、PicturePlan、PictureBuilder。
    • 新增 API:Fluent 列级 .AsImage(...)、Builder 表级 .WithPicture(...)。
    • 两类图片:数据列图片(每行一张,值可为文件路径 / byte[] / Stream)与 固定位置图片(整表一张,用于 Logo / 水印)。
    • 图片格式自动嗅探(PNG / JPEG / GIF / BMP / TIFF),无需调用方指定格式枚举。
    • 未指定尺寸时使用原图尺寸;ImageSizeMode.Fit 支持等比缩放,不变形。
    • 插入后自动按需调整行高 / 列宽,避免图片溢出覆盖相邻内容。
    • 缺失兜底三档:Skip(默认留空)/ WriteSourceText(写回来源便于排查)/ Throw。
  • 新增 net472 目标框架,供 .NET Framework 4.7.2+(含 4.8)传统桌面项目直接引用。

修复

  • ConfigHelper 保存时用 File.Move(tmp, target, overwrite: true), 该重载在 .NET Framework 下不存在,导致 net472 目标无法编译(此前因未加该 TFM 而未被发现)。 改为 File.Replace(tmp, target, null):全目标框架可用、替换原子, 并消除原"先删除再移动"在两步之间中断会丢失配置文件文件的隐患。
  • 补齐 ExcelMapper / LogHelper 打包所需的 VassasCoIcon.png (此前仅 ConfigHelper 与 SnowflakeIdHelper 目录内有该图标), 避免 PackageIcon 引用缺失导致 dotnet pack 失败。

兼容性

  • 目标框架:net472 / netstandard2.0 / net6.0 / net8.0
  • 图片写入仅在 DOM 引擎(ClosedXML)下生效;Engine=Auto 时自动降级保护, 显式 OpenXmlStreaming 时图片跳过。
  • 本次均为新增能力 + 内部实现替换,未修改任何既有公开 API 签名,对现有调用方无破坏性变更。

许可证

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 is compatible.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on VassasCo.Utility.ExcelMapper:

Package Downloads
VassasCo.Utility

C# 桌面开发工具库 (WinForm / WPF / Avalonia): - ConfigHelper:零代码实体类⇋JSON/XML配置双向映射(带注释、热重载、原子保存、列表展开) - LogHelper:异步高性能日志系统(建造者模式、异步队列、自动清理、14种日志类型) - CrashDumpHelper:崩溃捕获+FirstChance异常追踪+MiniDump生成 - SnowflakeIdHelper:分布式雪花ID生成器(单调时钟杜绝回拨、集群WorkerId分配、ID反解) - ScheduleHelper:全能定时任务调度器(CRON/固定速率/固定延迟、重试退避、超时取消、日历过滤、线程池、优雅关闭) - RetryHelper:智能重试器(指数退避+抖动+断路器三态+降级+超时,同步/异步) - EventBus:进程内事件总线(类型发布/订阅、特性自动注册、优先级、条件过滤、粘性事件、多通道),支持 .NET Standard 2.0 / .NET 6 / .NET 8 / .NET 9 / .NET 10 - ExcelMapper:原生对象→Excel映射(嵌套类子表头合并、数组独立Sheet、Dictionary自适应、全可配置样式)

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.3.0 88 10/14/2026
2.2.0 83 10/14/2026
2.1.0 85 10/12/2026
2.0.0 111 9/23/2026
1.1.0 322 6/18/2026
1.0.0 266 6/18/2026