更新紀錄(2 筆,最後更新 2026年09月04日)
本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。
引用本文(DOI: 10.5281/zenodo.21616310)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
Go Komura(2026)。〈Excel 報表輸出的做法 - COM / Open XML / 範本〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616310 https://comcomponent.com/zh-TW/blog/2026/03/16/010-excel-report-output-how-to-build/
- DOI(最新版本)
- 10.5281/zenodo.21616310
- DOI(此版本)
- 10.5281/zenodo.22297149
在 Excel 報表輸出的諮詢裡,「想輸出到 Excel」這一句話中,其實常常混雜著好幾個不同的需求。
- 使用者之後想自己動手修改
- 想保留現在的
.xlsm - 連樞紐分析表、圖表、列印設定都想原封不動沿用
- 想在夜間批次中大量產出
- 想在伺服器上無人執行
- 也想要 PDF
這些需求沒辦法用單一方式全部漂亮地解掉。 最先要看的不是函式庫的名字,而是要驅動 Excel 應用程式,還是要組裝出 Excel 檔案。
在這裡看錯方向,一開始或許能動,之後維護會變得很吃力。 本文以 Windows 應用程式或業務系統上的 Excel 報表輸出為前提,梳理 COM 自動化 / Open XML / 範本套版 / 既有 VBA 併用 的選法。
flowchart TB
accTitle: 最先要看的分歧點
accDescr: Excel 報表輸出應該比函式庫的名字更早去看要驅動 Excel 應用程式還是要組裝 Excel 檔案這個分歧點,在這裡看錯方向即使一開始能動之後維護也會很吃力的圖。
q1{"要驅動 Excel 應用程式,還是要組裝檔案"}
q1 -->|"驅動應用程式"| a1["自動操作 Excel 的方式"]
q1 -->|"組裝檔案"| a2["直接組裝 xlsx 的方式"]
q1 -.-> w1["看錯之後維護會變得很吃力"]
圖 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 檔案」比較自然。
flowchart TB
accTitle: 從需求看第一候選
accDescr: 使用者之後會編輯的報表以範本與直接生成為第一候選,伺服器或服務上的自動生成不以 Office 自動化為前提,只有真的需要 Excel 應用程式行為時才把 COM 自動化限定在有人操作的執行情境使用的圖。
r1["之後會編輯的報表"] --> s1["範本與直接生成"]
r2["無人的自動生成"] --> s2["不以 Office 自動化為前提"]
r3["真的需要 Excel 本身的行為"] --> s3["COM 自動化限定在有人操作"]
圖 2: 只要看誰在哪裡執行,第一候選幾乎就決定了。
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 29 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
2. 最先要決定的事
Excel 報表輸出最先想決定好的事,整理成一張表。
| 確認項目 | 先決定的理由 |
|---|---|
最終產出是 .xlsx / .xlsm / PDF / CSV 的哪一種 |
光是這裡就能相當程度縮小方式的範圍 |
| 使用者輸出後是否會用 Excel 編輯 | 若以編輯為前提,Excel 功能與版面維持就很重要 |
| 執行地點是使用者電腦,還是伺服器 / 服務 / 批次 | 能使用 COM 自動化的範圍會大幅改變 |
| 是否保留既有的 VBA / 巨集 / 增益集 | 需要 .xlsm 範本或階段式遷移的設計 |
| 圖表、樞紐分析表、列印範圍、頁首 / 頁尾是否都要固定 | 收攏到範本比寫在程式碼裡更不容易壞 |
| 每次的列數、檔案數、同時執行數大約多少 | 大量輸出時,直接生成比 COM 更合適 |
| 報表的外觀由誰來變更 | 如果不只開發者、實務端也會動,範本方式的契合度比較好 |
3. 主要的實作方式
3.1 Excel COM 自動化
啟動 Excel,透過 COM 操作 Workbook、Worksheet、Range 的方式。
把它想成是「駕駛一台真正的 Excel」就容易理解了。
強項是能原封不動地使用 Excel 特有的行為。 它和既有的活頁簿、圖表、樞紐分析表、列印設定、巨集、PDF 輸出都很合,可以直接處理「Excel 最後會怎麼呈現」。
不過,弱點也很明顯。
- 需要安裝 Excel
- 會背上處理程序生命週期、檔案鎖定、對話方塊、bitness、使用者設定檔相依這些問題
- 從無人伺服器或服務執行 Office Automation,Microsoft 自己並不推薦也不支援
第三點是本文最強的主張,所以把根據明確寫出來。Microsoft 的支援文件「Considerations for server-side Automation of Office」中明確寫著,不推薦也不支援在伺服器端執行 Office Automation。文中列出的理由有下列 5 點。
| 理由 | 內容 |
|---|---|
| 使用者身分 | Office 以有使用者存在為前提,會去讀取每位使用者專屬的登錄檔設定。在沒有使用者設定檔的帳戶下執行的服務,會在這裡失敗 |
| 桌面互動性 | Office 以可互動的桌面為前提,有時會顯示強制回應對話方塊。在沒有人能關掉它的環境裡,執行緒會一直卡在那裡 |
| 可重新進入與延展性 | Office 應用程式是單一執行緒的 COM 伺服器,並不可重新進入。它是為單一用戶端設計的,撐不住伺服器用途所需的多重執行 |
| 穩固性與穩定性 | 首次使用時安裝的功能可能會跳出非預期的對話方塊,而且本來就沒有針對伺服器端部署做過測試 |
| 伺服器端的安全性 | 它沒有針對分散式元件的安全性控制,也不會驗證要求。快取的認證資訊有可能在多個用戶端之間被共用 |
關於 Microsoft 365 的 RPA 環境,另外在「Considerations for unattended automation of Office」中有整理。方式選型上如果這一點成了爭論焦點,就用這兩篇當出處。
flowchart TB
accTitle: 伺服器端自動化的定位
accDescr: 從無人伺服器或服務執行的 Office Automation,Microsoft 自己並不推薦也不支援,方式選型上成為爭論焦點時可以用支援文件與 RPA 環境的文章這兩篇當出處的圖。
sv1["從無人伺服器執行 Office Automation"] --> sv2["Microsoft 不推薦也不支援"]
sv2 -.-> sv3["支援文件可以當根據"]
sv2 -.-> sv4["M365 的 RPA 環境由另一篇整理"]
圖 3: 這個方式能不能用,不是靠喜好,而是靠官方出處定案。
3.2 .xlsx 直接生成
.xlsx 是 Open XML 格式,因此不啟動 Excel 也能直接組裝檔案。
只要使用 Open XML SDK 這類手段,就能從程式側操作活頁簿、工作表、儲存格、樣式、表格。
這個方式的強項是 在沒有安裝 Excel 的環境也容易執行,和批次或伺服器很合。
另一方面,如果想連 Excel 本身偏 UI 的行為都自然重現,就會有點吃力。 欄寬自動調整、分頁、複雜的外觀、既有活頁簿的深度編輯等,若想全部只用程式碼漂亮地做完,行數會愈長愈多。
flowchart TB
accTitle: 直接生成方式的性格
accDescr: xlsx 是 Open XML 格式所以不啟動 Excel 也能直接組裝,和沒有安裝 Excel 的環境或批次與伺服器都很合,另一方面若想連 Excel 偏 UI 的行為都重現程式碼就會愈寫愈多的圖。
dg1["xlsx 是 Open XML 格式"] --> dg2["不啟動 Excel 就組裝"]
dg2 --> dg3["和批次或伺服器很合"]
dg2 -.-> dg4["重現偏 UI 的行為會讓程式碼變多"]
圖 4: 不需要 Excel 的強項,和難以重現外觀的弱項互為表裡。
從 .NET 處理 .xlsx 的函式庫有好幾個,而 授權條件在實務上會產生影響。常被列入候選的是下面這幾個。
| 函式庫 | 授權 | 定位 |
|---|---|---|
Open XML SDK(DocumentFormat.OpenXml) |
MIT | Microsoft 出品。幾乎是直接操作 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] = ... 地獄。
flowchart TB
accTitle: 範本套版的分工
accDescr: 報表的外觀與公式與列印設定放在範本側,程式碼側複製範本後把資料寫進具名範圍或表格這些決定好的入口,讓版面修改與業務邏輯修改分開的圖。
tp1["範本側"] --> tp2["持有外觀、公式、列印設定"]
tc1["程式碼側"] --> tc2["只把值寫進決定好的入口"]
tp2 --> tw1["版面修改與業務修改分開"]
tc2 --> tw1
圖 5: 把外觀與邏輯的守備範圍分開,是這個方式的核心。
3.4 保留既有 VBA 資產的方式
既有的 .xlsm 或 VBA 還活著的話,不要一次全部重做通常比較自然。
把報表的 UI 與最後的整形留在 VBA,重的計算與 DB / HTTP / 業務邏輯移到 C# / .NET 側,這種分法相當實際。
此時重要的是 不要讓職責變得模糊。
- VBA 側負責活頁簿內部的行為
- .NET 側負責資料取得與業務處理
- 兩者的界線用具名範圍、表格、對外介面等固定下來
flowchart TB
accTitle: 既有 VBA 與 .NET 的職責分工
accDescr: 保留既有的 xlsm 與 VBA 時,VBA 側負責活頁簿內部的行為,.NET 側負責資料取得與業務處理,兩者的界線用具名範圍或表格或對外介面固定下來的圖。
vb1["VBA 側"] --> vb2["活頁簿內部的行為"]
nt1[".NET 側"] --> nt2["資料取得與業務處理"]
vb2 --> bd1["界線用具名範圍等固定"]
nt2 --> bd1
圖 6: 保留資產的訣竅,是把界線固定下來,不讓職責模糊。
3.5 使用 Microsoft 365 / Graph 的情況
如果 Excel 檔案一開始就在 OneDrive / SharePoint 上,而且想從 Web 應用程式或行動應用程式共同使用,Microsoft Graph 的 Excel API 也會進入選項。
不過,它並不是把本機電腦上的任意檔案隨手大量產生的通用解。權限、儲存位置、工作階段、維運從一開始就以 M365 為前提。
3.6 是不是真的非 Excel 不可
如果報表的需求是「人之後要動的表」,選 Excel 是自然的。 不過若是下面這些需求,改用別的格式往往更直接了當。
- 印出來保存 -> PDF
- 匯入其他系統 -> CSV / TSV / JSON
- 只要能在瀏覽器看到就好 -> HTML / Web 畫面
- 主要目的是彙總與視覺化 -> BI 或儀表板
flowchart TB
accTitle: 確認是不是真的需要 Excel
accDescr: 報表的需求如果是人之後要動的表選 Excel 是自然的,但如果目的是印出保存或匯入其他系統或用瀏覽器閱覽或彙總與視覺化,改用別的格式往往更直接了當的圖。
ne1{"是不是人之後要動的表"}
ne1 -->|"會動"| ne2["選 Excel 是自然的"]
ne1 -->|"不會動"| ne3["考慮別的格式"]
ne3 -.-> ne4["PDF / CSV / Web 畫面 / BI 等"]
圖 7: 輸出格式從目的回推,不要把 Excel 當成預設。
4. 方式比較
把各方式的差異排進一張表,會是這樣。
| 方式 | 需要安裝 Excel | 與無人執行的契合度 | 既有版面再利用 | 與 Excel 特有功能的契合度 | 適合的場合 |
|---|---|---|---|---|---|
| COM 自動化 | 需要 | 弱 | 強 | 非常強 | 使用者電腦上的輸出、既有 .xlsm、最後轉成 PDF |
.xlsx 直接生成 |
不需要 | 強 | 中 | 中 | 批次、伺服器、大量輸出 |
| 範本套版 | 不需要(輸出時) | 強 | 強 | 中~強 | 多數業務報表的第一候選 |
| 既有 VBA 併用 | 視使用形態而定 | 弱~中 | 非常強 | 強 | 階段式遷移、活用既有資產 |
| Graph Excel API | 以 M365 為前提 | 中 | 中 | 中 | OneDrive / SharePoint 上的共同使用 |
5. 常見需求別的選法
5.1 在使用者電腦上輸出,並直接編輯
這種情況下,範本 + 直接生成 相當有力。 因為使用者輸出後會用 Excel 打開,最後的編輯交給 Excel 就好。
5.2 在夜間批次或服務上大量生成
只要牽涉到夜間批次,先從 把 COM 自動化排除掉 開始比較安全。
生成收攏到 .xlsx 的直接生成,需要時再讓使用者之後用 Excel 打開。
flowchart TB
accTitle: 夜間批次的推進方式
accDescr: 在夜間批次或服務上大量生成時先把 COM 自動化從候選中排除,把生成收攏到 xlsx 的直接生成,需要時再讓使用者之後用 Excel 打開比較安全的圖。
nb1["想在夜間批次大量生成"] --> nb2["先把 COM 自動化排除"]
nb2 --> nb3["收攏到 xlsx 的直接生成"]
nb3 -.-> nb4["需要時讓使用者之後打開"]
圖 8: 無人執行的設計,從做減法開始比較安全。
5.3 想活用既有的 .xlsm / VBA
既有資產還活著的話,把 .xlsm 留作範本,只從外部進行資料套版 比較實際。
5.4 明細列數很大
Excel 單一工作表的上限是 1,048,576 列 × 16,384 欄。 明細很大時,一開始就把這裡決定好。
- 超過幾列就分工作表
- 超過幾筆就分檔案
- 是不是一開始用 CSV 反而更自然
flowchart TB
accTitle: 明細很大時要先決定的事
accDescr: Excel 單一工作表有列數與欄數的上限,因此明細很大時要先決定超過幾列就分工作表、超過幾筆就分檔案、以及是不是用 CSV 反而更自然的圖。
lg1["明細很大"] --> lg2["單一工作表有上限"]
lg2 --> lg3["決定分工作表的基準"]
lg2 --> lg4["決定分檔案的基準"]
lg2 -.-> lg5["重新考慮是不是 CSV 更自然"]
圖 9: 不要撞上上限才開始想,先把分割方針決定好。
6. 實務上容易推薦的組合
實務上不容易壞的,是分成 4 層的組合。
| 層 | 角色 | 這一層不做的事 |
|---|---|---|
| ReportModel | 整理報表需要的值 | 不知道儲存格位址 |
| Template | 持有外觀、公式、列印設定、圖表 | 不知道 DB 或業務邏輯 |
| Binder | 把資料寫進具名範圍 / 表格 | 不帶入商業判斷 |
| Finisher | 需要時進行 VBA / COM / 轉 PDF | 不做原始資料取得 |
這種分法的好處是 程式碼比較不會被 Excel 的外觀拖著走。
6.1 層與層之間流動的是什麼
重要的是 越過層的界線的東西是什麼。這裡決定好,版面變更與業務邏輯變更就能分開推進。
flowchart LR
DB[("DB / API / 檔案")] -->|"原始資料"| RM["ReportModel<br/>只把報表需要的值<br/>整理好並保存"]
TP["Template<br/>xlsx 或 xlsm<br/>外觀、公式、列印設定"] -->|"複製後的活頁簿"| BD
RM -->|"名稱與值的配對"| BD["Binder<br/>把值寫進<br/>具名範圍與表格"]
BD -->|"填好值的活頁簿"| FN["Finisher<br/>轉 PDF 或呼叫 VBA 等<br/>只在需要時"]
FN -->|"產出物"| OUT["xlsx / xlsm / PDF"]
BD -.->|"不需要 Finisher 時<br/>到這裡就完成"| OUT
圖 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 所述。
flowchart TB
accTitle: 經由暫存檔的儲存流程
accDescr: 儲存時先寫滿同一個資料夾裡的暫存檔再用 File.Move 換成原本的名字,失敗時就刪掉暫存檔,藉此防止寫到一半就斷掉的檔案掛著業務上的檔名留下來的流程圖。
sv1["寫進同一個資料夾的暫存檔"] --> sv2{"是否寫完"}
sv2 -->|"成功"| sv3["用 File.Move 換成原本的名字"]
sv2 -->|"失敗"| sv4["刪掉暫存檔並把例外往外傳"]
sv3 -.-> sv5["名字只會在內容齊全時出現"]
圖 11: 業務上的檔名,只給完成的檔案。
7. 常見陷阱
7.1 不要把儲存格位址當成業務規格
一旦 Cells[12, 7] 開始表達業務規則,版面變更就直接變成規格變更。
程式碼透過具名範圍或表格名稱來操作報表,可以撐得比較久。
flowchart TB
accTitle: 不要把儲存格位址當成業務規格
accDescr: 儲存格位址一旦開始表達業務規則版面變更就直接變成規格變更,因此程式碼透過具名範圍或表格名稱來操作報表可以撐得比較久的圖。
ad1["儲存格位址表達業務規則"] --> ad2["版面變更變成規格變更"]
nm1["透過具名範圍或表格名稱操作"] --> nm2["版面改了程式碼也撐得住"]
圖 12: 用名稱而不是位址來操作,決定了報表程式碼的壽命。
7.2 不要把合併儲存格當成資料的入口
合併儲存格是為了外觀而存在的功能。 拿來當套版目標,在新增列或範圍計算時很容易出事。
7.3 不要用「帶著外觀的字串」填入數值或日期
值就以值放入,外觀收攏到儲存格格式比較自然。
7.4 不要讓範本變更變成沒人管的野生流程
範本雖然不是程式碼,實質上卻就是規格本身。 當成版本控管、差異確認、審查的對象來處理比較穩妥。
7.5 要用 COM 就不要輕看 bitness 與生命週期管理
在 COM 自動化或 VBA 整合中,32 位元 / 64 位元的差異、Excel 處理程序的善後、檔案鎖定、使用者環境差異,都會不起眼卻確實地產生影響。
8. 總結
Excel 報表輸出看起來只是「輸出到 Excel」一句話就能講完,實際上有幾個分歧需要先決定。
- 是要驅動 Excel 應用程式
- 還是要組裝 Excel 檔案
- 是使用者電腦,還是無人執行
- 是否保留既有的 VBA 或
.xlsm - 最終產出是 Excel,還是 PDF 或 CSV
實務上的第一候選,範本 + 直接生成 相當強。 再視需要往上加 既有 VBA 的再利用 或 在使用者電腦上做最後的 Excel 處理,這種形式比較容易收斂。
flowchart TB
accTitle: 容易收斂的組合怎麼做
accDescr: 實務上把範本與直接生成放在第一候選,再視需要加上既有 VBA 的再利用或在使用者電腦上做最後的 Excel 處理,這樣的形式比較容易收斂的圖。
sm1["以範本與直接生成為主軸"] --> sm2["需要時加上既有 VBA 的再利用"]
sm1 --> sm3["需要時加上使用者電腦上的最後處理"]
圖 13: 先定下一個主軸,只把例外加上去,就比較容易收斂。
9. 參考資料
依閱讀順序排列。
9.1 決定方式之前要讀的
- Considerations for server-side Automation of Office — 3.1 中「伺服器端的 Office Automation 不受推薦也不受支援」的出處。作為方式選型的根據最常用到
- Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment — 屬於上面那條例外的,M365 RPA 環境下的條件
- Excel specifications and limits — 包含 5.4 的 1,048,576 列 × 16,384 欄在內的上限清單
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 處理大型活頁簿時要深入的
- 如何:使用 SAX(XML 的簡易 API)複製工作表 — 用 Open XML SDK 處理大到放不進記憶體的活頁簿時的手法。在範本套版的範圍內通常用不到
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
什麼是 OLE 物件 —— 內嵌與連結的機制以及業務文件中的陷阱
在 Word 中內嵌 Excel 表格的功能,本質就是 OLE 物件。本文從內嵌與連結的差異、複合檔案與結構化儲存體、In-Place Activation 的機制,一路談到連結中斷、檔案膨脹與資安對策,全部立足於實務視角。
C# 操作 Excel 時 EXCEL.EXE 殘留的問題 ── COM 參照釋放模式與替換判斷
本文從 COM 參照計數與 RCW 的運作原理,整理 C# 透過 Microsoft.Office.Interop.Excel 操作 Excel 時 EXCEL.EXE 進程殘留的問題。內容涵蓋「兩個點規則」的陷阱、Marshal.ReleaseComObject 派與 G...
什麼是 VBA - 限制、未來走向、該被取代的情境與務實的遷移模式
本文從實務角度釐清 VBA 的本質、桌面前提的限制、Excel for the web 與巨集封鎖、64bit 與伺服器自動化的注意事項,並依職責分派 Office Scripts、Office Add-ins、.NET 外移等替代目標,提示階段性遷移的具體步驟。
Windows 列印驅動程式停止提供 ── 業務應用程式的報表與標籤列印如何因應
Microsoft 正分階段推動 v3/v4 列印驅動程式的停止提供,從 2026 年 7 月起會優先選用 IPP 類別驅動程式。本文整理 Windows protected print mode 之下會消失哪些東西,並以判斷表梳理業務應用程式的報表、標籤列印中相依處的盤點...
WinRT 就是 COM —— IInspectable、.winmd、語言投影,以及 WinUI 至今仍立在二進位契約之上的原因
WinRT 不是受管理的執行階段,而是在 COM 之上加了中繼資料(.winmd)與語言投影的 ABI。本文從 IUnknown 與 IInspectable 的關係,一路談到桌面應用程式裡 HWND 初始化與 package identity 的卡關之處。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
要把 Excel 報表輸出如何嵌進 Windows 應用程式或業務系統,這個題目本身就很接近 Windows 應用程式開發,因此和 Windows 應用程式開發服務很合。
技術諮詢 & 設計審查
如果想連同執行環境與維運條件,一起梳理 COM 自動化、Open XML、範本、既有 VBA 的取捨,用技術諮詢與設計審查的形式推進會比較順。
常見問題
整理諮詢這個主題時常見的問題。
- Excel 報表輸出該選 COM 自動化,還是直接產生檔案?
- 最先要看的不是函式庫的名字,而是要驅動 Excel 應用程式,還是要組裝 Excel 檔案。多數業務報表其實不是「操作 Excel」,而是「組裝 Excel 檔案」比較自然;如果是使用者之後會編輯的報表,第一候選就是範本加上 .xlsx/.xlsm 直接生成。只有真的需要 Excel 應用程式本身的行為時,才把 COM 自動化限定在桌面上有人操作的執行情境使用,這樣最自然。
- 可以在伺服器或夜間批次上使用 Excel 的 COM 自動化嗎?
- 避開比較安全。從無人伺服器或服務執行 Office Automation,Microsoft 自己並不推薦也不支援。COM 自動化需要安裝 Excel,還會背上處理程序生命週期、檔案鎖定、對話方塊、bitness、使用者設定檔相依這些問題。夜間批次或大量輸出請收攏到 .xlsx 的直接生成,需要時再讓使用者之後用 Excel 打開比較安全。
- 可以保留既有的 .xlsm 與 VBA 資產,同時做出報表輸出嗎?
- 可以。既有資產還活著的話,不要一次全部重做,把 .xlsm 留作範本,只從外部進行資料套版比較實際。把報表的 UI 與最後的整形留在 VBA,重的計算與 DB、HTTP、業務邏輯移到 C#/.NET 側,這種分法很好用。重點是不要讓職責變得模糊,兩者的界線要用具名範圍、表格、對外介面等固定下來。
- 實作 Excel 報表時該避開的陷阱有哪些?
- 首先重要的是不要把儲存格位址當成業務規格,比起 Cells[12, 7] 這種位址指定,透過具名範圍或表格名稱來操作報表可以撐得更久。合併儲存格是為了外觀的功能,不要拿來當套版目標;數值與日期以值放入,外觀收攏到儲存格格式;範本實質上就是規格,要納入版本控管與審查對象——這幾點也很有用。另外 Excel 單一工作表的上限是 1,048,576 列 × 16,384 欄,明細很大時要先把分工作表或分檔案的方針決定好。