Excel 报表输出的实现方法 - COM / Open XML / 模板

· 更新日期: · · Excel, 报表, Windows 开发, Office, COM, Open XML

更新记录(2 条,最后更新 2026年09月03日)

本文的修改记录。已保存的更新前版本,可通过带有 DOI 的永久链接阅读。

本文此前是日文原文的节译,缺少大量章节、表格、Mermaid 图、图题、脚注与 FAQ。现已改写为日文原文的完整译文,技术主张与日文版一致,并补上了此前缺失的图与表,同时统一了全篇的术语译法。 查看更新前的版本 (DOI: 10.5281/zenodo.22276849)
补充了日文原文中已有的咨询引导(consultation_services)。正文内容没有改动。 查看更新前的版本 (DOI: 10.5281/zenodo.21615491)
首次发布
引用本文(DOI: 10.5281/zenodo.21615490)

本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。

小村 豪(2026)。《Excel 报表输出的实现方法 - COM / Open XML / 模板》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615490 https://comcomponent.com/zh-CN/blog/2026/03/16/010-excel-report-output-how-to-build/

DOI(最新版本)
10.5281/zenodo.21615490
DOI(此版本)
10.5281/zenodo.22282014

在关于 Excel 报表输出的咨询中,“想输出到 Excel”这句话背后,往往混杂着好几个不同的需求,这种情况并不少见。

  • 用户之后想手动修改
  • 想保留现有的 .xlsm
  • 想连透视表、图表、打印设置都原样保留使用
  • 想在夜间批处理中大量输出
  • 想在服务器上无人值守执行
  • 还想要 PDF

这些需求并不能靠单一方案就干净地全部解决。 一开始应该关注的,不是库的名字,而是要驱动 Excel 应用,还是要生成 Excel 文件。

如果在这一点上判断失误,即使一开始能跑通,后续的维护也会很痛苦。 本文以 Windows 应用和业务系统中的 Excel 报表输出为前提,梳理 COM 自动化 / Open XML / 模板填充 / 沿用现有 VBA 的选型方法。

首先应该看的分岔点在Excel报表输出中,比库的名字更应该先看的是要驱动Excel应用还是要生成Excel文件这个分岔点,判断失误的话即使一开始能跑通后续维护也会很痛苦。驱动应用生成文件驱动Excel应用,还是生成文件自动操作Excel的方案直接组装xlsx的方案判断失误会让后续维护很痛苦

图1:在挑选库之前,先决定是操作还是组装。

目标读者与前提

本文面向即将决定如何从业务系统输出 Excel 报表的开发者。

前提是从运行在 Windows 上的 C# / .NET 应用或批处理进行输出。文中也考虑了已经背负现有 VBA 资产的情况,但即便如此,本文的前提也不是“全部由 VBA 完结”,而是与 .NET 一侧划分职责。代码示例使用 C# / .NET 8。

先要理解的术语

术语 含义
Open XML Office 2007 之后的文件格式。.xlsx 的实体是把若干 XML 文件打包而成的 ZIP 归档,不启动 Excel 也能由程序组装
COM 自动化(Office Automation) 实际启动 Excel 等 Office 应用,并由外部程序操作它的方案。COM 是 Windows 上组件之间相互调用的机制,Excel 对外公开了这个入口
位数(bitness) 指以 32 位还是 64 位来构建和运行。在 COM 自动化中,如果调用方与 Excel 本体的位数不一致,连接就会失败
命名范围 在 Excel 中为单元格或单元格区域起的名字。可以用这个名字代替 Cells[12, 7] 这样的地址来指定数据的填充目标
表格(ListObject) 通过 Excel 的“套用表格格式”创建的结构。追加行时格式和公式会自动延伸,适合作为明细的入口

1. 先说结论

