Как построить вывод отчётов Excel: COM / Open XML / шаблоны
· Обновлено: · Го Комура · Excel, Отчёты, Разработка Windows, Office, COM, Open XML
История изменений (1 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619726)
Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.
Го Комура (2026). Как построить вывод отчётов Excel: COM / Open XML / шаблоны. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619726 https://comcomponent.com/ru/blog/2026/03/16/010-excel-report-output-how-to-build/
- DOI (последняя версия)
- 10.5281/zenodo.21619726
- DOI (эта версия)
- 10.5281/zenodo.21619727
В консультациях по выводу отчётов Excel за фразой «хотим вывести в Excel» нередко смешаны сразу несколько разных требований.
- Пользователь потом хочет править документ вручную
- Нужно сохранить уже существующий
.xlsm - Хочется оставить как есть сводные таблицы, диаграммы и настройки печати
- Нужно генерировать большой объём ночным пакетным заданием
- Требуется запуск без участия человека на сервере
- Также нужен PDF
Одним способом всё это чисто не закрыть. Первое, на что смотреть, — не имя библиотеки, а то, запускаете ли вы приложение Excel или собираете файл Excel.
Если ошибиться здесь, сначала всё заработает, а сопровождение потом станет тяжёлым. В этой статье, исходя из вывода отчётов Excel в Windows-приложениях и бизнес-системах, разбираем выбор между COM-автоматизацией / Open XML / подстановкой данных в шаблон / совместным использованием существующего VBA.
flowchart TB
accTitle: Развилка, которую стоит смотреть первой
accDescr: Рисунок показывает, что в выводе отчётов Excel важнее не имя библиотеки, а выбор — запускать приложение Excel или собирать файл Excel, и что ошибка здесь приводит к тому, что сначала всё работает, а сопровождение потом становится тяжёлым.
q1{"Запускать приложение Excel или собирать файл?"}
q1 -->|"Запускать приложение"| a1["Способ, который автоматизирует Excel"]
q1 -->|"Собирать файл"| a2["Способ, который собирает xlsx напрямую"]
q1 -.-> w1["Ошибка здесь усложняет сопровождение"]
Рис. 1: До выбора библиотеки решают, управлять приложением или собирать файл.
Для кого эта статья и какие допущения
Текст рассчитан на разработчиков, которым сейчас нужно выбрать способ вывода отчётов Excel из бизнес-системы.
Допущение — вывод идёт из приложения или пакетного задания на C# / .NET, которое работает в Windows. Существующие VBA-наработки тоже учитываются, но и в этом случае речь не про «закрыть всё одним VBA», а про разделение ролей со стороной .NET. Примеры кода — C# / .NET 8.
Термины, которые стоит зафиксировать заранее
| Термин | Смысл |
|---|---|
| Open XML | Формат файлов начиная с Office 2007. Сущность .xlsx — ZIP-архив из XML-файлов, его можно собрать программой, не запуская Excel |
| COM-автоматизация (Office Automation) | Способ, при котором реально запускается приложение Office вроде Excel и им управляет внешняя программа. COM — механизм вызовов между компонентами Windows, и Excel открывает для него интерфейс |
| bitness | 32-бит или 64-бит: как собирают и как запускают. В COM-автоматизации соединение падает, если разрядность вызывающей стороны и самого Excel не совпадает |
| Именованный диапазон | Имя, которое в Excel дают ячейке или диапазону. Вместо адреса вроде Cells[12, 7] точку подстановки данных задают этим именем |
| Таблица (ListObject) | Структура, которую создаёт «Форматировать как таблицу». При добавлении строки формат и формулы сами растягиваются, поэтому таблица удобна как вход для детализации |
1. Сначала вывод
Сначала только выводы.
- Если пользователь потом открывает отчёт в Excel и правит его, первый кандидат — шаблон + прямая генерация
.xlsx/.xlsm. - Если генерация автоматическая — на сервере / в службе / по расписанию, — безопаснее не закладываться на автоматизацию Office.
- Если нужно сохранить существующие
.xlsm, VBA, диаграммы, сводные таблицы и настройки печати, конструкция меньше ломается, когда макет и специфичные для Excel возможности живут в шаблоне, а код занимается только подстановкой данных. - COM-автоматизацию естественно использовать только тогда, когда действительно нужно поведение самого приложения Excel, — и ограничивать её выполнением на рабочем столе в присутствии пользователя.
- Если речь просто о выгрузке списка, требованиям с самого начала часто лучше отвечают CSV / PDF / веб-страница.
Иными словами, для большинства бизнес-отчётов естественнее не «управлять Excel», а «собирать файл Excel».
flowchart TB
accTitle: Первый кандидат по требованиям
accDescr: Рисунок показывает, что отчёт, который пользователь потом правит, первым кандидатом имеет шаблон и прямую генерацию, что автоматическая генерация на сервере или в службе не должна опираться на автоматизацию Office, и что COM-автоматизацию ограничивают выполнением в присутствии пользователя только когда нужно поведение самого Excel.
r1["Отчёт, который потом правят"] --> s1["Шаблон и прямая генерация"]
r2["Автогенерация без человека"] --> s2["Не закладываться на автоматизацию Office"]
r3["Нужно поведение самого Excel"] --> s3["COM-автоматизацию — только attended"]
Рис. 2: Кто и где запускает генерацию — и первый кандидат почти определён.
Карта знаний этой статьи
Вывод отчётов Excel сильно расходится по устройству в зависимости от того, управляют приложением Excel или собирают файл Excel напрямую. COM-автоматизация на необслуживаемом сервере у Microsoft прямо названа неподдерживаемой; для ночных пакетных заданий и массового вывода подходит прямая генерация через Open XML SDK, ClosedXML, NPOI или EPPlus. Для отчётов, которые пользователь потом редактирует, первый кандидат — оставить внешний вид в шаблоне и подставлять значения в именованные диапазоны и таблицы; разделение на четыре слоя ReportModel, Template, Binder и Finisher снижает зависимость от адресов ячеек и влияние смены макета. Сохранение: полностью записывают во временный файл, затем безопасно подменяют через FileMode.CreateNew и File.Move; если строки детализации не помещаются, не обрезают молча, а останавливают исключением — это практический акцент.
flowchart LR
accTitle: Карта знаний: как строить вывод отчётов Excel
accDescr: Схема выбора между способами реализации вывода отчётов — COM-автоматизация, прямая генерация xlsx, подстановка в шаблон, совместное использование существующего VBA, Graph API — четырёхслойной структуры ReportModel/Template/Binder/Finisher и безопасного шаблона сохранения файла
excel_report_output["вывод отчётов Excel"]
report_template_method["способ подстановки в шаблон"]
excel_com_automation["автоматизация Excel через COM (Excel COM Interop)"]
xlsx_direct_generation["прямая генерация .xlsx"]
server_side_office_automation["серверная автоматизация Office"]
closedxml["ClosedXML"]
open_xml_sdk["Open XML SDK"]
npoi["NPOI"]
epplus["EPPlus"]
named_range["именованный диапазон"]
list_object_table["Таблица (ListObject)"]
binder_layer["слой Binder"]
report_model_layer["слой ReportModel"]
template_layer["слой Template"]
finisher_layer["слой Finisher"]
atomic_file_write_pattern["безопасная запись через временный файл"]
partial_file_risk["риск оставления обрезанного файла"]
filemode_createnew["FileMode.CreateNew"]
silent_overwrite_risk["риск тихой перезаписи одноимённого файла"]
detail_overflow_guard["исключение при переполнении строк детализации"]
silent_truncation_risk["риск тихого усечения строк детализации"]
cell_address_hardcoding["хардкод адресов ячеек как бизнес-спецификация"]
excel_sheet_row_limit["лимит листа Excel (1,048,576 строк × 16,384 столбцов)"]
graph_excel_api["Microsoft Graph Excel API"]
existing_vba_reuse["сохранение существующего VBA"]
excel_report_output -->|"использует"| excel_com_automation
excel_report_output -->|"использует"| xlsx_direct_generation
report_template_method -->|"рекомендуется для"| excel_report_output
excel_com_automation -->|"не рекомендуется"| server_side_office_automation
report_template_method -.->|"использует"| closedxml
closedxml -->|"использует"| open_xml_sdk
xlsx_direct_generation -.->|"использует"| open_xml_sdk
xlsx_direct_generation -.->|"использует"| closedxml
xlsx_direct_generation -.->|"использует"| npoi
xlsx_direct_generation -.->|"использует"| epplus
report_template_method -->|"использует"| named_range
report_template_method -->|"использует"| list_object_table
binder_layer -->|"использует"| named_range
binder_layer -->|"использует"| list_object_table
binder_layer -->|"требует"| report_model_layer
binder_layer -->|"требует"| template_layer
finisher_layer -->|"требует"| binder_layer
finisher_layer -.->|"использует"| excel_com_automation
atomic_file_write_pattern -->|"предотвращает"| partial_file_risk
atomic_file_write_pattern -->|"использует"| filemode_createnew
filemode_createnew -->|"предотвращает"| silent_overwrite_risk
detail_overflow_guard -->|"предотвращает"| silent_truncation_risk
cell_address_hardcoding -->|"не рекомендуется"| excel_report_output
xlsx_direct_generation -.->|"требует"| excel_sheet_row_limit
graph_excel_api -.->|"не рекомендуется"| excel_report_output
existing_vba_reuse -->|"использует"| named_range
existing_vba_reuse -->|"использует"| list_object_table
report_template_method -->|"использует"| atomic_file_write_pattern
report_template_method -->|"использует"| detail_overflow_guard
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 29, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Что решить в первую очередь
Сведём в таблицу то, что стоит решить заранее при выводе отчётов Excel.
| Что уточнить | Почему решить это заранее |
|---|---|
Каким будет конечный результат — .xlsx / .xlsm / PDF / CSV |
Уже это сильно сужает выбор способа |
| Будет ли пользователь править результат в Excel после вывода | Если правка предполагается, важны функции Excel и сохранение макета |
| Где выполняется генерация — на ПК пользователя или на сервере / в службе / в пакетном задании | Это сильно меняет, где вообще уместна COM-автоматизация |
| Нужно ли сохранить существующие VBA / макросы / надстройки | Понадобится проектирование .xlsm-шаблона и поэтапного перехода |
| Нужно ли жёстко зафиксировать диаграммы, сводные таблицы, область печати, колонтитулы | Надёжнее вынести это в шаблон, а не в код |
| Сколько строк, файлов и параллельных запусков приходится на одну генерацию | При большом объёме прямая генерация обычно подходит лучше, чем COM |
| Кто будет менять внешний вид отчёта | Если это делают не только разработчики, но и сотрудники на местах, шаблонный подход сочетается лучше |
3. Основные способы реализации
3.1 COM-автоматизация Excel
Способ, при котором запускается Excel, а Workbook, Worksheet и Range управляются через COM.
Проще всего понимать его как управление настоящим Excel «за рулём».
Сильная сторона — можно напрямую использовать поведение, которое есть только у Excel. Он хорошо сочетается с существующими книгами, диаграммами, сводными таблицами, настройками печати, макросами и экспортом в PDF и позволяет работать с тем, «как Excel в итоге всё покажет».
Слабые стороны тоже чёткие.
- Нужна установка Excel
- Приходится иметь дело со временем жизни процесса, блокировками файлов, диалоговыми окнами, разрядностью (bitness) и зависимостью от профиля пользователя
- Сама Microsoft не рекомендует и не поддерживает Office Automation с безлюдного сервера или из службы
Третий пункт — самое сильное утверждение этой статьи, поэтому источник стоит назвать явно. В статье поддержки Microsoft «Considerations for server-side Automation of Office» прямо сказано, что серверную Office Automation компания не рекомендует и не поддерживает. Приводятся пять причин.
| Причина | Содержание |
|---|---|
| Идентификатор пользователя | Office рассчитан на присутствие пользователя и читает параметры реестра для каждого пользователя. В службе, которая работает от учётки без профиля пользователя, это ломается |
| Интерактивность рабочего стола | Office рассчитан на интерактивный рабочий стол и может показать модальный диалог. В среде, где закрыть его некому, поток так и остаётся стоять |
| Реентерабельность и масштабируемость | Приложения Office — однопоточные COM-серверы и не реентерабельны. Они спроектированы под одного клиента и не выдерживают параллельного выполнения, нужного серверу |
| Устойчивость и стабильность | Функции установки при первом использовании могут неожиданно показать диалог; для серверного размещения тесты не проводились |
| Безопасность на стороне сервера | Нет средств контроля доступа, рассчитанных на распределённые компоненты, запросы не аутентифицируются. Есть риск, что кэшированные учётные данные окажутся общими для нескольких клиентов |
Для RPA-среды Microsoft 365 отдельно разобрано в «Considerations for unattended automation of Office». Если этот пункт стал спорным при выборе способа, опирайтесь на эти две статьи.
flowchart TB
accTitle: Место серверной автоматизации
accDescr: Рисунок показывает, что Office Automation с безлюдного сервера или из службы Microsoft не рекомендует и не поддерживает, и что при споре о выборе способа можно опереться на статью поддержки и на отдельную статью про RPA-среду M365.
sv1["Office Automation с безлюдного сервера"] --> sv2["Microsoft не рекомендует и не поддерживает"]
sv2 -.-> sv3["Статья поддержки служит основанием"]
sv2 -.-> sv4["RPA-среда M365 разобрана в отдельной статье"]
Рис. 3: Годится ли этот способ — не вопрос вкуса: его закрывают официальные источники.
3.2 Прямая генерация .xlsx
.xlsx — формат Open XML, поэтому файл можно собрать напрямую, не запуская Excel.
Средствами вроде Open XML SDK программа может работать с книгой, листами, ячейками, стилями и таблицами.
Сильная сторона этого подхода — он легче работает в среде без установленного Excel и хорошо сочетается с пакетными заданиями и серверами.
С другой стороны, становится тяжелее, когда нужно естественно воспроизвести поведение, которое ближе к UI самого Excel. Автоподбор ширины столбцов, разбиение на страницы, сложное оформление, глубокое редактирование существующих книг — если пытаться сделать всё это аккуратно одним кодом, объём кода быстро растёт.
flowchart TB
accTitle: Характер прямой генерации
accDescr: Рисунок показывает, что xlsx — формат Open XML и его можно собрать без запуска Excel, что это хорошо сочетается со средой без Excel, пакетными заданиями и серверами, но попытка воспроизвести поведение ближе к UI Excel раздувает код.
dg1["xlsx — формат Open XML"] --> dg2["Собирают, не запуская Excel"]
dg2 --> dg3["Хорошо сочетается с batch и сервером"]
dg2 -.-> dg4["Воспроизведение UI раздувает код"]
Рис. 4: Сильная сторона «Excel не нужен» и слабость в воспроизведении внешнего вида — две стороны одного подхода.
Библиотек для работы с .xlsx из .NET несколько, и на практике упираются в условия лицензии. Часто рассматривают вот этот набор.
| Библиотека | Лицензия | Место |
|---|---|---|
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 или коммерческая лицензия | Функций много, но для коммерческого использования нужна платная лицензия. Если выбирать по памяти о LGPL времён версии 4, легко ошибиться с лицензией |
Если выбран способ подстановки в шаблон, внешний вид держит шаблон, а от кода требуется только «записать значения в заранее заданные входы». Поэтому обёртки с меньшим объёмом кода оказываются удобнее.
3.3 Подстановка данных в шаблон
На практике проще всего рекомендовать способ, при котором сначала делают шаблон Excel, а код занимается только подстановкой данных.
Внешний вид отчёта, формулы, условное форматирование, область печати, колонтитулы, логотип и диаграммы остаются на стороне шаблона. Код копирует шаблон и записывает данные в заранее заданные «точки входа» — именованные диапазоны, таблицы, диапазоны ячеек.
Благодаря этому правки макета и правки бизнес-логики оказываются разделены.
Так гораздо легче избежать типичного для Excel-отчётов ада вида Cells[37, 9] = ....
flowchart TB
accTitle: Разделение ролей при подстановке в шаблон
accDescr: Рисунок показывает, что внешний вид, формулы и настройки печати живут в шаблоне, а код только копирует шаблон и записывает данные в заранее заданные входы — именованные диапазоны и таблицы, — поэтому правки макета и бизнес-логики разделяются.
tp1["Сторона шаблона"] --> tp2["Держит вид, формулы, печать"]
tc1["Сторона кода"] --> tc2["Только пишет значения во входы"]
tp2 --> tw1["Правки макета и логики разделены"]
tc2 --> tw1
Рис. 5: Суть способа — развести внешний вид и логику по своим местам.
3.4 Сохранение существующих VBA-наработок
Если существующий .xlsm и VBA ещё живы, чаще естественнее не переписывать всё разом.
Вполне реалистичное разделение — оставить UI отчёта и финальное оформление в VBA, а тяжёлые вычисления, работу с БД / 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 и его хотят совместно использовать из веб- или мобильного приложения, в число вариантов попадает и Excel API Microsoft Graph.
Однако это не общее решение, чтобы на локальном ПК массово штамповать произвольные файлы. Права доступа, место хранения, сессии и эксплуатация с самого начала опираются на M365.
3.6 Нужен ли вообще именно Excel
Если требование к отчёту — «таблица, с которой потом будет работать человек», выбор Excel естествен. Но для требований ниже часто честнее другой формат.
- Печать и архивное хранение -> PDF
- Импорт в другую систему -> CSV / TSV / JSON
- Достаточно просмотра в браузере -> HTML / веб-страница
- Основная цель — агрегация и визуализация -> BI или дашборд
flowchart TB
accTitle: Проверка, нужен ли именно Excel
accDescr: Рисунок показывает, что если отчёт — таблица, с которой потом работает человек, выбор Excel естествен, но для печати и хранения, импорта в другую систему, просмотра в браузере, агрегации и визуализации часто честнее другой формат.
ne1{"Таблица, с которой потом работают?"}
ne1 -->|"Работают"| ne2["Выбор Excel естествен"]
ne1 -->|"Не работают"| ne3["Смотреть другой формат"]
ne3 -.-> ne4["PDF / CSV / веб / 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. Архитектура, которую проще рекомендовать на практике
На практике меньше ломается архитектура, разделённая на четыре слоя.
| Слой | Роль | Чего здесь не делают |
|---|---|---|
| ReportModel | Формирует значения, нужные отчёту | Ничего не знает об адресах ячеек |
| Template | Держит внешний вид, формулы, настройки печати, диаграммы | Ничего не знает о БД и бизнес-логике |
| 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: Между четырьмя слоями текут только пары имя-значение; адрес ячейки за 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}");
В этом коде сознательно держат пять пунктов.
- Адреса ячеек в коде нет. Точка подстановки — только именованные диапазоны. Даже если в шаблон добавить одну строку, этот код не меняется
- Шаблон не перезаписывают. Открытую книгу всегда сохраняют под другим именем
- Даты и числа вносят как значения. Если вставить их отформатированными строками, в 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, не недооценивать разрядность и управление временем жизни
В 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 — условия в RPA-среде M365, которые являются исключением к предыдущему пункту
- Excel specifications and limits — перечень пределов, включая 1 048 576 строк × 16 384 столбца из раздела 5.4
9.2 Что использовать при прямой генерации
- About the Open XML SDK for Office — основа, на которой
.xlsxсобирают без Excel - ClosedXML — обёртка из примера кода в разделе 6.2. Лицензия MIT
- NPOI — вариант, когда нужно работать и со старым
.xls. Apache License 2.0 - EPPlus — условия лицензии начиная с версии 5 обязательно проверить до внедрения
9.3 Если работа идёт в M365 / SharePoint
- Обзор API книг и диаграмм Excel — Microsoft Graph
- Доступ к OneDrive и SharePoint через Microsoft Graph API
9.4 Когда книга не помещается в память
- Как скопировать лист с помощью SAX (простой API для XML) — приём работы с Open XML SDK, когда книга не помещается в память. В рамках подстановки в шаблон обычно не нужен
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Почему после работы с Excel из C# остаётся EXCEL.EXE — освобождение COM-ссылок и решение о замене
Разбираем, почему при автоматизации Excel из C# через COM остаётся процесс EXCEL.EXE: счётчик ссылок COM, RCW, ловушка «правила двух точе...
Что такое VBA — ограничения, перспективы, когда стоит заменить и как мигрировать
Разбираем, что такое VBA, его ограничения и перспективы, в каких случаях его стоит заменить и как поэтапно переносить макросы Excel и вну...
Перенос макросов Excel VBA в Power Automate — что заменить Office Scripts, а что оставить в VBA
Разбираем, можно ли перенести макросы Excel VBA в Power Automate: что заменяется Office Scripts, что умеет только VBA, ограничения коннек...
Печать и PDF в Windows-приложениях: PrintDocument, WPF и библиотеки отчётов
Печать WinForms через PrintDocument, печать WPF через FlowDocument и FixedDocument и варианты вывода PDF сведены в таблицу по требованиям...
Автоматизация бизнес-процессов в Power Automate — облачные потоки, потоки на компьютере и обработка ошибок
Разбираем, чем облачный поток Power Automate отличается от потока на компьютере, как их сочетать с PowerShell и VBA, какие лицензии нужны...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Миграция ActiveX
Решения о сохранении, обёртке или замене компонентов COM / ActiveX / OCX.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Как встроить вывод отчётов Excel в Windows-приложение или бизнес-систему — тема, по сути близкая к самой разработке Windows-приложений, поэтому она хорошо сочетается с услугой разработки Windows-приложений.
Технические консультации и ревью дизайна
Если нужно разобрать, когда брать COM-автоматизацию, Open XML, шаблоны и существующий VBA, с учётом среды выполнения и условий эксплуатации, это удобно вести как техническую консультацию и ревью архитектуры.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что выбрать для вывода отчётов Excel — COM-автоматизацию или прямую генерацию файла?
- Смотреть в первую очередь нужно не на имя библиотеки, а на то, запускаете ли вы приложение Excel или собираете файл Excel. Для большинства бизнес-отчётов естественнее не «управлять Excel», а «собирать файл Excel». Если пользователь потом правит отчёт вручную, первый кандидат — шаблон плюс прямая генерация .xlsx/.xlsm. COM-автоматизацию естественно оставлять только тогда, когда действительно нужно поведение самого приложения Excel, — и ограничивать её выполнением на рабочем столе в присутствии пользователя.
- Можно ли использовать COM-автоматизацию Excel на сервере или в ночном пакетном задании?
- Безопаснее этого избегать. Сама Microsoft не рекомендует и не поддерживает Office Automation с безлюдного сервера или из службы. COM-автоматизация требует установленного Excel и тянет за собой время жизни процесса, блокировки файлов, диалоговые окна, разрядность (bitness) и зависимость от профиля пользователя. Для ночных пакетных заданий и большого объёма безопаснее склоняться к прямой генерации .xlsx, а при необходимости давать пользователю позже открыть файл в Excel.
- Можно ли сделать вывод отчётов, сохранив существующие .xlsm и VBA?
- Можно. Если существующие наработки ещё живы, реалистичнее не переписывать всё разом, а оставить .xlsm как шаблон и снаружи делать только подстановку данных. Удобное разделение: UI отчёта и финальное оформление остаются в VBA, а тяжёлые вычисления, БД, HTTP и бизнес-логика уходят на сторону C#/.NET. Важно не оставлять зоны ответственности размытыми: границу между сторонами фиксируют именованные диапазоны, таблицы и публичные интерфейсы.
- Каких ловушек стоит избегать при реализации отчётов Excel?
- Прежде всего не превращать адреса ячеек в бизнес-спецификацию: код живёт дольше, если обращается к отчёту через именованные диапазоны и имена таблиц, а не через адрес вроде Cells[12, 7]. Объединённые ячейки — функция для внешнего вида, их не стоит брать точкой входа для данных. Числа и даты вносить как значения, а оформление отдавать формату ячейки. Шаблон по сути и есть спецификация, поэтому его стоит версионировать и проводить ревью. Кроме того, предел одного листа Excel — 1 048 576 строк × 16 384 столбца, поэтому при большой детализации заранее решают, как дробить листы или файлы.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.