При организации обмена данными в 1С:Предприятии нередко возникает задача: объект зарегистрирован к выгрузке, однако по определенным бизнес-условиям его не следует отправлять в текущем пакете сообщений. При этом крайне важно сохранить регистрацию объекта, чтобы он гарантированно ушел в одном из следующих сеансов обмена. Разберем внутренние механизмы платформы 1С, выясним причину возможной потери данных и рассмотрим правильные варианты реализации такого сценария.
Для понимания сути проблемы проанализируем, как платформа 1С работает с регистрацией изменений. Выгрузка изменений стандартно инициируется следующим вызовом:
Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, ЗаписьСообщения.НомерСообщения);
Пока Выборка.Следующий() Цикл
Об = Выборка.Получить();
// Обработка и запись объекта в XML
КонецЦикла;
В момент выполнения метода ПланыОбмена.ВыбратьИзменения платформа на уровне СУБД выполняет массовое обновление таблиц регистрации изменений: всем записям с пустым номером (NULL) присваивается текущий номер исходящего сообщения ЗаписьСообщения.НомерСообщения.
Когда база-приемник успешно загрузит пакет, она отправит ответную квитанцию с подтверждением. При получении квитанции вызывается метод ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерПринятого), который безоговорочно удаляет из таблиц изменений все записи, номер сообщения которых меньше либо равен подтвержденному номеру.
Отсюда возникает главная опасность: если мы просто пропустим запись объекта в XML-поток, объект все равно останется привязан к текущему номеру пакета. Как только узел-получатель подтвердит прием, платформа удалит запись об изменении этого объекта из таблицы регистрации. В результате приемник объект так и не получит, а источник посчитает его успешно переданным, что приведет к рассинхронизации данных.
В типовых событиях модуля плана обмена (ПриОтправкеДанныхПодчиненному или ПриОтправкеДанныхГлавному) доступно управление отправкой через параметр ОтправкаЭлемента:
Процедура ПриОтправкеДанныхПодчиненному(ЭлементДанных, ОтправкаЭлемента)
Если Не ВыгружатьОбъект(ЭлементДанных) Тогда
ОтправкаЭлемента = ОтправкаЭлементаДанных.Игнорировать;
КонецЕсли;
КонецПроцедуры
Рассмотрим важный нюанс: установка значения ОтправкаЭлементаДанных.Игнорировать отменяет только сериализацию объекта в XML-сообщение. При этом платформа не сбрасывает номер сообщения в таблице регистрации изменений. Если ограничиться только этой строкой, данные будут безвозвратно удалены из очереди регистрации при подтверждении пакета. Следовательно, применение этого метода требует обязательного сброса номера сообщения.
Вызов метода ПланыОбмена.ЗарегистрироватьИзменения(Узел, Объект) перезаписывает строку в таблице регистрации и сбрасывает поле НомерСообщения обратно в NULL. Благодаря этому объект не попадет под удаление при подтверждении пакета и будет выбран при следующей выгрузке.
Однако вызывать повторную регистрацию построчно непосредственно внутри цикла обхода Выборка.Следующий() крайне не рекомендуется. Изменение таблицы регистрации параллельно с ее чтением выборкой провоцирует избыточные блокировки и снижает скорость выгрузки.
Разберем по шагам наиболее эффективный алгоритм:
ОтправкаЭлемента = ОтправкаЭлементаДанных.Игнорировать).Посмотрим на пример кода:
МассивПропущенных = Новый Массив;
Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, ЗаписьСообщения.НомерСообщения);
Пока Выборка.Следующий() Цикл
ОбъектДанных = Выборка.Получить();
Если НеобходимоПропустить(ОбъектДанных) Тогда
МассивПропущенных.Добавить(ОбъектДанных.Ссылка);
Продолжить;
КонецЕсли;
// Сериализация объекта в XML
ЗаписатьXML(ЗаписьXML, ОбъектДанных);
КонецЦикла;
// Пакетный сброс номера сообщения в таблице регистрации
Если МассивПропущенных.Количество() > 0 Тогда
ПланыОбмена.ЗарегистрироватьИзменения(УзелПланаОбмена, МассивПропущенных);
КонецЕсли;
Метод платформы ПланыОбмена.ВыбратьИзменения принимает третий необязательный параметр: ФильтрВыборки. В него можно передавать ссылку, массив ссылок, набор записей или объект метаданных.
В этом случае номер сообщения присваивается исключительно тем объектам, которые попали в фильтр. Все остальные изменения остаются со значением номера NULL, не блокируются и не удаляются при подтверждении.
Разберем шаги реализации:
Документ.РеализацияТоваровУслуг.Изменения) отбираем только те объекты, у которых НомерСообщения ЕСТЬ NULL и которые удовлетворяют всем прикладным условиям выгрузки.ВыбратьИзменения.Посмотрим на пример выборки с фильтрацией:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаИзменений.Ссылка КАК Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг.Изменения КАК ТаблицаИзменений
|ГДЕ
| ТаблицаИзменений.Узел = &Узел
| И ТаблицаИзменений.НомерСообщения ЕСТЬ NULL
| И ТаблицаИзменений.Ссылка.Проведен = ИСТИНА";
Запрос.УстановитьПараметр("Узел", УзелПланаОбмена);
МассивДляВыгрузки = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
Если МассивДляВыгрузки.Количество() > 0 Тогда
Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, ЗаписьСообщения.НомерСообщения, МассивДляВыгрузки);
Пока Выборка.Следующий() Цикл
ОбъектДанных = Выборка.Получить();
ЗаписатьXML(ЗаписьXML, ОбъектДанных);
КонецЦикла;
КонецЕсли;
Наиболее архитектурно верным решением в среде 1С является исключение попадания «лишних» объектов в очередь изменений еще на этапе их записи, а не во время формирования пакета.
Постоянная регистрация с последующим отсечением перегружает таблицы регистрации и увеличивает нагрузку на диск. Чтобы этого избежать, отключим свойство Авторегистрация для нужных объектов в составе плана обмена и настроим точечную регистрацию через подписку на событие ПередЗаписью или ПриЗаписи, проверяя все необходимые условия до вызова ПланыОбмена.ЗарегистрироватьИзменения.