先把结论列出来。

  • 如果是用户之后要打开 Excel 编辑的报表,首选方案是模板 + .xlsx / .xlsm 直接生成。
  • 如果是在服务器 / 服务 / 调度器中自动生成,不以 Office 自动化为前提会更安全。
  • 如果想活用现有的 .xlsm、VBA、图表、透视表、打印设置,把布局和 Excel 特有功能放在模板一侧,代码只专注于数据填充,会更不容易出问题。
  • 只有在真正需要 Excel 应用本身的行为时,才自然地把 COM 自动化限定在桌面端有人值守的执行场景中使用。
  • 如果只是单纯的列表输出,很多情况下从一开始就用 CSV / PDF / Web 页面反而更符合需求。

总而言之,在很多业务报表中,与其说是操作 Excel,不如说组装 Excel 文件更自然。

从需求看首选方案用户之后要编辑的报表以模板和直接生成为首选,服务器或服务中的自动生成不以Office自动化为前提,只有真正需要Excel应用本身行为时才把COM自动化限定在有人值守的执行场景。之后要编辑的报表模板与直接生成无人值守的自动生成不以Office自动化为前提真正需要Excel本身的行为COM自动化限定为有人值守

图2:只要看清谁在哪里运行,首选方案基本就定了。

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 29 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

2. 首先要决定的事项

把 Excel 报表输出中一开始就要决定的事项整理成表格。

确认项目 提前决定的理由
最终产物是 .xlsx / .xlsm / PDF / CSV 中的哪一种 这一点能大幅收窄可选方案
用户输出后是否会用 Excel 编辑 如果以编辑为前提,保留 Excel 功能和布局就很重要
执行场所是用户 PC,还是服务器 / 服务 / 批处理 能否使用 COM 自动化的范围会大不相同
是否要保留现有的 VBA / 宏 / 加载项 需要设计 .xlsm 模板或分阶段迁移方案
是否要固定图表、透视表、打印范围、页眉 / 页脚 交给模板处理会比放在代码里更不容易出问题
单次的行数、文件数、并发执行数大概是多少 大批量输出更适合直接生成而不是 COM
报表外观由谁来修改 如果不只是开发者、现场人员也要改动,模板方式更合适

3. 主要实现方案

3.1 Excel COM 自动化

这是启动 Excel,通过 COM 操作 Workbook、Worksheet、Range 的方案。 可以理解为驾驶一台真正的 Excel。

它的优势是可以直接使用 Excel 特有的行为。 与现有工作簿、图表、透视表、打印设置、宏、PDF 输出等配合得很好,能够直接处理“Excel 最终会呈现成什么样”这件事本身。

不过,它的弱点也很明显。

  • 需要安装 Excel
  • 存在进程生命周期、文件锁定、对话框、位数(bitness)、用户配置文件依赖等问题
  • 微软自身并不推荐、也不支持从无人值守的服务器或服务中进行 Office Automation

第三点是本文中最强的主张,因此把依据写清楚。微软的支持文章《Considerations for server-side Automation of Office》中明确写明,不推荐也不支持在服务器端进行 Office Automation。文中列出的理由有下面 5 点。

理由 内容
用户标识 Office 以存在用户为前提,会去读取每个用户各自的注册表配置。在以没有用户配置文件的账户运行的服务中,这一步就会失败
桌面交互性 Office 以存在可交互的桌面为前提,有时会弹出模态对话框。在无人能关闭它的环境里,线程会一直停在那里
重入与可伸缩性 Office 应用是单线程的 COM 服务器,并不可重入。它是面向单一客户端设计的,承受不了服务器用途所需的多重执行
健壮性与稳定性 首次使用时安装功能可能弹出预料之外的对话框,而且微软本来就没有针对服务器端部署做过测试
服务器端安全性 它不具备面向分布式组件的安全控制,也不对请求进行身份验证。缓存的凭据存在被多个客户端共享的风险

关于 Microsoft 365 的 RPA 环境,另有《Considerations for unattended automation of Office》做了梳理。在方案选型中这一点成为争论焦点时,请把这两篇作为依据。

服务器端自动化的定位微软自身并不推荐也不支持从无人值守的服务器或服务中进行Office Automation,在方案选型出现争论时可以把支持文章和RPA环境的文章这两篇作为依据。从无人值守服务器进行Office Automation微软既不推荐也不支持支持文章可作为依据M365的RPA环境另有专文梳理

