Смотрите мое обновление внизу для получения дополнительной информации.
Иногда у меня есть проекты, в которых я должен выводить некоторые данные в виде файла Excel (формат xlsx). Процесс обычно:
Пользователь нажимает некоторые кнопки в моем приложении
Мой код выполняет запрос к БД и как-то обрабатывает результаты
Мой код генерирует файл * .xlsx, используя библиотеки взаимодействия Excel или какую-либо стороннюю библиотеку (например, Aspose.Cells)
Я могу легко найти примеры кода для того, как сделать это онлайн, но я ищу более надежный способ сделать это. Мне бы хотелось, чтобы мой код следовал некоторым принципам разработки, чтобы обеспечить удобство сопровождения и простоту понимания моего кода.
Вот как выглядела моя первоначальная попытка сгенерировать файл xlsx:
var wb = new Workbook();
var ws = wb.Worksheets[0];
ws.Cells[0, 0].Value = "Header";
ws.Cells[1, 0].Value = "Row 1";
ws.Cells[2, 0].Value = "Row 2";
ws.Cells[3, 0].Value = "Row 3";
wb.Save(path);
Плюсы: не очень. Это работает, так что это хорошо.
Минусы:
- Ссылки на ячейки жестко закодированы, поэтому у меня в коде есть магические числа.
- Трудно добавлять или удалять столбцы и строки без обновления многих ссылок на ячейки.
- Мне нужно изучить какую-нибудь стороннюю библиотеку. Некоторые библиотеки используются как другие библиотеки, но могут быть проблемы. У меня возникла проблема, когда библиотеки com-взаимодействия используют ссылки на ячейки на основе 1, тогда как Aspose.Cells использует ссылки на ячейки на основе 0.
Вот одно решение, которое решает некоторые из минусов, которые я перечислил выше. Я хотел рассматривать таблицу данных как свой собственный объект, который можно перемещать и изменять, не вдаваясь в манипуляции с ячейками и не нарушая другие ссылки на ячейки. Вот некоторый псевдокод:
var headers = new Block(new string[] { "Col 1", "Col 2", "Col 3" });
var body = new Block(new string[,]
{
{ "Row 1", "Row 1", "Row 1" },
{ "Row 2", "Row 2", "Row 2" },
{ "Row 3", "Row 3", "Row 3" }
});
body.PutBelow(headers);
Как часть этого решения, у меня будет некоторый объект BlockEngine, который принимает контейнер Blocks и выполняет манипуляции с ячейками, необходимые для вывода данных в виде файла * .xlsx. К объекту Block может быть прикреплено форматирование.
Плюсы:
- Это удаляет большинство магических чисел, которые были в моем исходном коде.
- Это скрывает много кода манипулирования ячейкой, хотя манипулирование ячейкой все еще требуется в объекте BlockEngine, о котором я упоминал.
- Гораздо проще добавлять и удалять строки, не затрагивая другие части электронной таблицы.
Минусы:
- По-прежнему сложно добавлять или удалять столбцы. Если бы я хотел поменять местами столбцы два и три, мне пришлось бы поменять местами содержимое ячейки. В этом случае это будет восемь правок и, следовательно, восемь возможностей ошибиться.
- Если у меня есть какое-либо форматирование для этих двух столбцов, я должен также обновить его.
- Это решение не поддерживает горизонтальное размещение блоков; Я могу разместить только один блок под другим. Конечно, мог
tableRight.PutToRightOf(tableLeft)
, но это вызвало бы проблемы, если бы tableRight и tableLeft имели разное количество строк. Чтобы разместить таблицы, движок должен знать о каждой другой таблице. Это кажется излишне сложным для меня. - Мне все еще нужно изучать сторонний код, хотя через слой абстракции через объекты Block и BlockEngine код будет менее тесно связан со сторонней библиотекой, чем моя первоначальная попытка. Если бы я хотел поддерживать множество различных вариантов форматирования в слабосвязанной форме, мне, вероятно, пришлось бы писать много кода; мой BlockEngine был бы огромным беспорядком.
Вот решение, которое идет другим путем. Вот процесс:
Я беру данные своего отчета и генерирую XML-файл в каком-то выбранном формате.
Затем я использую преобразование xsl для преобразования файла xml в файл электронной таблицы Excel 2003 XML.
Оттуда я просто преобразовываю электронную таблицу xml в файл xlsx, используя стороннюю библиотеку.
Я нашел эту страницу, которая описывает аналогичный процесс и содержит примеры кода.
Плюсы:
- Это решение практически не требует клеточных манипуляций. Вместо этого вы используете xsl / xpath для выполнения ваших манипуляций. Чтобы поменять местами два столбца в таблице, вы перемещаете целые столбцы в файле xsl, в отличие от других моих решений, которые требуют замены ячеек.
- Хотя вам по-прежнему нужна сторонняя библиотека, которая может конвертировать XML-таблицу Excel 2003 в файл xlsx, это почти все, что вам понадобится для этой библиотеки. Количество кода, которое вам нужно написать, чтобы вызвать стороннюю библиотеку, ничтожно мало.
- Я думаю, что это решение является самым простым для понимания и требует наименьшего количества кода.
- Код, который создает данные в моем собственном формате xml, будет простым.
- Файл xsl будет сложным только потому, что электронная таблица Excel 2003 сложна. Однако легко проверить вывод файла xsl: просто откройте вывод в Excel и проверьте сообщения об ошибках.
- Сгенерировать образцы файлов Excel 2003 XML Spreadsheet очень просто: просто создайте электронную таблицу, которая будет выглядеть как нужный вам файл xlsx, а затем сохраните ее как Excel 2003 XML Spreadsheet.
Минусы:
- XML-таблицы Excel 2003 не поддерживают определенные функции. Например, вы не можете автоматически подбирать ширину столбцов. Вы не можете включать изображения в верхние или нижние колонтитулы. Если вы собираетесь экспортировать полученный xlsx-файл в формат pdf, вы не можете установить закладки в формате pdf. (Я взломал исправление для этого, используя комментарии клетки.). Вы должны сделать это с помощью сторонней библиотеки.
- Требуется библиотека, поддерживающая электронные таблицы Excel 2003 XML.
- Использует 11-летний формат файла MS Office.
Примечание. Я понимаю, что xlsx-файлы на самом деле являются zip-файлами, содержащими xml-файлы, но форматирование xml кажется слишком сложным для моих целей.
Наконец, я рассмотрел решения, включающие SSRS, но он кажется слишком раздутым для моих целей.
Возвращаясь к моему первоначальному вопросу, что такое хороший шаблон проектирования для генерации файлов Excel в коде? Я могу придумать несколько решений, но ни одно из них не кажется идеальным. У каждого есть недостатки.
Обновление: поэтому я попробовал и мое решение BlockEngine, и свое решение для электронных таблиц XML для создания похожих файлов XLSX. Вот мои мнения о них:
Решение BlockEngine:
- Это просто требует слишком много кода, учитывая альтернативы.
- Мне было слишком легко переписать один блок другим, если у меня было неправильное смещение.
- Я изначально заявил, что форматирование может быть прикреплено на уровне блоков. Я обнаружил, что это не намного лучше, чем делать форматирование отдельно от содержимого блока. Я не могу придумать хороший способ объединить контент и форматирование. Я также не могу найти хороший способ держать их отдельно. Это просто беспорядок.
Решение электронной таблицы XML:
- Я собираюсь с этим решением сейчас.
- Следует повторить, что это решение требует гораздо меньше кода. Я эффективно заменяю BlockEngine на сам Excel. Мне все еще нужен хак для таких функций, как закладки и разрывы страниц.
- Формат XML Spreadsheet очень придирчив, но легко внести небольшие изменения и сравнить результаты с существующим файлом в вашей любимой программе Diff. И как только вы поймете какую-то особенность, вы можете поставить ее на место и забыть об этом оттуда.
- Я по-прежнему обеспокоен тем, что это решение опирается на старый формат файла Excel.
- Созданный мной файл XSLT прост в работе. Работа с форматированием здесь намного проще, чем с решением BlockEngine.
источник
Вот решение, которое я часто использовал в прошлом:
создать обычный документ Excel (обычно в формате xlsx) в качестве шаблона, содержащий все заголовки столбцов, включая их заголовок и форматирование по умолчанию для столбцов и, возможно, форматирование для ячеек заголовка.
внедрить этот шаблон в ресурсы вашей программы. Во время выполнения, первый шаг - извлечь шаблон как новый файл и поместить его в папку назначения.
используйте Interop или стороннюю библиотеку, чтобы заполнить данные во вновь созданном xlsx. Не ссылайтесь на жестко закодированные номера столбцов, вместо этого используйте некоторые метаданные (например, заголовки столбцов) для определения правильных столбцов.
Плюсы:
что-то вроде вашего блочного подхода теперь работает лучше. Например, замена столбцов: не нужно ничего менять в коде вашего блока, поскольку правильные столбцы идентифицируются по их заголовкам
Пока ваши столбцы имеют уникальное форматирование, большая часть форматирования может быть выполнена непосредственно в Excel, манипулируя вашим шаблоном. Это дает вам ощущение WYSIWYG вместе со свободой использования любого варианта форматирования, доступного в Excel, без необходимости написания кода для него.
Минусы:
вам все еще нужно использовать стороннюю библиотеку или Interop. Я упоминал, что Interop работает медленно?
когда заголовки столбцов изменяются в вашем шаблоне, вам также необходимо адаптировать свой код (но это легко обнаружить, если иметь процедуру проверки, которая сигнализирует, если ожидаемые столбцы отсутствуют)
когда вам нужно динамическое форматирование разных ячеек в одном столбце, вам все равно придется иметь дело с этим в коде
Как общий совет, какой бы подход вы ни выбрали, у него есть преимущества, позволяющие отделить макет от контента и использовать декларативные решения.
источник
Есть две вещи для рассмотрения:
По поводу первого:
Если электронные таблицы, которые вам нужно сгенерировать , не содержат никакого форматирования или формул , тогда вам довольно просто создать CSV-файл или файл с разделителями табуляции вместо реального XLSX. Excel открывает эти файлы, часто по умолчанию на многих компьютерах. Это не поможет вам с жестким кодированием столбцов и строк, но избавит вас от дополнительной работы с объектной моделью Excel.
Если вам нужно форматирование или формулы, тогда работа с объектной моделью Excel - это разумный путь, особенно если вы создаете электронную таблицу, которая сама по себе не слишком жестко запрограммирована. Другими словами, если ваша электронная таблица использует относительные формулы и имена диапазонов надлежащим образом, она может сочетаться с менее жестким кодированием магических чисел.
По поводу второго:
Вы можете работать по ячейкам за ячейкой с жестко запрограммированными ссылками на строки и столбцы или работать с массивами / коллекциями списков и
for
циклами, чтобы обобщить совокупность ячеек.источник
BlockEngine
решении. Я мог бы взятьIList<IBusinessObject>
и выплюнутьBlock
объект. Плюсы и минусы все равно будут такими же.