При интеграции 1С с внешними информационными системами (например, интернет-магазинами, CRM или сторонними базами данных) часто возникает необходимость создавать новые элементы справочников с заранее определенным уникальным идентификатором (GUID/UUID). Это критически важно для обеспечения однозначного соответствия объектов в разных базах данных.
Однако на практике разработчики часто сталкиваются с ситуацией, когда код по созданию элемента с конкретным GUID отрабатывает без ошибок, но после сохранения объекта в базе данных его идентификатор непредсказуемо меняется на новый. Давайте подробно разберем, почему это происходит, как устроены механизмы платформы 1С:Предприятие 8 для работы с GUID и как решить эту проблему правильно.
Прежде всего, проанализируем, как выглядит штатный алгоритм установки GUID для нового элемента справочника. Для решения этой задачи платформа предоставляет связку из менеджера справочника и метода УстановитьСсылкуНового(). Разберем этот процесс по шагам на примере справочника Номенклатура.
Сначала нам нужно получить строковое представление GUID из внешней системы и преобразовать его в объект типа УникальныйИдентификатор. Затем мы получаем ссылку на будущий элемент и передаем её в создаваемый объект. Рассмотрим пример корректного исходного кода:
// Предположим, у нас есть строковое представление GUID из внешней базы
СтрокаGUID = "d48e89ad-9781-11ee-b9d1-00155d012402";
// Преобразуем строку во внутренний объект платформы
НовыйGUID = Новый УникальныйИдентификатор(СтрокаGUID);
// Получаем "пустую" ссылку с нужным нам GUID через менеджер справочника
СсылкаНаНоменклатуру = Справочники.Номенклатура.ПолучитьСсылку(НовыйGUID);
// Создаем новый объект номенклатуры
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
// Явно указываем платформе, какую ссылку должен получить этот объект при записи
НовыйЭлемент.УстановитьСсылкуНового(СсылкаНаНоменклатуру);
// Заполняем обязательные реквизиты
НовыйЭлемент.Наименование = "Новый тестовый товар";
// Записываем объект в базу данных
НовыйЭлемент.Записать();
В обычных условиях этот код отрабатывает идеально: в базе данных появляется элемент с ровно тем идентификатором, который мы передали в переменной СтрокаGUID.
Если приведенный выше код написан абсолютно верно, почему у некоторых разработчиков (как у автора темы на форуме) GUID все равно заменяется новым при вызове метода Записать()?
Причина кроется в механизмах бизнес-логики самой конфигурации. В 1С:Предприятии процесс записи объекта — это не просто сохранение строки в таблицу СУБД. Это сложный процесс, сопровождающийся выполнением различных обработчиков событий и подписок на события. На этапе выполнения метода Записать() платформа последовательно вызывает:
ПередЗаписью в модуле объекта.ПередЗаписью.ПриЗаписи в модуле объекта.ПриЗаписи.Очень часто в типовых конфигурациях (особенно в некачественно доработанных базах) внутри этих обработчиков содержится логика, которая принудительно переопределяет или очищает ссылку нового объекта. Например, разработчик мог написать проверку: «Если ссылка объекта пустая или не заполнена по какому-то алгоритму, сгенерировать ее заново». В результате ваш предопределенный GUID заменяется новым системным идентификатором непосредственно в момент вызова метода Записать().
Самый простой и быстрый способ обойти выполнение лишнего прикладного кода, который затирает наш GUID — перевести объект в режим загрузки данных. Для этого у каждого регистрируемого объекта существует свойство ОбменДанными.
Установка свойства ОбменДанными.Загрузка = Истина сообщает платформе и всем обработчикам событий, что объект переносится из другой системы в рамках обмена. Прикладной код в модулях объекта и подписках в 99% случаев игнорирует такие объекты, чтобы не мешать миграции данных.
Модифицируем наш исходный код:
НовыйGUID = Новый УникальныйИдентификатор(СтрокаGUID);
СсылкаНаНоменклатуру = Справочники.Номенклатура.ПолучитьСсылку(НовыйGUID);
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.УстановитьСсылкуНового(СсылкаНаНоменклатуру);
НовыйЭлемент.Наименование = "Номенклатура с гарантированным GUID";
// Включаем режим загрузки данных, чтобы обойти обработчики «ПередЗаписью» и «ПриЗаписи»
НовыйЭлемент.ОбменДанными.Загрузка = Истина;
НовыйЭлемент.Записать();
Этот способ мгновенно решает проблему сброса GUID, однако у него есть существенный недостаток. Поскольку при ОбменДанными.Загрузка = Истина отключаются все проверки конфигурации, система может записать объект с некорректно заполненными реквизитами, не зарегистрировать изменения на планах обмена для других узлов базы данных, не создать подчиненные регистры сведений и не рассчитать связанные данные. Использовать данный подход нужно с большой осторожностью.
Чтобы гарантированно сохранить переданный GUID и одновременно с этим заставить систему выполнить всю необходимую бизнес-логику (проверки, заполнение реквизитов, регистрацию в планах обмена), в профессиональной разработке на платформе 1С применяется паттерн «Двойной записи».
Суть паттерна заключается в следующем:
ОбменДанными.Загрузка = Истина. Объект успешно сохраняется в СУБД с требуемым GUID.ОбменДанными.Загрузка (устанавливаем в Ложь) и повторно вызываем метод Записать(). На этом этапе объект уже физически существует в базе, его GUID зафиксирован и больше не изменится, но платформа корректно запустит все обработчики событий, подписки и проверки бизнес-логики для существующего объекта.Рассмотрим, как выглядит программная реализация паттерна «Двойной записи»:
НовыйGUID = Новый УникальныйИдентификатор(СтрокаGUID);
СсылкаНаНоменклатуру = Справочники.Номенклатура.ПолучитьСсылку(НовыйGUID);
// Проверяем, нет ли уже такого элемента в базе
Если СсылкаНаНоменклатуру.ПолучитьОбъект() = Неопределено Тогда
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.УстановитьСсылкуНового(СсылкаНаНоменклатуру);
НовыйЭлемент.Наименование = "Новый товар";
// Проход 1: Фиксируем GUID в базе данных без проверок
НовыйЭлемент.ОбменДанными.Загрузка = Истина;
НовыйЭлемент.Записать();
// Проход 2: Запускаем всю бизнес-логику для созданного объекта
НовыйЭлемент.ОбменДанными.Загрузка = Ложь;
НовыйЭлемент.Записать();
КонецЕсли;
Обратим внимание на то, как автор темы на форуме проверял, существует ли элемент с таким GUID в информационной базе. Он использовал следующую конструкцию:
Если СсылкаНаНоменклатуру.Наименование <> "" тогда
Возврат СсылкаНаНоменклатуру;
Конецесли;
Такой подход является в корне неверным и неоптимальным! И вот почему:
Наименование не вызовет ошибку выполнения, но вернет пустую строку. При этом платформа выполнит неявный и тяжелый запрос к базе данных, пытаясь прочитать несуществующий объект.Для оптимизации этой операции и защиты от ошибок рекомендуется использовать два альтернативных метода.
Вариант А. Через получение объекта:
Если СсылкаНаНоменклатуру.ПолучитьОбъект() = Неопределено Тогда
// Элемент гарантированно отсутствует в базе данных, можно создавать
КонецЕсли;
Вариант Б. Через легкий запрос к СУБД (Рекомендуется для высокой производительности):
Запрос работает значительно быстрее, так как не кэширует «битые» ссылки и не считывает лишние данные из таблиц базы данных.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаНоменклатуру);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
// Элемент действительно не найден, пишем логику создания
КонецЕсли;
Еще одна классическая ошибка начинающих программистов 1С заключается в передаче неправильного типа данных в метод УстановитьСсылкуНового(). Попытка написать код следующим образом приведет к падению системы:
// ОШИБКА: Метод УстановитьСсылкуНового ожидает объект Ссылка, а не УникальныйИдентификатор!
НовыйЭлемент.УстановитьСсылкуНового(Новый УникальныйИдентификатор(СтрокаGUID));
Всегда помните: метод УстановитьСсылкуНового() принимает исключительно тип данных соответствующей ссылки (в нашем случае это СправочникСсылка.Номенклатура), предварительно полученный с помощью метода менеджера справочника ПолучитьСсылку(УникальныйИдентификатор).
Учитывайте архитектуру клиент-серверного взаимодействия платформы 1С:Предприятие 8. Метод УстановитьСсылкуНового() может быть вызван исключительно в контексте **Сервера**, в модулях внешнего соединения (COM) или в Толстом клиенте. Попытка выполнить установку ссылки на стороне Тонкого клиента вызовет ошибку компиляции или выполнения.
Соблюдение этих простых правил, использование паттерна «Двойной записи» и правильная проверка существования объектов позволят вам создавать надежные интеграционные решения и избегать досадных ошибок с изменением или потерей уникальных идентификаторов данных.