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