Разработчики в среде 1С часто сталкиваются с задачей сохранения промежуточных или рабочих табличных данных между сеансами работы пользователей. Естественное желание любого программиста — сделать это быстро, просто и с минимальным написанием кода. Иногда это желание подталкивает к нестандартным решениям, которые на первый взгляд кажутся изящными, но в реальной эксплуатации превращаются в серьезные архитектурные проблемы.
В этой статье мы подробно разберем ситуацию, когда разработчик пытается использовать табличную часть (ТЧ) единственного элемента справочника или документа в качестве «хранилища» для временной таблицы, чтобы избежать работы с регистрами сведений. Мы проанализируем, почему такой подход является ошибочным, какие скрытые угрозы он в себе таит, и рассмотрим правильные, производительные альтернативы с готовыми примерами кода.
При работе в 1С с внешними обработками или временными формами данные табличных частей стираются при закрытии сессии. Чтобы сохранить эти данные, их необходимо физически записать в базу данных. Традиционные объектные методы работы с табличными частями очень удобны. Разработчику доступны такие простые и понятные методы, как Очистить(), Загрузить(), Добавить(), а также возможность легкой привязки к элементу формы типа ТабличноеПоле.
Работать с независимыми регистрами сведений многим кажется более трудоемким процессом: необходимо создавать наборы записей, настраивать отборы, управлять менеджерами записи. Из-за этого возникает соблазн создать в конфигурации специальный справочник (или документ) со служебной табличной частью, программно создать в нем один-единственный элемент и использовать его табличную часть для записи, чтения и очистки данных. На первый взгляд код выглядит просто, но этот подход имеет критические недостатки.
Рассмотрим подробнее, почему использование табличной части одного элемента справочника для постоянной перезаписи данных — это плохая практика:
ПолучитьОбъект() платформа считывает элемент из базы данных в память сервера полностью — со всеми его реквизитами и всеми строками табличной части. Если в вашей таблице накопилось 50 000 строк, то при каждом чтении или записи система будет гонять этот массив данных между СУБД, сервером приложений и клиентом. Это приведет к зависаниям интерфейса и перерасходу памяти. В то же время регистры сведений позволяют считывать и записывать данные порционно с помощью запросов.Записать() для объекта справочника СУБД выполняет ресурсоемкую операцию: она полностью удаляет все старые строки табличной части этого элемента в таблице базы данных, а затем строчно вставляет новые. Это порождает колоссальную нагрузку на дисковую подсистему и базу данных.Выясним причину, почему разработчики боятся регистров сведений. Основной аргумент — «сложный» код для очистки и перезаписи данных. На самом деле это миф. Давайте посмотрим на реальные примеры кода для работы с независимым регистром сведений (без измерений или с минимальным набором измерений).
Для полной очистки таблицы регистра сведений не нужно перебирать записи в цикле. Достаточно записать пустой набор записей. Это делается буквально в одну строку:
// Полная очистка независимого регистра сведений
РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей().Записать();
Если нам необходимо полностью заменить данные в регистре сведений на новые, полученные, например, из Таблицы Значений (ТЗ), код также будет предельно кратким и лаконичным. Разберем этот процесс по шагам:
// 1. Создаем набор записей регистра
НаборЗаписей = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
// 2. Загружаем таблицу значений (структура колонок ТЗ должна совпадать с полями регистра)
НаборЗаписей.Загрузить(ВашаТаблицаЗначений);
// 3. Записываем набор в базу данных с заменой существующих записей
НаборЗаписей.Записать(Истина);
Этот код работает в разы быстрее, чем перезапись табличной части справочника, не вызывает объектных блокировок и не имеет лимита в 99 999 строк.
Если вам все же очень хочется работать с регистром сведений в «объектном» стиле (используя привычные методы Очистить(), Загрузить() и Выгрузить()), вы можете один раз написать программный интерфейс в модуле менеджера вашего регистра сведений. Это красивое и профессиональное решение.
Откройте модуль менеджера регистра сведений ИмяВашегоРегистра и добавьте в него следующие экспортные процедуры и функции:
// Процедура полной очистки регистра
Процедура Очистить() Экспорт
Набор = СоздатьНаборЗаписей();
Набор.Записать(Истина);
КонецПроцедуры
// Функция выгрузки всех данных регистра в Таблицу Значений
Функция Выгрузить() Экспорт
Набор = СоздатьНаборЗаписей();
Набор.Прочитать();
Возврат Набор.Выгрузить();
КонецФункции
// Процедура загрузки данных из Таблицы Значений с полной заменой
Процедура Загрузить(ТаблицаДанных) Экспорт
Набор = СоздатьНаборЗаписей();
Набор.Загрузить(ТаблицаДанных);
Набор.Записать(Истина);
КонецПроцедуры
После этого в любом месте конфигурации вы сможете работать с регистром сведений точно так же легко и прозрачно, как и с табличной частью справочника:
// Пример использования объектных методов регистра в коде:
// 1. Получаем данные в Таблицу Значений
ТЗ_Данные = РегистрыСведений.ИмяВашегоРегистра.Выгрузить();
// 2. Делаем любые манипуляции с Таблицей Значений (поиск, удаление, добавление строк)
// ...
// 3. Очищаем регистр
РегистрыСведений.ИмяВашегоРегистра.Очистить();
// 4. Записываем обновленные данные обратно
РегистрыСведений.ИмяВашегоРегистра.Загрузить(ТЗ_Данные);
Такой подход полностью решает проблему удобства разработки, сохраняя при этом все преимущества производительности и надежности регистров сведений перед табличными частями.
Проанализируем еще одну альтернативу. Если вам нужно просто хранить плоскую таблицу значений целиком от сессии к сессии, и при этом вам не требуется делать к ней сложные запросы в базе данных, можно использовать объект метаданных Константа с типом данных ХранилищеЗначения.
Этот способ позволяет «упаковать» любую таблицу значений (вместе с типами колонок и данными) в бинарный вид и сохранить в одной системной записи. Посмотрим на пример реализации:
// Код для сохранения Таблицы Значений в константу:
Константы.ИмяВашейКонстанты.Установить(Новый ХранилищеЗначения(ВашаТаблицаЗначений));
// Код для извлечения Таблицы Значений из константы:
Хранилище = Константы.ИмяВашейКонстанты.Получить();
Если Хранилище <> Неопределено Тогда
ВашаТаблицаЗначений = Хранилище.Получить();
КонецЕсли;
Преимущества данного метода: данные не засоряют структуру метаданных, сохраняются очень быстро одной операцией записи, а сама таблица восстанавливается в один клик. Нет необходимости создавать искусственные справочники-пустышки.
Если конфигурация является локальной, а данные табличной части нужны только конкретному пользователю на его рабочем компьютере, идеальным решением будет сохранение таблицы значений в локальный файл формата .xml или во внутренний бинарный формат 1С.
Рассмотрим пример сохранения и восстановления таблицы значений на диске:
// Путь к файлу для сохранения данных
ПутьКФайлу = КаталогВременныхФайлов() + "temp_table_data.dat";
// 1. Сохранение таблицы значений на диск
ЗначениеВФайл(ПутьКФайлу, ВашаТаблицаЗначений);
// 2. Восстановление таблицы значений из файла при запуске новой сессии
Если ФайлСуществует(ПутьКФайлу) Тогда
ВашаТаблицаЗначений = ЗначениеИзФайла(ПутьКФайлу);
КонецЕсли;
Этот способ работает мгновенно, полностью исключает какие-либо блокировки базы данных СУБД и не раздувает размер самого файла базы данных .1CD или таблиц SQL.
Использование табличной части единственного элемента справочника или документа для сохранения промежуточных данных — это классический «антипаттерн» разработки в 1С. Стремление сэкономить несколько строк кода приводит к созданию архитектурного долга, который обязательно проявит себя в виде зависаний, блокировок или потери данных при превышении лимита строк.
Для правильной организации хранения данных рекомендуем придерживаться следующих правил:
Загрузить() и Очистить() в его модуле менеджера.ХранилищеЗначения.ЗначениеВФайл() и ЗначениеИзФайла().