При интеграции 1С с внешними информационными системами часто возникает задача выгрузки данных из регистров сведений в формат XML. Если с независимыми регистрами сведений проблем обычно не возникает, то при попытке выгрузить данные из регистров, подчиненных регистратору (например, РаботникиОрганизаций или Кадровая история сотрудников), разработчики часто сталкиваются с техническими трудностями. Метод Прочитать() для набора записей таких регистров требует обязательной установки отбора по регистратору, иначе система возвращает пустой результат или выдает ошибку.
В этой статье мы подробно разберем, почему возникает эта проблема, как правильно обойти ограничение по регистратору, рассмотрим альтернативные и более производительные способы выгрузки данных через запросы и Сериализатор XDTO, а также выясним, как организовать регулярную выгрузку изменений с помощью механизмов платформы 1С.
Давайте проанализируем ситуацию. В платформе 1С:Предприятие регистры сведений могут быть двух типов по способу записи:
Прочитать() и сериализовать его в XML.Попытка прочитать весь подчиненный регистр через пустой объект НаборЗаписей без указания конкретного документа в отборе по регистратору обречена на провал. Платформа спроектирована так, что объект РегистрСведенийНаборЗаписей для подчиненного регистра может одновременно работать с данными только в пределах одного регистратора. Если отбор не установлен, метод Прочитать() либо ничего не вернет, либо вызовет исключение.
Выяснив причину, рассмотрим первый и самый очевидный способ решения проблемы, предложенный участниками форума. Если нам обязательно нужно использовать объект НаборЗаписей для выгрузки структуры в XML «как есть», мы должны сначала получить список всех уникальных регистраторов, которые делали записи в этот регистр. Для этого отлично подойдет запрос.
Разберем по шагам реализацию этого алгоритма:
Шаг 1. Создаем запрос к виртуальной таблице или к физической таблице регистра сведений, чтобы выбрать уникальные ссылки на документы-регистраторы.
Шаг 2. Запускаем цикл по результату запроса.
Шаг 3. На каждой итерации цикла устанавливаем отбор по регистратору в наборе записей, читаем данные и записываем их в XML.
Посмотрим на пример практической реализации этого кода:
// Инициализируем запись XML в строку (или в файл)
ЗаписьXMLСтрока = Новый ЗаписьXML;
ЗаписьXMLСтрока.УстановитьСтроку();
ЗаписьXMLСтрока.ЗаписатьНачалоЭлемента("Root_1C");
// Шаг 1. Запрос для получения уникальных регистраторов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| РаботникиОрганизаций.Регистратор КАК Регистратор
|ИЗ
| РегистрСведений.РаботникиОрганизаций КАК РаботникиОрганизаций
|ГДЕ
| РаботникиОрганизаций.Регистратор ССЫЛКА Документ.ПриемНаРаботуВОрганизацию
| ИЛИ РаботникиОрганизаций.Регистратор ССЫЛКА Документ.КадровоеПеремещениеОрганизаций";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаРегистраторов = РезультатЗапроса.Выбрать();
// Создаем объект набора записей один раз вне цикла для экономии ресурсов
НаборРС3 = РегистрыСведений.РаботникиОрганизаций.СоздатьНаборЗаписей();
// Шаг 2. Обход регистраторов в цикле
Пока ВыборкаРегистраторов.Следующий() Цикл
// Шаг 3. Установка отбора, чтение и запись в XML
НаборРС3.Отбор.Регистратор.Установить(ВыборкаРегистраторов.Регистратор);
НаборРС3.Прочитать();
// Записываем набор записей текущего регистратора в XML
ЗаписатьXML(ЗаписьXMLСтрока, НаборРС3);
КонецЦикла;
ЗаписьXMLСтрока.ЗаписатьКонецЭлемента(); // Root_1C
РезультатСтрокой = ЗаписьXMLСтрока.Закрыть();
Этот способ рабочий, однако у него есть существенный недостаток: если регистраторов в базе тысячи, программа будет делать тысячи отдельных запросов к СУБД (метод НаборРС3.Прочитать() на каждом шаге цикла инициирует обращение к базе данных). Это крайне неэффективно с точки зрения производительности.
Чтобы избежать многократных обращений к базе данных в цикле, мы можем использовать более прогрессивный подход: прочитать все данные регистра за один единственный запрос, выгрузить результат в таблицу значений и затем вручную сериализовать ее структуру в XML.
Рассмотрим подробнее данный вариант. Метод ЗаписатьXML() не умеет автоматически преобразовывать произвольную таблицу значений в стандартный XML-формат регистра сведений, поэтому структуру XML-файла мы будем формировать самостоятельно.
// Записываем данные напрямую в файл для экономии памяти
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\workers_export.xml", "UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("WorkersHistory");
// Получаем все данные регистра одним запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Работники.Период КАК Период,
| Работники.Сотрудник КАК Сотрудник,
| Работники.Организация КАК Организация,
| Работники.ПодразделениеОрганизации КАК Подразделение,
| Работники.Должность КАК Должность,
| Работники.Регистратор КАК Регистратор
|ИЗ
| РегистрСведений.РаботникиОрганизаций КАК Работники";
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Обходим таблицу значений и пишем XML вручную
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("Record");
// Записываем измерения, ресурсы и реквизиты
ЗаписьXML.ЗаписатьНачалоЭлемента("Period");
ЗаписатьXML(ЗаписьXML, СтрокаДанных.Период);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Employee");
ЗаписатьXML(ЗаписьXML, СтрокаДанных.Сотрудник.УникальныйИдентификатор());
// Также можно записать текстовое представление для удобства интеграции
ЗаписьXML.ЗаписатьАтрибут("Presentation", Строка(СтрокаДанных.Сотрудник));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Organization");
ЗаписатьXML(ЗаписьXML, СтрокаДанных.Организация);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Position");
ЗаписатьXML(ЗаписьXML, СтрокаДанных.Должность);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Recorder");
ЗаписатьXML(ЗаписьXML, СтрокаДанных.Регистратор);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента(); // Record
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента(); // WorkersHistory
ЗаписьXML.Закрыть();
Преимущества этого подхода: высокая скорость работы (база данных опрашивается ровно один раз), гибкость настройки формата XML (мы можем выгружать не ссылки 1С, а понятные внешним системам GUID или текстовые представления), минимальное потребление ресурсов сервера.
Если вам все же необходимо получить канонический XML-вид объектов 1С, но вы хотите избежать ручного кодирования каждого тега, на помощь приходит Сериализатор XDTO. Этот механизм позволяет быстро преобразовывать прикладные типы данных в объекты XDTO и автоматически выгружать их в XML-формат.
Рассмотрим, как сериализовать набор записей регистра с помощью XDTO:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\workers_xdto.xml");
ЗаписьXML.ЗаписатьНачалоЭлемента("ExportData");
НаборЗаписей = РегистрыСведений.РаботникиОрганизаций.СоздатьНаборЗаписей();
// Для примера выгрузим записи по конкретному документу
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
НаборЗаписей.Прочитать();
// Сериализуем набор через XDTO-сериализатор базы данных
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, НаборЗаписей, НазначениеТипаXML.Явное);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
Использование СериализаторXDTO гарантирует строгое соблюдение типов данных 1С при выгрузке, корректно описывает пространства имен и предотвращает падение системы при обработке сложных комплексных типов.
В сообщениях 11 и 12 автор темы упомянул, что выгрузка требуется не только разово, но и на регулярной основе для отслеживания изменений в справочнике СотрудникиОрганизаций. Бессистемная полная выгрузка миллионов кадровых записей при каждом сеансе обмена быстро приведет к падению производительности систем.
Для решения задачи регулярной синхронизации изменений в 1С предусмотрен мощный встроенный механизм — Планы обмена. Он позволяет регистрировать изменения только тех объектов (справочников, документов, регистров сведений), которые были изменены, добавлены или удалены с момента последней синхронизации.
Давайте разберем по шагам, как настроить и использовать план обмена для нашей задачи:
ОбменСВнешнейСистемой).СотрудникиОрганизаций и регистр сведений РаботникиОрганизаций. Для регистра сведений указываем режим авторегистрации «Разрешить».После этого платформа 1С автоматически начнет записывать изменения в специальную таблицу регистрации каждый раз, когда пользователь создает или редактирует сотрудника, либо когда проводятся кадровые документы.
Программный код для чтения зарегистрированных изменений и их выгрузки в XML будет выглядеть следующим образом:
// Получаем узел, для которого предназначена выгрузка
УзелПолучатель = ПланыОбмена.ОбменСВнешнейСистемой.НайтиПоКоду("EXT001");
// Начинаем выборку зарегистрированных изменений
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелПолучатель, 1); // 1 - номер сообщения
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\changes_only.xml", "UTF-8");
ЗаписьXML.ЗаписатьНачалоЭлемента("Message");
// Записываем заголовок сообщения обмена
ПланыОбмена.ЗаписатьНачалоСообщения(ЗаписьXML, УзелПолучатель);
Пока ВыборкаИзменений.Следующий() Цикл
ОбъектИзменения = ВыборкаИзменений.Получить();
// Проверяем тип изменившегося объекта
Если ТипЗнч(ОбъектИзменения) = Тип("СправочникОбъект.СотрудникиОрганизаций") Тогда
// Сериализуем изменившегося сотрудника
ЗаписатьXML(ЗаписьXML, ОбъектИзменения);
ИначеЕсли ТипЗнч(ОбъектИзменения) = Тип("РегистрСведенийНаборЗаписей.РаботникиОрганизаций") Тогда
// Изменился набор записей по кадровой истории
ЗаписатьXML(ЗаписьXML, ОбъектИзменения);
КонецЕсли;
КонецЦикла;
ПланыОбмена.ЗаписатьКонецСообщения(ЗаписьXML);
ЗаписьXML.ЗаписатьКонецЭлемента(); // Message
ЗаписьXML.Закрыть();
// После успешной отправки файла подтверждаем получение, чтобы очистить регистрацию изменений
ПланыОбмена.УдалитьРегистрациюИзменений(УзелПолучатель, 1);
Использование этого механизма сокращает объем передаваемых данных в сотни раз, так как выгружаются исключительно дельты изменений.
Если база данных 1С содержит десятки тысяч сотрудников, стандартные методы могут вызывать переполнение оперативной памяти (OutOfMemory) или зависание сервера. Придерживайтесь следующих рекомендаций при проектировании регулярных обменов:
ЗаписьXML.УстановитьСтроку() заставляет платформу держать весь сформированный текст XML-документа в оперативной памяти сервера. Всегда используйте запись напрямую в физический файл на диске через ЗаписьXML.ОткрытьФайл(). Это позволяет сбрасывать данные порциями.ЗаписьJSON и потребляет меньше трафика.СрезПоследних на текущую дату. Получить ее данные можно только через запрос, что опять же решает проблему отсутствия отбора по регистратору.Таким образом, для разовой выгрузки кадровой истории подчиненного регистра лучше всего использовать точечные запросы со сборкой структуры XML вручную или через XDTO. Для регулярной синхронизации незаменимым инструментом являются планы обмена, которые гарантируют доставку только измененных сведений без лишней нагрузки на систему.