Почему не работает УстановитьСсылкуНового() в 1С и как это исправить

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе интеграции 1С с внешними информационными системами часто возникает задача создания элементов справочников или документов с заранее определенными уникальными идентификаторами (GUID). Платформа 1С:Предприятие предоставляет для этого штатный метод УстановитьСсылкуНового(). Однако разработчики нередко сталкиваются с ситуацией, когда этот метод отрабатывает без ошибок, но после вызова метода Записать() система игнорирует переданный идентификатор и записывает объект с совершенно другим, автоматически сгенерированным GUID.

В этой статье мы подробно разберем, почему платформа может заменять установленную ссылку, как подписки на события влияют на этот процесс, как правильно оптимизировать код проверки существования объектов и как гарантировать сохранение нужного GUID при записи.

В чем суть проблемы: почему сбрасывается GUID?

Давайте проанализируем стандартную ситуацию. Разработчик получает GUID из внешнего источника, формирует по нему ссылку и пытается назначить её новому объекту:


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

Казалось бы, код написан абсолютно верно. Но при записи в базу данных создается элемент с новым случайным GUID. Выясним, почему так происходит.

Главная причина кроется в прикладном коде конфигурации (особенно это актуально для современных тяжелых решений на базе БСП, таких как "ERP Управление предприятием", "Комплексная автоматизация" или "Управление торговлей"). В этих конфигурациях активно используются подписки на события (например, ПередЗаписью или ПриЗаписи), а также логика в модулях самих объектов справочников.

Если в процессе записи объекта срабатывает сторонний алгоритм, который повторно инициализирует создание связанных сущностей, перезаписывает свойства или выполняет повторный вызов методов генерации кодов/ссылок, платформа 1С считает первоначальную ссылку недействительной. Происходит тихое переопределение: система генерирует новую ссылку "взамен" предложенной вами, не сообщая об ошибке.

Вторая распространенная причина — изменение версии данных (Data Version) внутри транзакции записи, из-за чего зарезервированная ссылка "слетает", уступая место новой системной генерации.

Решение проблемы: использование режима загрузки данных

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

Когда свойство ОбменДанными.Загрузка установлено в значение Истина, большинство подписок на события и проверок в модуле объекта игнорируют выполнение тяжелой бизнес-логики. Это позволяет записать объект "как есть", гарантируя сохранение переданной через УстановитьСсылкуНового() ссылки.

Рассмотрим правильную реализацию этого подхода:


// Получаем нужную нам ссылку по GUID
ТекущаяСсылка = Справочники.Контрагенты.ПолучитьСсылку(СтрДанные.КонтрагентGUID);

// Пробуем получить объект из базы данных по этой ссылке
ТекущийОбъект = ТекущаяСсылка.ПолучитьОбъект();

Если ТекущийОбъект = Неопределено Тогда
    // Если объекта нет в базе, создаем новый элемент
    ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
    // Принудительно устанавливаем для него нашу ссылку
    ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
КонецЕсли;

// Заполняем реквизиты объекта данными
ЗаполнитьЗначенияСвойств(ТекущийОбъект, СтрДанные, , "Ссылка");

// Включаем режим загрузки данных, чтобы обойти подписки на события
ТекущийОбъект.ОбменДанными.Загрузка = Истина;

// Записываем объект в базу данных
ТекущийОбъект.Записать();

Важное предупреждение: использование свойства ОбменДанными.Загрузка = Истина полностью отключает прикладные проверки (например, контроль уникальности кодов, заполнение обязательных реквизитов и триггеры создания подчиненных объектов). Если вы используете этот метод для разового первоначального наполнения нормативно-справочной информации (НСИ), этот подход идеален. Если же запись происходит в рамках регулярного интерактивного обмена, убедитесь, что все необходимые данные подготовлены корректно и отсутствие бизнес-логики не нарушит целостность базы.

Оптимизация проверки существования объекта

В коде, который часто встречается на форумах, можно увидеть неэффективную конструкцию:


// НЕПРАВИЛЬНО и неэффективно:
Если ТекущаяСсылка.ПолучитьОбъект() = Неопределено Тогда
    ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
    ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
Иначе
    ТекущийОбъект = ТекущаяСсылка.ПолучитьОбъект();
КонецЕсли;