图3:这个方案能否使用不取决于个人偏好,而由官方出处决定。

3.2 .xlsx 直接生成

.xlsx 是 Open XML 格式,因此不启动 Excel 也能直接组装文件。 借助 Open XML SDK 之类的手段,程序侧可以直接操作工作簿、工作表、单元格、样式、表格(Table)。

这种方案的优势是即使在没有安装 Excel 的环境中也容易运行,与批处理和服务器配合得很好。

另一方面,如果想自然地重现 Excel 自身那些偏向界面的行为,就会有点吃力。 列宽自动调整、分页、复杂的外观、对现有工作簿的深度编辑等,如果想只靠代码就全部漂亮地搞定,代码行数会不断膨胀。

直接生成方案的特点xlsx是Open XML格式所以不启动Excel也能直接组装,在未安装Excel的环境以及批处理和服务器上都很合适,但要重现Excel偏向界面的行为时代码会不断增加。xlsx是Open XML格式不启动Excel也能组装与批处理和服务器很合适重现界面行为会让代码增加

图4:不需要 Excel 是优势,难以重现外观是与之互为表里的劣势。

从 .NET 处理 .xlsx 的库有好几个,而且许可证条件在实务中会真正起作用。常被列为候选的是下面这些。

库 许可证 定位
Open XML SDK(DocumentFormat.OpenXml) MIT 微软出品。几乎是直接操作 Open XML 的结构。能做的事最广,代价是哪怕写一个单元格也要不少代码量
ClosedXML MIT Open XML SDK 的封装。可以用直观的 API 处理工作表、单元格、命名范围、表格。支持 .xlsx 和 .xlsm,不需要安装 Excel
NPOI Apache License 2.0 把 Java 的 Apache POI 移植到 .NET 的产物。特点是也能处理 .xls 这类旧格式
EPPlus 版本 5 之后为 Polyform Noncommercial 或商业许可证 功能强大,但商业使用需要付费许可证。如果凭着版本 4 系列 LGPL 时代的印象来选型,会在许可证上踩坑

如果采用模板填充方案,外观由模板一侧负责,代码要做的只是“把值放进约定好的入口”。因此,代码量较少的封装类库用起来更顺手。

3.3 模板填充

在实务中最推荐的,是先做好 Excel 模板,代码只专注于数据填充的方案。

报表的外观、公式、条件格式、打印范围、页眉 / 页脚、Logo、图表都放在模板一侧。 代码一侧则负责复制模板,并把数据写入命名范围、表格、单元格区域等约定好的入口。

这样一来,布局修改和业务逻辑修改就分开了。 可以相当有效地避免 Excel 报表中常见的 Cells[37, 9] = ... 地狱。

模板填充的职责划分报表的外观、公式和打印设置放在模板一侧,代码一侧复制模板并把数据写入命名范围和表格这些约定好的入口,从而把布局修改和业务逻辑修改分开。模板一侧持有外观、公式、打印设置代码一侧只把值写入约定好的入口布局修改与业务修改分开

图5:把外观和逻辑各自的地盘分开,是这个方案的核心。

3.4 保留现有 VBA 资产的方案

如果现有的 .xlsm 或 VBA 仍在使用,很多时候不一次性全部重写会更自然。 把报表的界面和最终排版留在 VBA 中,把繁重的计算、数据库 / HTTP 调用、业务逻辑放到 C# / .NET 一侧,这种分工相当现实可行。

这时重要的是,不要让职责变得含糊。

  • VBA 一侧负责工作簿内部的行为
  • .NET 一侧负责数据获取和业务处理
  • 两者的边界通过命名范围、表格、公开接口等方式固定下来
现有VBA与.NET的职责划分保留现有xlsm和VBA时,VBA一侧负责工作簿内部的行为,.NET一侧负责数据获取和业务处理,两者的边界用命名范围、表格和公开接口固定下来。VBA一侧工作簿内部的行为.NET一侧数据获取与业务处理边界用命名范围等固定

