Как зарегистрировать изменения для периодического регистра сведений в РИБ без ошибок?

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

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

В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен механизм основного отбора в периодических регистрах, нужно ли считывать данные перед регистрацией изменений и как правильно написать оптимальный код для решения этой задачи.

Выясняем причину: почему возникает ошибка?

Для начала проанализируем, как платформа «1С:Предприятие» регистрирует изменения для независимых регистров сведений. Для этого используется служебная таблица изменений конкретного регистра (в базе данных она имеет имя вида <ИмяРегистраСведений>.Изменения). Запись в этой таблице идентифицируется с помощью так называемого Ключа основного отбора.

Состав этого ключа зависит от настроек метаданных регистра в конфигураторе:

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

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

Пошаговое решение проблемы

Рассмотрим ситуацию на практическом примере. Нам необходимо зарегистрировать изменения для периодического регистра сведений ФИОФизЛиц по определенному списку физических лиц. Нам достаточно передать только самые актуальные данные (срез последних) на текущую дату.

Разберем по шагам правильный алгоритм реализации этой задачи.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ФИОФизЛицСрезПоследних.Период КАК Период,
    |    ФИОФизЛицСрезПоследних.ФизЛицо КАК ФизЛицо
    |ИЗ
    |    РегистрСведений.ФИОФизЛиц.СрезПоследних(&Период, ФизЛицо В (&ФизическиеЛица)) КАК ФИОФизЛицСрезПоследних";

Запрос.УстановитьПараметр("ФизическиеЛица", СписокФизическихЛиц);
Запрос.УстановитьПараметр("Период", ТекущаяДата());

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Шаг 2. Программная регистрация изменений в цикле.
Теперь мы создаем набор записей регистра, обязательно заполняем отбор как по измерению ФизЛицо, так и по полученному значению Период, после чего регистрируем изменения для конкретного узла обмена.


УзелОбмена = НайтиНужныйУзелОбмена(); // Функция получения узла РИБ

Набор = РегистрыСведений.ФИОФизЛиц.СоздатьНаборЗаписей();

Пока Выборка.Следующий() Цикл
    // Сбрасываем предыдущие отборы
    Набор.Отбор.Сбросить();
    
    // Устанавливаем отбор по Периоду (ОБЯЗАТЕЛЬНО для периодического регистра с основным отбором по периоду)
    Набор.Отбор.Период.Значение = Выборка.Период;
    Набор.Отбор.Period.Использование = Истина;
    
    // Устанавливаем отбор по ключевому измерению
    Набор.Отбор.ФизЛицо.Значение = Выборка.ФизЛицо;
    Набор.Отбор.ФизЛицо.Использование = Истина;
    
    Попытка
        ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, Набор);
    Исключение
        ЗаписьЖурналаРегистрации("РегистрацияИзменений", УровеньЖурналаРегистрации.Ошибка,,, 
            "Ошибка регистрации для физлица: " + Выборка.ФизЛицо + ". Описание: " + ОписаниеОшибки());
    КонецПопытки;
КонецЦикла;

Нужно ли вызывать метод Набор.Прочитать()?

Среди разработчиков часто возникает дискуссия: нужно ли перед вызовом метода ПланыОбмена.ЗарегистрироватьИзменения() выполнять чтение набора записей с помощью метода Набор.Прочитать()?

Ответ однозначный: нет, вызывать этот метод не нужно.

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

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

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

Нюансы работы со «Срезом последних» в распределенных базах (РИБ)

Рассмотрим важную архитектурную проблему, которая может возникнуть, если вы решите обмениваться только актуальными записями ("срезом последних") в периодических регистрах.

Предположим, в вашей базе-источнике по физическому лицу Иванову есть целая история изменений фамилии:

  1. 01.01.2020 — Иванов
  2. 01.01.2022 — Сидоров (актуальное состояние)

Если вы примените код, описанный выше (с использованием СрезПоследних), вы зарегистрируете изменение только на дату 01.01.2022. При отправке этого изменения в подчиненную базу улетит только одна эта запись.

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

Альтернативный подход: регистрация всей истории изменений

Если специфика вашего бизнес-процесса требует полной идентичности истории изменений периодического регистра во всех узлах РИБ, вам необходимо пойти другим путем:

Вариант А. Изменение настроек метаданных (если конфигурация не на замке).
Если снять флаг «Основной отбор по периоду» в свойствах регистра сведений в конфигураторе, то поле Период исключается из Ключа основного отбора. В этом случае при регистрации изменений вам вообще не нужно указывать период в отборе набора записей. Достаточно установить отбор по измерению (например, ФизЛицо). Платформа автоматически зарегистрирует к отправке всю историю записей по этому физическому лицу за все периоды.

Вариант Б. Программная выгрузка всей истории через запрос.
Если конфигурацию менять нельзя, а передать нужно всю историю, то вместо виртуальной таблицы СрезПоследних в запросе следует использовать физическую таблицу регистра сведений, выбрав из нее все исторические записи по конкретным объектам:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |    ФИОФизЛиц.Период КАК Период,
    |    ФИОФизЛиц.ФизЛицо КАК ФизЛицо
    |ИЗ
    |    РегистрСведений.ФИОФизЛиц КАК ФИОФизЛиц
    |ГДЕ
    |    ФИОФизЛиц.ФизЛицо В (&ФизическиеЛица)";

При обходе такой выборки вы зарегистрируете изменения для каждой исторической точки во времени, гарантируя полную идентичность данных на стороне получателя.

Регистрация удаления записей

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

← На главную