Проанализируем этот код. Если объект существует в базе данных, метод ПолучитьОбъект() вызывается дважды: первый раз для проверки в условии Если, второй раз — внутри ветки Иначе для присвоения переменной. Метод ПолучитьОбъект() выполняет полное чтение записи из базы со всеми табличными частями и реквизитами, что создает избыточную нагрузку на СУБД.

Правильный шаблон проектирования подразумевает однократное получение объекта в переменную с последующей проверкой на Неопределено:


// ПРАВИЛЬНО:
ТекущийОбъект = ТекущаяСсылка.ПолучитьОбъект();
Если ТекущийОбъект = Неопределено Тогда
    ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
    ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
КонецЕсли;

Если объект существует, мы сразу начинаем работать с уже прочитанным объектом без повторных обращений к СУБД. Если объекта нет, метод возвращает Неопределено, и мы переходим к инициализации нового элемента.

Массовая проверка существования объектов (для оптимизаторов)

Если перед вами стоит задача загрузить тысячи элементов, проверка существования через ПолучитьОбъект() в цикле приведет к критическому падению производительности. Для ускорения процесса рекомендуется использовать пакетные запросы или функции проверки существования ссылок.

Если в вашей конфигурации установлена Библиотека Стандартных Подсистем (БСП), для одиночной быстрой проверки используйте функцию ОбщегоНазначения.СсылкаСуществует(). Она не считывает сам объект, а выполняет легкий запрос к базе данных.

Для массовой же загрузки лучше всего использовать следующий алгоритм:

  1. Выгрузить все идентификаторы (GUID) из источника данных в таблицу значений.
  2. В цикле сформировать ссылки для каждой строки таблицы с помощью метода ПолучитьСсылку() и записать их в отдельную колонку.
  3. Одним запросом передать массив полученных ссылок в базу данных, чтобы выявить, какие из них уже физически существуют, а какие — нет.
  4. Для отсутствующих ссылок применить метод УстановитьСсылкуНового(), а существующие обновить.

Посмотрим на пример реализации массовой проверки:


// Предположим, у нас есть ТаблицаДанных с колонкой "КонтрагентGUID"
ТаблицаДанных.Колонки.Добавить("СсылкаНаКонтрагента", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));

// Шаг 1: Генерируем ссылки "на лету" без обращения к БД
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
    СтрокаДанных.СсылкаНаКонтрагента = Справочники.Contragents.ПолучитьСсылку(СтрокаДанных.КонтрагентGUID);
КонецЦикла;

// Шаг 2: Выгружаем массив сгенерированных ссылок
МассивСсылок = ТаблицаДанных.ВыгрузитьКолонку("СсылкаНаКонтрагента");

// Шаг 3: Проверяем существование ссылок в БД одним запросом
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Контрагенты.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |	Контрагенты.Ссылка В(&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);

ТаблицаСуществующих = Запрос.Выполнить().Выгрузить();
ТаблицаСуществующих.Индексировать("Ссылка");

// Шаг 4: Обходим исходные данные и создаем/обновляем объекты
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
    
    // Ищем ссылку в результатах запроса существующих элементов
    НайденнаяСтрока = ТаблицаСуществующих.Найти(СтрокаДанных.СсылкаНаКонтрагента, "Ссылка");
    
    Если НайденнаяСтрока = Неопределено Тогда
        // Элемента нет в базе — создаем и жестко задаем GUID
        НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
        НовыйОбъект.УстановитьСсылкуНового(СтрокаДанных.СсылкаНаКонтрагента);
    Иначе
        // Элемент найден — считываем его для изменения
        НовыйОбъект = СтрокаДанных.СсылкаНаКонтрагента.ПолучитьОбъект();
    КонецЕсли;
    
    ЗаполнитьЗначенияСвойств(НовыйОбъект, СтрокаДанных, , "Ссылка");
    
    НовыйОбъект.ОбменДанными.Загрузка = Истина; // Защита от сброса GUID подписками
    НовыйОбъект.Записать();
    
КонецЦикла;

Этот способ минимизирует количество запросов к СУБД, сводя их к одному пакетному запросу, что в десятки раз ускоряет выполнение импорта данных внешней системы и исключает любые проблемы с потерей уникальных идентификаторов.

← На главную