图6:保住资产的诀窍,是固定边界、不让职责含糊。

3.5 使用 Microsoft 365 / Graph 的场景

如果 Excel 文件本来就存放在 OneDrive / SharePoint 上,并且想通过 Web 应用或移动应用协同使用,Microsoft Graph 的 Excel API 也是一个可选方案。

不过,它并不是那种能随意批量生成本地 PC 上任意文件的通用方案。权限、存储位置、会话、运维方式一开始就是以 M365 为前提的。

3.6 首先要问:真的需要用 Excel 吗

如果报表的需求是“之后由人来操作的表格”,选择 Excel 是自然的。 不过,如果是下面这类需求,往往用别的格式会更直接。

  • 打印后保管 -> PDF
  • 导入到其他系统 -> CSV / TSV / JSON
  • 只要能在浏览器中查看 -> HTML / Web 页面
  • 主要目的是汇总与可视化 -> BI 或仪表盘
确认是否真的需要Excel如果报表的需求是之后由人来操作的表格,选择Excel是自然的,但如果主要目的是打印保管、导入其他系统、浏览器查看或者汇总与可视化,往往用别的格式更直接。要操作不操作是不是之后由人操作的表格选择Excel是自然的考虑别的格式PDF / CSV / Web页面 / BI等

图7:输出格式要从目的倒推,不要把 Excel 当成默认值。

4. 方案对比

把各方案的差异整理成一张表,如下所示。

方案 是否需要安装 Excel 与无人值守执行的契合度 现有布局复用性 与 Excel 特有功能的契合度 适合场景
COM 自动化 需要 弱 强 非常强 用户 PC 上的输出、沿用现有 .xlsm、最终转 PDF
.xlsx 直接生成 不需要 强 中 中 批处理、服务器、大批量输出
模板填充 不需要(输出时) 强 强 中~强 多数业务报表的首选方案
沿用现有 VBA 视使用形态而定 弱~中 非常强 强 分阶段迁移、活用现有资产
Graph Excel API 以 M365 为前提 中 中 中 OneDrive / SharePoint 上的协同使用

5. 按常见需求分类的选型方法

5.1 在用户 PC 上输出并直接编辑

这种情况下,模板 + 直接生成相当有力。 因为用户输出后会用 Excel 打开,最终的编辑可以交给 Excel 处理。

5.2 在夜间批处理或服务中批量生成

如果涉及夜间批处理,从先排除 COM 自动化开始会更安全。 生成方式转向 .xlsx 直接生成,必要时再让用户之后用 Excel 打开。

夜间批处理的推进方式在夜间批处理或服务中批量生成时,先把COM自动化从候选中排除,把生成转向xlsx的直接生成,必要时再让用户之后用Excel打开会更安全。想在夜间批处理中大量生成先排除COM自动化转向xlsx的直接生成必要时让用户之后打开

图8:无人值守执行的设计,从做减法开始更安全。

5.3 想活用现有的 .xlsm / VBA

如果现有资产仍在使用,把 .xlsm 保留为模板,只从外部进行数据填充是比较现实的做法。

5.4 明细行数很大

Excel 单个工作表的上限是 1,048,576 行 × 16,384 列。 如果明细数据量很大,一开始就要把这一点定下来。

  • 超过多少行就拆分工作表
  • 超过多少条数据就拆分文件
  • 是否本来就更适合用 CSV
明细很大时要先决定的事Excel单个工作表有行数和列数的上限,所以明细数据量很大时要先决定超过多少行拆分工作表、超过多少条拆分文件,以及是否本来就更适合用CSV。明细数据量很大单个工作表有上限决定拆分工作表的标准决定拆分文件的标准重新考虑CSV是否更自然

图9:不要等撞上上限才想,先把拆分策略定下来。

6. 实务中比较推荐的结构

在实务中比较不容易出问题的,是分成 4 层的结构。

