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