Как при выгрузке в периферийную базу РИБ загружать только новые объекты и не затирать существующие?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При настройке распределенных информационных баз (РИБ) разработчики часто сталкиваются с задачей разделения логики синхронизации. Типичный сценарий: в центральной базе создаются и редактируются документы или элементы справочников, которые должны уходить в периферийную базу. Однако, если в периферийной базе этот объект уже существует и, возможно, был изменен локально, мы не хотим затирать его центральными данными. Нам нужно только создавать новые объекты, игнорируя изменения по уже существующим.

В этой статье мы подробно разберем, почему стандартные подходы через правила регистрации объектов (ПРО) здесь не работают, какие архитектурные ограничения платформы 1С нужно учитывать и как правильно реализовать эту задачу несколькими надежными способами.

Разбор архитектурного заблуждения: почему ПРО и ПКО здесь не всегда помогут

Для начала проанализируем, почему стандартные инструменты КД (Конвертации данных) вызывают путаницу у начинающих разработчиков.

Во-первых, часто пытаются использовать Правила регистрации объектов (ПРО). Разработчики логично рассуждают: если объект не нужно изменять в периферийной базе, то давайте просто не будем его регистрировать к обмену в центральной базе. Но в этот момент они забывают главное правило распределенных систем:

Правила регистрации объектов (ПРО) работают исключительно на стороне источника (отправителя).

Центральная база в момент записи объекта и расчета правил регистрации физически ничего не знает о состоянии периферийной базы. Она не может "спросить" удаленный узел: «А есть ли у тебя уже этот договор?» или «Менял ли ты этот документ?». ПРО могут оперировать только локальными данными центральной базы. Поэтому отсечь перезапись существующих объектов на этапе регистрации невозможно.

Попытки написать в ПРО обработчик со следующим кодом:


Если Не Объект.ЭтоНовый() И ПланыОбмена.ГлавныйУзел() = Неопределено Тогда
    Отказ = Истина;
КонецЕсли;

приводят к полной неработоспособности обмена по двум причинам:

  1. Метод ЭтоНовый() возвращает Истина только для объектов, которые еще ни разу не были записаны в базу данных (у них нет ссылки). В обработчиках ПРО (которые чаще всего выполняются уже после записи объекта в БД) этот метод почти всегда возвращает Ложь, даже если пользователь только что создал элемент. В итоге новые объекты просто перестают регистрироваться.
  2. Если пользователь создал документ, записал его (он успешно зарегистрировался на обмен), а затем изменил реквизит ДО момента отправки почты — при повторной записи признак "нового" пропадет, регистрация отменится, и периферийная база вообще не получит этот объект.

Во-вторых, рассмотрим Правила конвертации объектов (ПКО). В классической «Конвертации данных 2.1» действительно есть флаг «Не замещать существующие объекты в приемнике при загрузке, а только создавать новые и заполнять их» (устанавливает свойство НеЗамещатьОбъект = Истина).

Однако в стандартном платформенном РИБ обмен по умолчанию идет через XML-сериализацию платформы. Платформа напрямую пишет данные в таблицы базы-приемника и не использует правила конвертации. Использовать ПКО в РИБ можно только в том случае, если ваш обмен перенастроен на работу через Библиотеку стандартных подсистем (БСП) с использованием внешних правил обмена, что существенно снижает скорость работы обмена и усложняет поддержку.

Давайте выясним, как решить эту задачу правильно и надежно с помощью стандартных механизмов платформы 1С.

Способ 1. Фильтрация на стороне приемника через модуль Плана обмена (Рекомендуемый)

Поскольку только база-получатель (в нашем случае — периферийная) достоверно знает, какие объекты в ней уже созданы, всю логику проверки мы должны перенести на ее сторону. Самое правильное место для этого — модуль плана обмена, по которому осуществляется синхронизация.

Рассмотрим по шагам, как это реализовать:

  1. Откроем конфигуратор периферийной базы (или конфигурацию-источник, если конфигурации идентичны).
  2. Найдем наш план обмена РИБ и откроем его модуль объекта.
  3. Добавим или найдем предопределенный обработчик события ПриПолученииДанныхОтГлавного (или воспользуемся общим механизмом БСП ПередЗаписьюПолученныхДанных).

Проанализируем пример кода для обработчика в модуле плана обмена:


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

КонецПроцедуры

Важное преимущество этого способа: присвоение переменной ЭлементДанных = Неопределено; сообщает платформе, что данный элемент нужно просто пропустить при чтении файла обмена. Это не вызывает критической ошибки транзакции (в отличие от директивы Отказ = Истина;), и весь остальной пакет данных загружается абсолютно корректно.

Способ 2. Использование подписки на событие ПередЗаписью

Если по каким-то причинам вы не хотите модифицировать модуль плана обмена (например, чтобы не снимать его с поддержки), можно решить задачу через создание общей подписки на события.

Рассмотрим этот алгоритм по шагам:

  1. В ветке конфигурации Общие -> Подписки на события создадим новую подписку.
  2. Назовем ее, например, ЗапретПерезаписиПриОбменеПередЗаписью.
  3. В качестве источника выберем типы документов или справочников, для которых нужно применить это правило (например, ДокументОбъект.ДоговорыКонтрагентов, СправочникОбъект.Номенклатура).
  4. Событие выберем как ПередЗаписью.
  5. В качестве обработчика укажем процедуру из общего модуля (модуль должен быть серверным и иметь флаг "Вызов сервера" или "Клиент (обычное приложение)").

Пропишем следующий код в созданной процедуре общего модуля:


Процедура ЗапретПерезаписиПриОбменеПередЗаписьюПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт

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

КонецПроцедуры

Этот способ гарантирует, что даже если платформа попытается записать пришедшие изменения, мы на лету заменим их старыми локальными значениями из базы-приемника, тем самым защитив наши локальные данные от затирания.

Способ 3. Если обмен идет по правилам обмена (КД 2.1)

В некоторых конфигурациях РИБ настроен не стандартными средствами платформы напрямую, а через подсистемы БСП, которые используют классические правила обмена (XML-файлы правил конвертации данных). В таком случае мы можем решить задачу непосредственно в правилах.

Как мы уже выяснили, стандартная галочка «Не замещать существующие объекты в приемнике...» в КД 2.1 из-за внутренних особенностей платформы (например, при совмещении поиска по УИД и полям поиска) иногда может отрабатывать некорректно. Чтобы обойти это ограничение, пропишем логику вручную в обработчиках событий ПКО нужного нам объекта.

Откроем правила в программе «Конвертация данных 2.1», найдем нужное ПКО и перейдем к обработчикам событий:

Использование встроенных обработчиков КД 2.1 позволяет гибко управлять логикой: например, вы можете не заменять только определенные реквизиты (например, цены или контрагентов), оставляя другие реквизиты доступными для обновления из центральной базы.

Резюме и рекомендации

При решении задач выборочной синхронизации в распределенных информационных базах 1С важно соблюдать правильное разделение ответственности между компонентами системы:

← На главную