层 职责 不在这里做的事
ReportModel 整理报表所需的数值 不知道单元格地址
Template 持有外观、公式、打印设置、图表 不知道数据库或业务逻辑
Binder 把数据写入命名范围 / 表格 不引入业务判断
Finisher 必要时进行 VBA / COM / 转 PDF 处理 不获取原始数据

这种分层方式的好处是,代码不容易被 Excel 的外观牵着走。

6.1 层与层之间流动的是什么

重要的是跨越层边界的东西是什么。只要这一点定下来,布局变更和业务逻辑变更就可以分头推进。

原始数据复制出的工作簿名字与值的组合填好值的工作簿产物不需要 Finisher 时到这里就完成数据库 / API / 文件ReportModel只把报表需要的值整理后持有Templatexlsx 或 xlsm外观、公式、打印设置Binder把值写入命名范围与表格Finisher转 PDF 或调用 VBA 等仅在必要时xlsx / xlsm / PDF

图10:在 4 层之间流动的只有名字与值的组合,单元格地址不会跑到 Binder 之外。

跨越边界的只有名字与值的组合,单元格地址不会传到 Binder 之外。如果模板一侧的情况一直传到了 ReportModel,那么设计在那一刻就已经开始崩塌了。

6.2 最小实现示例

用 ClosedXML 来写模板填充,代码如下。模板 Invoice.xlsx 一侧需要预先定义 Rpt_Title、Rpt_IssuedOn、Rpt_CustomerName、Rpt_DetailRows 这几个命名范围。

// C# / .NET 8 + ClosedXML(MIT 许可证)
//   dotnet add package ClosedXML
using ClosedXML.Excel;

// 相当于 ReportModel。完全不持有单元格地址
var rows = new (string Code, string Name, int Qty, decimal UnitPrice)[]
{
    ("A-100", "滚珠轴承", 12, 480m),
    ("A-205", "轴", 3, 12800m),
    ("B-010", "安装支架", 30, 260m),
};

const string TemplatePath = @"templates\Invoice.xlsx";
string outputDir = "output";
Directory.CreateDirectory(outputDir);

// 不要用“精确到秒的时间”来构造文件名。在逐条处理的批处理中,
// 只要有两条落在同一秒,文件名就会相同,后保存的那份会覆盖
// 先前的报表。麻烦之处在于谁也不会察觉有东西消失了。
// 一定要放入能唯一确定该报表的业务标识(这里是发票号)。
string invoiceNo = "INV-2026-000123";     // 由调用方传入
string outputPath = Path.Combine(outputDir, $"Invoice_{invoiceNo}.xlsx");

// 1. 打开模板。因为会另存为别的名字,所以模板本身不会被改写
using var workbook = new XLWorkbook(TemplatePath);

// 2. 表头写入命名单元格。要点是代码里不出现单元格地址
workbook.Cell("Rpt_Title").Value = "发票";
workbook.Cell("Rpt_IssuedOn").Value = DateTime.Today;   // 作为值写入。显示格式交给模板一侧
workbook.Cell("Rpt_CustomerName").Value = "示例股份有限公司";

// 3. 明细以命名范围为入口,按区域内的相对位置写入
var detail = workbook.Range("Rpt_DetailRows");
if (rows.Length > detail.RowCount())
{
    // 超出了预留的行数。不要悄悄截断,就在这里停下来
    throw new InvalidOperationException(
        $"明细共有 {rows.Length} 条,但模板的 Rpt_DetailRows 只有 {detail.RowCount()} 行。" +
        "请增加模板一侧的行数,或者拆分工作表。");
}

for (int i = 0; i < rows.Length; i++)
{
    var row = rows[i];
    detail.Cell(i + 1, 1).Value = row.Code;        // 区域内从 1 开始的相对位置
    detail.Cell(i + 1, 2).Value = row.Name;
    detail.Cell(i + 1, 3).Value = row.Qty;
    detail.Cell(i + 1, 4).Value = row.UnitPrice;
}

