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