В процессе интеграции 1С с внешними информационными системами разработчики часто сталкиваются с необходимостью создания элементов справочников или документов с заранее определенным уникальным идентификатором (GUID). Для этого в платформе 1С:Предприятие предназначен метод УстановитьСсылкуНового(). Однако на практике нередко возникает ситуация, когда после вызова метода Записать() объект сохраняется в базе данных совершенно с другим GUID.
В этой статье мы подробно разберем, почему переданный идентификатор может игнорироваться или заменяться платформой, какие типичные ошибки совершают программисты при проверке существования таких объектов, и как гарантированно записать элемент с нужным GUID.
Рассмотрим классическую ситуацию, с которой столкнулся автор темы. Исходный код выглядит следующим образом:
НовыйОбъект = Справочники.ВнешниеОбработки.СоздатьЭлемент();
НоваяСсылка = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
НовыйОбъект.УстановитьСсылкуНового(НоваяСсылка);
гуид = XMLСтрока(НовыйОбъект.ПолучитьСсылкуНового()); // Здесь они равны
НовыйОбъект.Записать();
фактгуид = XMLСтрока(НовыйОбъект.Ссылка); // Ошибка: фактгуид не равен исходному гуиду!
Давайте разберемся, почему так происходит. При вызове метода УстановитьСсылкуНового() мы резервируем для незаписанного объекта конкретную ссылку. До момента записи метод ПолучитьСсылкуНового() возвращает именно эту зарезервированную ссылку. Однако при вызове метода Записать() стартует транзакция записи, в рамках которой выполняются обработчики событий самого объекта и подписки на события конфигурации.
Если в конфигурации (особенно в типовых решениях вроде ЗУП, УПП, ERP или КА) существуют подписки на события ПередЗаписью или ПриУстановкеНовогоКода, в них может содержаться логика, которая непреднамеренно сбрасывает или перезаписывает ссылку нового. Выясним причину этого поведения.
Очень часто в подписках на события (например, при настройке прав доступа или автоматической регистрации объекта в различных регистрах) разработчики пытаются получить ссылку нового элемента. Если код написан некорректно, система генерирует новую ссылку «на лету» и принудительно устанавливает ее объекту, затирая ранее заданную программистом ссылку нового. Типичный пример некорректного кода в подписке:
// Некорректный код в типовом механизме
СсылкаНового = Справочники[ЭтотОбъект.Метаданные().Имя].ПолучитьСсылку();
ЭтотОбъект.УстановитьСсылкуНового(СсылкаНового);
В данном примере система жестко заменяет любую существующую ссылку нового на свежесгенерированную, полностью игнорируя тот факт, что разработчик уже передал в объект свой GUID. Правильный и безопасный вариант этого кода должен обязательно проверять, заполнена ли ссылка нового в текущий момент:
// Корректный и безопасный код
СсылкаНового = ЭтотОбъект.ПолучитьСсылкуНового();
Если Не ЗначениеЗаполнено(СсылкаНового) Тогда
СсылкаНового = Справочники[ЭтотОбъект.Метаданные().Имя].ПолучитьСсылку();
ЭтотОбъект.УстановитьСсылкуНового(СсылкаНового);
КонецЕсли;
Если в вашей конфигурации присутствует устаревший или некорректно написанный код в подписках, при записи объекта ваш GUID всегда будет перезаписываться случайным образом.
Самым простым, надежным и системным способом обойти любые проверки, подписки на события и алгоритмы автозаполнения реквизитов при программном создании объектов является использование свойства ОбменДанными.Загрузка. Проанализируем, как изменится наш код:
НовыйОбъект = Справочники.ВнешниеОбработки.СоздатьЭлемент();
НоваяСсылка = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
НовыйОбъект.УстановитьСсылкуНового(НоваяСсылка);
// Включаем режим загрузки данных
НовыйОбъект.ОбменДанными.Загрузка = Истина;
НовыйОбъект.Записать();
Установка свойства ОбменДанными.Загрузка в значение Истина сообщает платформе и конфигурации, что данный объект переносится из внешней системы (например, в рамках обмена данными). В этом режиме:
УстановитьСсылкуНового().Именно этот подход рекомендуется использовать при написании обработок импорта данных, интеграций и синхронизаций между различными информационными базами.
Если по какой-то причине использование режима ОбменДанными.Загрузка невозможно (например, вам критически важно, чтобы при создании объекта отработали все бизнес-процедуры, заполнились связанные регистры или проверились права доступа), придется скорректировать код подписок на события.
Как показано в обсуждении на форуме, в некоторых конфигурациях (например, старых релизах ЗУП 2.5) подписка на событие ПриУстановкеНовогоКода или процедуры настройки прав доступа принудительно генерируют новую ссылку. Чтобы решить эту проблему без изменения типовой конфигурации, можно временно переопределить ссылку нового непосредственно в обработчике события, либо обновить код соответствующей подписки до актуального состояния, добавив проверку ЗначениеЗаполнено(ЭтотОбъект.ПолучитьСсылкуНового()).
Еще одна важная проблема, поднятая в обсуждении, связана с проверкой наличия элемента в базе данных по его уникальному идентификатору. Рассмотрим ошибочный код автора:
НайденныйОбъект = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
Если ЗначениеЗаполнено(НайденныйОбъект) Тогда
// Эта ветка будет выполняться ВСЕГДА!
ОткрытьЗначение(НайденныйОбъект);
Иначе
Предупреждение("Объект не найден");
КонецЕсли;
В чем заключается ошибка? Метод ПолучитьСсылку() является чисто математической операцией конструирования ссылки в оперативной памяти компьютера на основе переданного GUID. Этот метод не обращается к базе данных и не проверяет физическое наличие записи в таблице СУБД. Он возвращает ссылку, которая содержит переданный GUID. Соответственно, функция ЗначениеЗаполнено() для такой ссылки всегда вернет значение Истина, так как ссылка не является пустой (ее GUID заполнен конкретным значением, отличным от "00000000-0000-0000-0000-000000000000"). Если объекта в базе нет, мы получим так называемую «битую ссылку» — при попытке отобразить ее на форме или прочитать реквизиты система выдаст текст "Объект не найден".
Для того чтобы достоверно узнать, существует ли физически в базе данных элемент с таким уникальным идентификатором, мы можем воспользоваться одним из двух проверенных методов.
Этот способ является наиболее производительным, так как СУБД возвращает минимальный объем данных и не кэширует весь объект в памяти 1С. Мы передаем сконструированную ссылку в параметры запроса и проверяем результат:
ИсходнаяСсылка = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВнешниеОбработки.Ссылка КАК Ссылка
|ИЗ
| Справочник.ВнешниеОбработки КАК ВнешниеОбработки
|ГДЕ
| ВнешниеОбработки.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ИсходнаяСсылка);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
// Объекта с таким GUID в базе данных нет! Можно создавать новый.
НовыйОбъект = Справочники.ВнешниеОбработки.СоздатьЭлемент();
НовыйОбъект.УстановитьСсылкуНового(ИсходнаяСсылка);
НовыйОбъект.Записать();
Иначе
// Объект уже существует в базе данных
СуществующийОбъект = ИсходнаяСсылка.ПолучитьОбъект();
КонецЕсли;
Данный способ подходит, если вам в любом случае потребуется изменять реквизиты объекта, если он будет найден. Метод ПолучитьОбъект() возвращает ссылку на реальный объект базы данных либо значение Неопределено, если записи с таким GUID не существует:
СсылкаНаОбъект = Справочники.ВнешниеОбработки.ПолучитьСсылку(Новый УникальныйИдентификатор(НовыйGUIDОбъекта));
ОбъектБД = СсылкаНаОбъект.ПолучитьОбъект();
Если ОбъектБД = Неопределено Тогда
// Объект не существует
НовыйОбъект = Справочники.ВнешниеОбработки.СоздатьЭлемент();
НовыйОбъект.УстановитьСсылкуНового(СсылкаНаОбъект);
НовыйОбъект.Записать();
Иначе
// Объект найден, с ним можно работать
ОбъектБД.Наименование = "Обновленное наименование";
ОбъектБД.Записать();
КонецЕсли;
Если вы разрабатываете функционал под управляемое приложение 1С, вам необходимо учитывать архитектуру работы тонкого клиента и сервера. Метод УстановитьСсылкуНового() доступен только на сервере при работе с типом данных СправочникОбъект (или любым другим прикладным объектом).
Частой ошибкой является попытка установить ссылку нового внутри объектных процедур формы с последующей конвертацией данных:
// Ошибка работы на управляемой форме!
ДокОбъект = РеквизитФормыВЗначение("Объект");
ДокОбъект.УстановитьСсылкуНового(НужнаяСсылка);
ЗначениеВРеквизитФормы(ДокОбъект, "Объект"); // Ссылка нового будет потеряна!
При вызове метода ЗначениеВРеквизитФормы() объект прикладного типа конвертируется в структуру данных формы (ДанныеФормыСтруктура). Данный тип данных принципиально не имеет механизмов хранения зарезервированной ссылки нового. В результате, при последующей стандартной записи формы, платформа сгенерирует абсолютно новый случайный GUID.
Как правильно поступать в управляемых формах:
ПередЗаписью().Соблюдение этих простых правил и понимание механизмов работы платформы 1С:Предприятие позволит вам легко и безошибочно синхронизировать объекты по уникальным идентификаторам с любыми внешними базами данных и сервисами.