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