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