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