Почему возникает ошибка «Не удалось записать» при изменении вида в регистре сведений «Контактная информация» и как ее исправить?

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия IT и автоматизация бизнеса
← На главную

При выполнении служебных обработок или переносе данных разработчики часто сталкиваются с необходимостью изменить измерение Вид у существующих записей в регистре сведений КонтактнаяИнформация. Однако попытка прочитать набор записей с простым отбором по типу, изменить вид в цикле и выполнить метод Записать() часто приводит к исключению: «Не удалось записать: "Контактная информация"!».

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

Анализ причин возникновения ошибки

Выясним причину сбоя при записи набора. Неинформативное сообщение об ошибке обычно маскирует одну из следующих критических ситуаций:

  1. Нарушение уникальности ключевых измерений. Регистр КонтактнаяИнформация в типовых конфигурациях (включая «Бухгалтерию предприятия 2.0») является непо периодическим и имеет три ключевых измерения: Объект, Тип и Вид. Если у одного физического лица или контрагента ранее было зарегистрировано два адреса электронной почты с разными видами (например, «Личный email» и «Рабочий email»), то при принудительной замене вида на одно и то же значение в рамках одного набора сформируются строки с абсолютно одинаковым набором измерений. Платформа 1С физически не может записать неуникальный ключ в базу данных.
  2. Опасность замещения данных при неполном отборе. Метод НаборЗаписей.Записать() по умолчанию вызывается с параметром Замещение = Истина. Если установить отбор только по одному измерению (Тип = Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты), платформа при попытке записи сначала попытается очистить в базе данных абсолютно все адреса электронной почты по всем объектам конфигурации, а затем вставить данные из набора. Это не только приводит к длительным блокировкам таблиц, но и грозит потерей данных, если в набор попали не все записи базы.
  3. Взведение флага Отказ в процедурах контроля. В модуле набора записей или в общих подписках на события конфигурации выполняются прикладные проверки. Если в обработчике ПередЗаписью обнаруживается логическое несоответствие (например, назначенный Вид предназначен для организаций, а объект является физическим лицом, либо реквизит Тип в справочнике ВидыКонтактнойИнформации не совпадает с измерением), программа устанавливает флаг Отказ = Истина, что приводит к системной ошибке записи.

Подводный камень при использовании Менеджера записи

Посмотрим на попытку обойти проблему через СоздатьМенеджерЗаписи, когда разработчики последовательно читают запись, меняют измерение Вид и вызывают запись менеджера:


// Ошибочный алгоритм:
Запись = РегистрыСведений.КонтактнаяИнформация.СоздатьМенеджерЗаписи();
Запись.Тип = Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты;
Запись.Объект = Выборка.Объект;
Запись.Вид = Выборка.Вид; // Старый вид
Запись.Прочитать();

Запись.Вид = НовыйВидКИ; // Изменение ключевого измерения
Запись.Записать();

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

Если для решения задачи выбирается МенеджерЗаписи, старую запись необходимо предварительно удалить:


// Сначала считываем и удаляем старую запись по ее исходному ключу
Менеджер = РегистрыСведений.КонтактнаяИнформация.СоздатьМенеджерЗаписи();
Менеджер.Объект = Выборка.Объект;
Менеджер.Тип = Выборка.Тип;
Менеджер.Вид = Выборка.СтарыйВид;
Менеджер.Прочитать();

Если Менеджер.Выбран() Тогда
    // Сохраняем значения ресурсов перед удалением
    ЗначениеПоля = Менеджер.Представление;
    Поле1 = Менеджер.Поле1;
    // Удаляем исходную запись из базы
    Менеджер.Удалить();
    
    // Создаем новую запись с обновленным измерением
    НоваяЗапись = РегистрыСведений.КонтактнаяИнформация.СоздатьМенеджерЗаписи();
    НоваяЗапись.Объект = Выборка.Объект;
    НоваяЗапись.Тип = Выборка.Тип;
    НоваяЗапись.Вид = НовыйВидКИ;
    НоваяЗапись.Представление = ЗначениеПоля;
    НоваяЗапись.Поле1 = Поле1;
    НоваяЗапись.Записать();
КонецЕсли;

Как получить точную причину ошибки

Когда при вызове метода Записать() платформа сообщает лишь общую формулировку, используем функцию ПодробноеПредставлениеОшибки() в блоке исключения. Это позволит увидеть точный стек вызовов и строку подсистемы, где произошел сбой:


Попытка
    Набор.Записать();
Исключение
    ЗаписьЖурналаРегистрации("ОбновлениеКИ", УровеньЖурналаРегистрации.Ошибка, , , 
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    Сообщить(ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Для быстрой проверки набора записей на предмет дубликатов измерений перед вызовом записи применим метод свертки таблицы значений:


ТаблицаКонтроль = Набор.Выгрузить();
КоличествоДо = ТаблицаКонтроль.Количество();
ТаблицаКонтроль.Свернуть("Объект, Тип, Вид");
Если ТаблицаКонтроль.Количество() < КоличествоДо Тогда
    Сообщить("Обнаружены дубли измерений! Набор не может быть записан.");
КонецЕсли;

Рекомендуемый способ: поэкземплярная обработка набором записей

Наиболее надежный и производительный вариант — выполнять выборку ссылок запросом и обновлять набор записей порционно для каждого объекта персонально. При этом обязательно накладывается отбор по двум измерениям: Объект и Тип.

Разберем по шагам реализацию безопасного алгоритма:

  1. Выбираем запросом только те объекты, у которых есть записи с требуемым типом контактной информации.
  2. В цикле для каждого объекта создаем отдельный набор записей с жестким фильтром по владельцу.
  3. Проверяем, нет ли у объекта записи с целевым видом, чтобы избежать коллизий.
  4. Заменяем измерение и записываем набор с включенным режимом загрузки данных, если требуется обойти платформенные подписки обмена.

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

Запрос.УстановитьПараметр("Тип", Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты);
Запрос.УстановитьПараметр("СтарыйВид", СтарыйВидКонтактнойИнформации);

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

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

Использование свойства ОбменДанными.Загрузка = Истина критически важно при пакетных обработках: оно предотвращает срабатывание событий ПередЗаписью и подписок на регистрацию изменений для планов обмена, что исключает блокировку записи внутренними механизмами конфигурации.

← На главную