В процессе интеграции 1С с внешними информационными системами часто возникает задача создания элементов справочников или документов с заранее определенными уникальными идентификаторами (GUID). Платформа 1С:Предприятие предоставляет для этого штатный метод УстановитьСсылкуНового(). Однако разработчики нередко сталкиваются с ситуацией, когда этот метод отрабатывает без ошибок, но после вызова метода Записать() система игнорирует переданный идентификатор и записывает объект с совершенно другим, автоматически сгенерированным GUID.
В этой статье мы подробно разберем, почему платформа может заменять установленную ссылку, как подписки на события влияют на этот процесс, как правильно оптимизировать код проверки существования объектов и как гарантировать сохранение нужного GUID при записи.
Давайте проанализируем стандартную ситуацию. Разработчик получает GUID из внешнего источника, формирует по нему ссылку и пытается назначить её новому объекту:
ТекущаяСсылка = Справочники.Контрагенты.ПолучитьСсылку(Новый УникальныйИдентификатор(ИдентификаторИзВнешнейСистемы));
ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
// Заполнение реквизитов...
ТекущийОбъект.Записать();
Казалось бы, код написан абсолютно верно. Но при записи в базу данных создается элемент с новым случайным GUID. Выясним, почему так происходит.
Главная причина кроется в прикладном коде конфигурации (особенно это актуально для современных тяжелых решений на базе БСП, таких как "ERP Управление предприятием", "Комплексная автоматизация" или "Управление торговлей"). В этих конфигурациях активно используются подписки на события (например, ПередЗаписью или ПриЗаписи), а также логика в модулях самих объектов справочников.
Если в процессе записи объекта срабатывает сторонний алгоритм, который повторно инициализирует создание связанных сущностей, перезаписывает свойства или выполняет повторный вызов методов генерации кодов/ссылок, платформа 1С считает первоначальную ссылку недействительной. Происходит тихое переопределение: система генерирует новую ссылку "взамен" предложенной вами, не сообщая об ошибке.
Вторая распространенная причина — изменение версии данных (Data Version) внутри транзакции записи, из-за чего зарезервированная ссылка "слетает", уступая место новой системной генерации.
Чтобы защитить установленную ссылку от вмешательства подписок на события и встроенных механизмов платформы, необходимо явно указать системе, что объект записывается в режиме обмена (загрузки) данными. Для этого используется свойство ОбменДанными.Загрузка.
Когда свойство ОбменДанными.Загрузка установлено в значение Истина, большинство подписок на события и проверок в модуле объекта игнорируют выполнение тяжелой бизнес-логики. Это позволяет записать объект "как есть", гарантируя сохранение переданной через УстановитьСсылкуНового() ссылки.
Рассмотрим правильную реализацию этого подхода:
// Получаем нужную нам ссылку по GUID
ТекущаяСсылка = Справочники.Контрагенты.ПолучитьСсылку(СтрДанные.КонтрагентGUID);
// Пробуем получить объект из базы данных по этой ссылке
ТекущийОбъект = ТекущаяСсылка.ПолучитьОбъект();
Если ТекущийОбъект = Неопределено Тогда
// Если объекта нет в базе, создаем новый элемент
ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
// Принудительно устанавливаем для него нашу ссылку
ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
КонецЕсли;
// Заполняем реквизиты объекта данными
ЗаполнитьЗначенияСвойств(ТекущийОбъект, СтрДанные, , "Ссылка");
// Включаем режим загрузки данных, чтобы обойти подписки на события
ТекущийОбъект.ОбменДанными.Загрузка = Истина;
// Записываем объект в базу данных
ТекущийОбъект.Записать();
Важное предупреждение: использование свойства ОбменДанными.Загрузка = Истина полностью отключает прикладные проверки (например, контроль уникальности кодов, заполнение обязательных реквизитов и триггеры создания подчиненных объектов). Если вы используете этот метод для разового первоначального наполнения нормативно-справочной информации (НСИ), этот подход идеален. Если же запись происходит в рамках регулярного интерактивного обмена, убедитесь, что все необходимые данные подготовлены корректно и отсутствие бизнес-логики не нарушит целостность базы.
В коде, который часто встречается на форумах, можно увидеть неэффективную конструкцию:
// НЕПРАВИЛЬНО и неэффективно:
Если ТекущаяСсылка.ПолучитьОбъект() = Неопределено Тогда
ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
Иначе
ТекущийОбъект = ТекущаяСсылка.ПолучитьОбъект();
КонецЕсли;
Проанализируем этот код. Если объект существует в базе данных, метод ПолучитьОбъект() вызывается дважды: первый раз для проверки в условии Если, второй раз — внутри ветки Иначе для присвоения переменной. Метод ПолучитьОбъект() выполняет полное чтение записи из базы со всеми табличными частями и реквизитами, что создает избыточную нагрузку на СУБД.
Правильный шаблон проектирования подразумевает однократное получение объекта в переменную с последующей проверкой на Неопределено:
// ПРАВИЛЬНО:
ТекущийОбъект = ТекущаяСсылка.ПолучитьОбъект();
Если ТекущийОбъект = Неопределено Тогда
ТекущийОбъект = Справочники.Контрагенты.СоздатьЭлемент();
ТекущийОбъект.УстановитьСсылкуНового(ТекущаяСсылка);
КонецЕсли;
Если объект существует, мы сразу начинаем работать с уже прочитанным объектом без повторных обращений к СУБД. Если объекта нет, метод возвращает Неопределено, и мы переходим к инициализации нового элемента.
Если перед вами стоит задача загрузить тысячи элементов, проверка существования через ПолучитьОбъект() в цикле приведет к критическому падению производительности. Для ускорения процесса рекомендуется использовать пакетные запросы или функции проверки существования ссылок.
Если в вашей конфигурации установлена Библиотека Стандартных Подсистем (БСП), для одиночной быстрой проверки используйте функцию ОбщегоНазначения.СсылкаСуществует(). Она не считывает сам объект, а выполняет легкий запрос к базе данных.
Для массовой же загрузки лучше всего использовать следующий алгоритм:
ПолучитьСсылку() и записать их в отдельную колонку.УстановитьСсылкуНового(), а существующие обновить.Посмотрим на пример реализации массовой проверки:
// Предположим, у нас есть ТаблицаДанных с колонкой "КонтрагентGUID"
ТаблицаДанных.Колонки.Добавить("СсылкаНаКонтрагента", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
// Шаг 1: Генерируем ссылки "на лету" без обращения к БД
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
СтрокаДанных.СсылкаНаКонтрагента = Справочники.Contragents.ПолучитьСсылку(СтрокаДанных.КонтрагентGUID);
КонецЦикла;
// Шаг 2: Выгружаем массив сгенерированных ссылок
МассивСсылок = ТаблицаДанных.ВыгрузитьКолонку("СсылкаНаКонтрагента");
// Шаг 3: Проверяем существование ссылок в БД одним запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка В(&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
ТаблицаСуществующих = Запрос.Выполнить().Выгрузить();
ТаблицаСуществующих.Индексировать("Ссылка");
// Шаг 4: Обходим исходные данные и создаем/обновляем объекты
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
// Ищем ссылку в результатах запроса существующих элементов
НайденнаяСтрока = ТаблицаСуществующих.Найти(СтрокаДанных.СсылкаНаКонтрагента, "Ссылка");
Если НайденнаяСтрока = Неопределено Тогда
// Элемента нет в базе — создаем и жестко задаем GUID
НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
НовыйОбъект.УстановитьСсылкуНового(СтрокаДанных.СсылкаНаКонтрагента);
Иначе
// Элемент найден — считываем его для изменения
НовыйОбъект = СтрокаДанных.СсылкаНаКонтрагента.ПолучитьОбъект();
КонецЕсли;
ЗаполнитьЗначенияСвойств(НовыйОбъект, СтрокаДанных, , "Ссылка");
НовыйОбъект.ОбменДанными.Загрузка = Истина; // Защита от сброса GUID подписками
НовыйОбъект.Записать();
КонецЦикла;
Этот способ минимизирует количество запросов к СУБД, сводя их к одному пакетному запросу, что в десятки раз ускоряет выполнение импорта данных внешней системы и исключает любые проблемы с потерей уникальных идентификаторов.