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