Разберем классическую задачу разработки в среде 1С:Предприятие, с которой часто сталкиваются программисты при импорте данных или создании сложных связей объектов. Представьте сценарий: нам необходимо программно создать новый элемент справочника Номенклатура и одновременно с этим — подчиненный ему элемент справочника ЕдиницыИзмерения, который затем должен быть назначен базовой единицей для нашей новой номенклатуры.
Часто разработчики пытаются решить эту задачу "в лоб" с помощью метода УстановитьСсылкуНового(), но сталкиваются с неприятной ошибкой платформы: «Значение поля "Владелец" не заполнено или заполнено неверно». Выясним причину этого поведения платформы и рассмотрим правильную архитектуру программной записи связанных элементов без применения "двойной записи" и без использования опасного режима загрузки данных.
Проанализируем ситуацию на примере кода, который часто приводит к ошибке:
БазоваяЕдиница = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");
НовыйЭлементНоменклатура = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлементНоменклатура.Наименование = "Новый товар";
НовыйЭлементНоменклатура.БазоваяЕдиницаИзмерения = БазоваяЕдиница;
// Устанавливаем будущую ссылку для Номенклатуры
НоваяСсылкаНоменклатуры = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор());
НовыйЭлементНоменклатура.УстановитьСсылкуНового(НоваяСсылкаНоменклатуры);
НовыйЭлементЕдиница = Справочники.ЕдиницыИзмерения.СоздатьЭлемент();
НовыйЭлементЕдиница.Наименование = БазоваяЕдиница.Наименование;
НовыйЭлементЕдиница.ЕдиницаПоКлассификатору = БазоваяЕдиница;
// Пытаемся передать еще не записанную ссылку во владельца подчиненного элемента
НовыйЭлементЕдиница.Владелец = НовыйЭлементНоменклатура.ПолучитьСсылкуНового();
НовыйЭлементЕдиница.Коэффициент = 1;
// Здесь происходит ошибка: Значение поля "Владелец" не заполнено!
НовыйЭлементЕдиница.Записать();
Посмотрим, почему этот код не работает. Платформа 1С на уровне СУБД жестко контролирует ссылочную целостность данных. Метод ПолучитьСсылкуНового() возвращает ссылку, которой физически еще нет в таблицах базы данных, так как транзакция записи объекта Номенклатура еще не была выполнена.
Когда мы вызываем метод НовыйЭлементЕдиница.Записать(), СУБД проверяет поле Владелец. Так как элемент-владелец с указанным уникальным идентификатором (UUID) отсутствует в базе, платформа блокирует запись подчиненного объекта и выдает ошибку заполнения владельца.
Некоторые разработчики пытаются обойти это ограничение, используя свойство ОбменДанными.Загрузка = Истина. Этот подход действительно отключает проверки ссылочной целостности и позволяет записать единицу измерения с несуществующим владельцем. Однако использовать этот метод в штатных обработках крайне не рекомендуется. Режим загрузки данных отключает все стандартные проверки заполнения реквизитов, а также блокирует выполнение обработчиков событий ПередЗаписью и ПриЗаписи, что может привести к нарушению логики работы конфигурации.
Решим задачу изящно и в соответствии со стандартами разработки фирмы "1С". Платформа накладывает строгие ограничения на запись подчиненных объектов без владельца, но при этом она абсолютно лояльно относится к записи владельцев, которые ссылаются на еще не созданные подчиненные элементы.
Используем это свойство платформы. Вместо того чтобы генерировать ссылку для Номенклатуры, мы заранее сгенерируем ссылку для подчиненной ЕдиницыИзмерения, присвоим ее номенклатуре, запишем номенклатуру, а затем запишем единицу измерения с уже созданным реальным владельцем.
Разберем по шагам этот алгоритм:
ЕдиницыИзмерения.УстановитьСсылкуНового(). Записывать объект на данном этапе не нужно.Номенклатура. Присвоим реквизитам базовой единицы ранее сгенерированную ссылку на единицу измерения. Платформа позволит это сделать без ошибок.Номенклатура. Теперь товар физически сохранен в базе данных и у нас есть его реальная ссылка.Владелец у подготовленного объекта единицы измерения, передав туда ссылку уже записанной номенклатуры.Посмотрим на пример программной реализации этого алгоритма:
// Получаем базовую классификаторную единицу (например, штуки)
БазоваяЕдиница = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");
// ШАГ 1. Генерируем ссылку для будущей единицы измерения номенклатуры
СсылкаНовойЕдиницы = Справочники.ЕдиницыИзмерения.ПолучитьСсылку(Новый УникальныйИдентификатор());
// ШАГ 2. Создаем объект Единицы в памяти и принудительно назначаем ему ссылку
НовыйЭлементЕдиница = Справочники.ЕдиницыИзмерения.СоздатьЭлемент();
НовыйЭлементЕдиница.УстановитьСсылкуНового(СсылкаНовойЕдиницы);
НовыйЭлементЕдиница.Наименование = БазоваяЕдиница.Наименование;
НовыйЭлементЕдиница.ЕдиницаПоКлассификатору = БазоваяЕдиница;
НовыйЭлементЕдиница.Коэффициент = 1;
// Владельца пока не заполняем и метод Записать() не вызываем!
// ШАГ 3. Создаем элемент Номенклатуры
НовыйЭлементНоменклатура = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлементНоменклатура.Наименование = "Новый товар (тест записи)";
НовыйЭлементНоменклатура.Артикул = "АРТ-100-200";
// Присваиваем номенклатуре зарезервированную ссылку подчиненной единицы
НовыйЭлементНоменклатура.БазоваяЕдиницаИзмерения = СсылкаНовойЕдиницы;
// ШАГ 4. Записываем Номенклатуру. Теперь она есть в базе данных!
НовыйЭлементНоменклатура.Записать();
// ШАГ 5. Теперь у нас есть реальный владелец. Передаем его ссылку в Единицу измерения
НовыйЭлементЕдиница.Владелец = НовыйЭлементНоменклатура.Ссылка;
// ШАГ 6. Записываем Единицу измерения в базу данных
НовыйЭлементЕдиница.Записать();
Проанализируем преимущества, которые дает этот шаблон проектирования:
ОбменДанными.Загрузка = Истина. Это гарантирует, что все встроенные контроли, проверки заполнения реквизитов и подписки на события в вашей конфигурации отработают корректно.