Как создать элемент справочника с заданным GUID и почему он меняется при записи

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

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

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

Анализ исходной проблемы: почему GUID меняется после записи

Рассмотрим классическую ситуацию, с которой столкнулся автор темы. Исходный код выглядит следующим образом:


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

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

Если в конфигурации (особенно в типовых решениях вроде ЗУП, УПП, ERP или КА) существуют подписки на события ПередЗаписью или ПриУстановкеНовогоКода, в них может содержаться логика, которая непреднамеренно сбрасывает или перезаписывает ссылку нового. Выясним причину этого поведения.

Очень часто в подписках на события (например, при настройке прав доступа или автоматической регистрации объекта в различных регистрах) разработчики пытаются получить ссылку нового элемента. Если код написан некорректно, система генерирует новую ссылку «на лету» и принудительно устанавливает ее объекту, затирая ранее заданную программистом ссылку нового. Типичный пример некорректного кода в подписке:


// Некорректный код в типовом механизме
СсылкаНового = Справочники[ЭтотОбъект.Метаданные().Имя].ПолучитьСсылку();
ЭтотОбъект.УстановитьСсылкуНового(СсылкаНового);

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


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

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

Решение 1: Использование механизма ОбменДанными.Загрузка

Самым простым, надежным и системным способом обойти любые проверки, подписки на события и алгоритмы автозаполнения реквизитов при программном создании объектов является использование свойства ОбменДанными.Загрузка. Проанализируем, как изменится наш код:


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

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

НовыйОбъект.Записать();

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

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

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

Решение 2: Исправление логики в подписках на события

Если по какой-то причине использование режима ОбменДанными.Загрузка невозможно (например, вам критически важно, чтобы при создании объекта отработали все бизнес-процедуры, заполнились связанные регистры или проверились права доступа), придется скорректировать код подписок на события.

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

Критическая ошибка: Как НЕ надо проверять существование объекта по GUID

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


НайденныйОбъект = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
Если ЗначениеЗаполнено(НайденныйОбъект) Тогда
    // Эта ветка будет выполняться ВСЕГДА!
    ОткрытьЗначение(НайденныйОбъект);
Иначе
    Предупреждение("Объект не найден");
КонецЕсли;

В чем заключается ошибка? Метод ПолучитьСсылку() является чисто математической операцией конструирования ссылки в оперативной памяти компьютера на основе переданного GUID. Этот метод не обращается к базе данных и не проверяет физическое наличие записи в таблице СУБД. Он возвращает ссылку, которая содержит переданный GUID. Соответственно, функция ЗначениеЗаполнено() для такой ссылки всегда вернет значение Истина, так как ссылка не является пустой (ее GUID заполнен конкретным значением, отличным от "00000000-0000-0000-0000-000000000000"). Если объекта в базе нет, мы получим так называемую «битую ссылку» — при попытке отобразить ее на форме или прочитать реквизиты система выдаст текст "Объект не найден".

Правильные способы проверки существования объекта по GUID

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

Способ А. Быстрая проверка через легкий запрос (рекомендуется)

Этот способ является наиболее производительным, так как СУБД возвращает минимальный объем данных и не кэширует весь объект в памяти 1С. Мы передаем сконструированную ссылку в параметры запроса и проверяем результат:


ИсходнаяСсылка = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВнешниеОбработки.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ВнешниеОбработки КАК ВнешниеОбработки
    |ГДЕ
    |	ВнешниеОбработки.Ссылка = &Ссылка";

Запрос.УстановитьПараметр("Ссылка", ИсходнаяСсылка);
РезультатЗапроса = Запрос.Выполнить();

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

Способ Б. Проверка через метод ПолучитьОбъект()

Данный способ подходит, если вам в любом случае потребуется изменять реквизиты объекта, если он будет найден. Метод ПолучитьОбъект() возвращает ссылку на реальный объект базы данных либо значение Неопределено, если записи с таким GUID не существует:


СсылкаНаОбъект = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
ОбъектБД = СсылкаНаОбъект.ПолучитьОбъект();

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

Особенности работы с УстановитьСсылкуНового() на управляемых формах

Если вы разрабатываете функционал под управляемое приложение 1С, вам необходимо учитывать архитектуру работы тонкого клиента и сервера. Метод УстановитьСсылкуНового() доступен только на сервере при работе с типом данных СправочникОбъект (или любым другим прикладным объектом).

Частой ошибкой является попытка установить ссылку нового внутри объектных процедур формы с последующей конвертацией данных:


// Ошибка работы на управляемой форме!
ДокОбъект = РеквизитФормыВЗначение("Объект");
ДокОбъект.УстановитьСсылкуНового(НужнаяСсылка);
ЗначениеВРеквизитФормы(ДокОбъект, "Объект"); // Ссылка нового будет потеряна!

При вызове метода ЗначениеВРеквизитФормы() объект прикладного типа конвертируется в структуру данных формы (ДанныеФормыСтруктура). Данный тип данных принципиально не имеет механизмов хранения зарезервированной ссылки нового. В результате, при последующей стандартной записи формы, платформа сгенерирует абсолютно новый случайный GUID.

Как правильно поступать в управляемых формах:

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

Соблюдение этих простых правил и понимание механизмов работы платформы 1С:Предприятие позволит вам легко и безошибочно синхронизировать объекты по уникальным идентификаторам с любыми внешними базами данных и сервисами.

← На главную