// 4. 另存为别的名字。模板作为只读资产保留下来。
//    写入先落到同一文件夹下的临时文件,写完之后再改名为本来的名字。
//    如果直接写入 outputPath,一旦中途失败(磁盘写满、
//    工作簿不一致等),就会留下一个“只写了一半的 .xlsx”,而且用的是业务上的文件名
string tempPath = Path.Combine(
    Path.GetDirectoryName(outputPath) ?? string.Empty,   // 让改名操作限制在同一个卷内
    $".{Path.GetFileName(outputPath)}.{Guid.NewGuid():N}.tmp");

try
{
    using (var stream = new FileStream(tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
    {
        workbook.SaveAs(stream);
    }

    // 如果同名文件已存在会抛出 IOException。同名文件到来时不会悄悄覆盖,
    // 而是当场就能发现(与 FileMode.CreateNew 的方针一致)
    File.Move(tempPath, outputPath);
}
catch
{
    // 失败了就不留痕迹。留下来的话,下一次执行就无法用同一个名字重试
    try { File.Delete(tempPath); } catch (IOException) { }
    throw;
}
Console.WriteLine($"已输出:{outputPath}");

这段代码有意识地做到了 5 点。

  • 代码里不出现单元格地址。 填充目标只有命名范围。即使给模板加一行,这段代码也不需要改
  • 不覆盖模板。 打开的工作簿一定另存为别的名字
  • 日期和数值作为值写入。 如果整理成字符串写入,Excel 一侧就既不能排序也不能汇总了
  • 明细溢出时用异常停下来。 悄悄截断是报表中最糟糕的一种损坏方式。5.4 的方针在这里落到了实现上
  • 写完之后再取本来的名字。 SaveAs 有可能在开始写入之后才失败。磁盘写满了、共享断开了、工作簿内容不合法了——这些都会发生。如果直接写入 outputPath,那时留下的就是一个名叫 Invoice_INV-2026-000123.xlsx、名字完美无缺、内容却写了一半的文件。人是靠名字来判断的,所以没办法把它和完成的报表区分开。而且 FileMode.CreateNew 会拒绝已存在的文件,于是再执行一次也会以“已经存在”为由停下来。先写入临时文件再用 File.Move 改名,那个名字就只会在内容齐全时才出现。之所以把改名目标放在同一个文件夹,是因为跨卷的 Move 会退化为复制,同样可能中途被截断

即使金额用 decimal 持有,在进入 Excel 文件格式的那一刻也会变成双精度浮点数。如果舍入的基准在业务上很重要,不要交给 Excel 的公式,而是在 ReportModel 一侧生成已经舍入好的值会更安全。

把 .xlsm 当作模板时流程也一样,只是保存时的扩展名要改成 .xlsm。保留宏的同时进行填充的结构,如 3.4 所述。

经由临时文件的保存流程保存时先把内容完整写入同一文件夹下的临时文件,再用File.Move改名为本来的名字,失败时删除临时文件,从而避免只写了一半的文件顶着业务上的文件名残留下来。成功失败写入同一文件夹下的临时文件是否完整写完用File.Move改名为本来的名字删除临时文件并抛出异常名字只在内容齐全时才出现

图11:业务上的文件名,只给已经完成的文件。

7. 容易踩坑的地方

7.1 不要把单元格地址变成业务规范

一旦 Cells[12, 7] 开始承载业务规则,布局变更就会直接变成规范变更。 代码通过命名范围或表名来操作报表,会更耐用。

不要把单元格地址变成业务规范一旦单元格地址开始承载业务规则,布局变更就会直接变成规范变更,因此代码通过命名范围或表名来操作报表会更耐用。单元格地址承载业务规则布局变更变成规范变更通过命名范围或表名操作改了布局代码也扛得住

图12:用名字而不是地址来操作,决定了报表代码的寿命。

7.2 不要把合并单元格作为数据入口

合并单元格是用于外观展示的功能。 如果把它当作填充目标,在新增行或区域计算时很容易出问题。

7.3 不要用“带外观的字符串”来填数字或日期

把值作为值写入,外观交给单元格格式处理,会更自然。

7.4 不要让模板变更处于野蛮生长的状态

模板不是代码,但实质上等同于规范本身。 把它纳入版本管理、差异确认、评审的对象来处理,会比较稳妥。

7.5 如果要用 COM,不要轻视位数和生命周期管理

在 COM 自动化或 VBA 联动中,32 位 / 64 位的差异、Excel 进程的收尾处理、文件锁定、用户环境差异,这些看似不起眼的问题会实实在在地产生影响。

8. 小结

Excel 报表输出看起来只是“输出到 Excel”这一句话就能说完的事,但实际上需要提前决定好几个分支。

  • 是驱动 Excel 应用
  • 还是生成 Excel 文件
  • 是用户 PC,还是无人值守执行
  • 是否要保留现有 VBA 或 .xlsm
  • 最终产物是 Excel,还是 PDF 或 CSV

作为实务中的首选方案,模板 + 直接生成相当强势。 在此基础上,根据需要再加上复用现有 VBA或在用户 PC 上进行最终的 Excel 处理,这种组合方式比较容易落地。

容易落地的结构怎么搭实务中把模板与直接生成放在首选位置,再根据需要加上复用现有VBA或在用户PC上进行最终Excel处理,这种做法比较容易落地。以模板与直接生成为主轴必要时加上复用现有VBA必要时加上用户PC上的最终处理

图13:先定下一个主轴,再只把例外加上去,就比较容易落地。

9. 参考资料

按阅读顺序排列。

9.1 决定方案之前要读的

9.2 直接生成时会用到的

  • About the Open XML SDK for Office — 不用 Excel 也能组装 .xlsx 的基础
  • ClosedXML — 6.2 的代码示例中使用的封装库。MIT 许可证
  • NPOI — 需要同时处理旧的 .xls 时的选项。Apache License 2.0
  • EPPlus — 采用之前请务必确认版本 5 之后的许可证条件

9.3 在 M365 / SharePoint 上处理的场景

9.4 处理超大工作簿时要深入的

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

Excel 报表输出应该选择 COM 自动化还是直接生成文件?
首先应该看的不是库的名字,而是要驱动 Excel 应用,还是要生成 Excel 文件。在很多业务报表中,与其说是操作 Excel,不如说组装 Excel 文件更自然;如果用户之后还要编辑该报表,模板 + .xlsx/.xlsm 直接生成是首选方案。只有在真正需要 Excel 应用本身的行为时,才自然地把 COM 自动化限定在桌面端有人值守的执行场景中使用。
可以在服务器或夜间批处理中使用 Excel 的 COM 自动化吗?
最好避免。微软自身并不推荐、也不支持从无人值守的服务器或服务中进行 Office Automation。COM 自动化不仅需要安装 Excel,还会遇到进程生命周期、文件锁定、对话框、位数(bitness)、用户配置文件依赖等一系列问题。在夜间批处理或大批量输出场景中,更安全的做法是转向 .xlsx 的直接生成,必要时再让用户之后用 Excel 打开。
能否在保留现有 .xlsm 或 VBA 资产的前提下构建报表输出?
可以。如果现有资产仍在使用,不必一次性全部重写,比较现实的做法是把 .xlsm 保留为模板,只从外部进行数据填充。把报表的界面和最终排版留在 VBA 中,把繁重的计算、数据库/HTTP 调用、业务逻辑放到 C#/.NET 一侧,这种分工方式比较好用。关键是不要让职责变得含糊,双方的边界要通过命名范围、表格(Table)、公开接口等方式固定下来。
在实现 Excel 报表时应该避免哪些常见的坑?
首先重要的是不要把单元格地址变成业务规范:与 Cells[12, 7] 这类地址指定相比,通过命名范围或表名来操作报表会更耐用。合并单元格是用于外观展示的功能,不要把它用作数据填充的目标;数字和日期应作为值写入,外观交给单元格格式处理;模板实质上等同于规范,因此要纳入版本管理和评审对象——这些点都很有效。另外,Excel 单个工作表的上限是 1,048,576 行 × 16,384 列,如果明细数据量很大,需要提前决定拆分工作表或拆分文件的策略。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表