Почему возникает ошибка "Объект не найден" при использовании УстановитьСсылкуНового и как ее исправить?

Программист 1С v8.3 (Управляемые формы)
← На главную

При программном создании связанных объектов в 1С:Предприятии часто возникает необходимость получить ссылку на новый, еще не записанный в базу объект, чтобы использовать ее в реквизитах другого объекта. Для этих целей в платформе предусмотрен метод УстановитьСсылкуНового. Однако, при некорректном его использовании, особенно в асинхронных или многоэтапных серверных вызовах, мы можем столкнуться с ошибкой "Объект не найден". Рассмотрим эту проблему подробнее и разберем эффективные способы ее решения.

В чем суть проблемы "Объект не найден"?

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

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

Давайте проанализируем исходный код, который привел к проблеме:


// В процедуре ПередЗаписьюНаСервере модуля УФ:
СсылкаНового = ОбщегоНазначения.МенеджерОбъектаПоСсылке(ОбъектСвязанногоСправочника.Ссылка).ПолучитьСсылку;
ОбъектСвязанногоСправочника.УстановитьСсылкуНового(СсылкаНового);
ТекущийОбъект[ИмяРеквизита] = СсылкаНового; // записываем в текущий объект ссылку на элемент связанного справочника

// Далее в процедуре ПриЗаписиНаСервере выполняем запись:
ОбъектСвязанногоСправочника.Записать;

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

Решение 1: Сохранение контекста и установка ссылки в одной процедуре

Первое и наиболее очевидное решение — это обеспечить, чтобы генерация ссылки, установка ее для нового объекта и его запись происходили в рамках одного логического блока или одной серверной процедуры, либо чтобы сгенерированная ссылка была явно передана между процедурами. Самый надежный способ — это объединить все действия в процедуре ПриЗаписиНаСервере, поскольку именно там происходит окончательная фиксация данных.

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

  1. Добавляем временный реквизит формы: Предположим, мы назовем его СсылкаНовогоСвязанногоОбъекта, тип - Произвольный.

  2. Генерируем ссылку и сохраняем ее в реквизит формы в ПередЗаписьюНаСервере:

    
    // В процедуре ПередЗаписьюНаСервере модуля УФ:
    // ОбъектСвязанногоСправочника - это объект, созданный ранее, например, Справочники.МойСправочник.СоздатьЭлемент()
    // Важно: ОбъектСвязанногоСправочника.Ссылка на этом этапе - пустая
    СсылкаНового = ОбщегоНазначения.МенеджерОбъектаПоСсылке(ОбъектСвязанногоСправочника.Ссылка).ПолучитьСсылку;
    
    // Сохраняем сгенерированную ссылку во временный реквизит формы
    // Это позволит нам передать ее в ПриЗаписиНаСервере
    ЭтаФорма.СсылкаНовогоСвязанногоОбъекта = СсылкаНового;
    
    // Присваиваем сгенерированную ссылку реквизиту текущего (основного) объекта
    // Это нужно для того, чтобы основной объект 'знал' ссылку на связанный
    ТекущийОбъект[ИмяРеквизита] = СсылкаНового;
    
  3. Используем сохраненную ссылку для записи связанного объекта в ПриЗаписиНаСервере:

    
    // В процедуре ПриЗаписиНаСервере модуля УФ:
    // ОбъектСвязанногоСправочника - это объект, созданный ранее, например, Справочники.МойСправочник.СоздатьЭлемент()
    // Получаем сгенерированную ссылку из реквизита формы
    СсылкаНового = ЭтаФорма.СсылкаНовогоСвязанногоОбъекта;
    
    // Устанавливаем эту ссылку для связанного объекта перед его записью
    // Это критически важно, чтобы объект был записан именно под этой ссылкой
    ОбъектСвязанногоСправочника.УстановитьСсылкуНового(СсылкаНового);
    
    // Для корректной записи, особенно при обмене данными, часто используют ОбменДанными.Загрузка = Истина
    // Это позволяет обойти стандартные проверки и обработчики, которые могут быть не нужны
    ОбъектСвязанногоСправочника.ОбменДанными.Загрузка = Истина;
    
    // Выполняем запись связанного объекта в базу данных
    ОбъектСвязанногоСправочника.Записать();
    
    // После записи связанного объекта, если основной объект еще не записан,
    // или если это нужно для обновления данных в форме, можем перечитать данные основного объекта.
    // Например, если основной объект также создается, то его запись произойдет после этого блока.
    

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

Решение 2: Использование ПолучитьСсылку с явным УникальнымИдентификатором

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

  1. Генерируем ссылку с новым УИД:

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

    Как мы видим из справки, метод ПолучитьСсылку() у менеджера объекта имеет параметр УникальныйИдентификатор. Если он не указан, то платформа сама сформирует новый уникальный идентификатор. Поэтому, вызов ПолучитьСсылку() без параметров обычно достаточен, но явное Новый УникальныйИдентификатор() подчеркивает намерение создать новую уникальную ссылку.

  2. Дальнейшая запись в ПриЗаписиНаСервере аналогична первому решению.

Пример из форума, демонстрирующий комплексный подход с созданием элемента и последующей работой с ним:


// Предположим, мы работаем с Документом, который создает Заказ
// В каком-то обработчике до записи основного Документа:
СсылкаЗаказа = Документы.Заказ.ПолучитьСсылку(Новый УИД); // Генерируем ссылку для нового заказа
ДокОбъект.ТекЗаказ = СсылкаЗаказа; // Присваиваем ссылку реквизиту основного документа
ДокОбъект.Записать(); // Записываем основной документ, чтобы ссылка на заказ сохранилась

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

Этот пример немного отличается, но демонстрирует ключевой принцип: сначала мы генерируем ссылку, сохраняем ее, а затем, когда создаем объект, устанавливаем эту ссылку с помощью УстановитьСсылкуНового и только потом записываем сам объект. Это обеспечивает целостность данных.

Особенности использования УстановитьСсылкуНового и ПолучитьСсылкуНового

Разберем еще раз терминологию, чтобы избежать путаницы:

  1. МенеджерОбъекта.ПолучитьСсылку(): Этот метод вызывается у менеджера справочника, документа или другого ссылочного объекта (например, Справочники.МойСправочник.ПолучитьСсылку()). Он предназначен для генерации новой, уникальной ссылки, которая может быть использована для будущего объекта. Как было упомянуто, он может принимать необязательный параметр УникальныйИдентификатор. Если параметр не указан, платформа сгенерирует новый УИД.

  2. Объект.УстановитьСсылкуНового(Ссылка): Этот метод вызывается у самого объекта (например, МойЭлементСправочника.УстановитьСсылкуНового(МояНоваяСсылка)). Его цель — присвоить новому, еще не записанному в базу данных объекту заранее определенную ссылку. Эта "зарезервированная" ссылка будет использоваться при его первой записи в базу данных. Крайне важно, чтобы этот метод был вызван перед Записать() для того же объекта и в том же логическом блоке (транзакции).

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

Посмотрим на пример из синтакс-помощника 1С:


Ссылка = СправочникОбъект.ПолучитьСсылкуНового();
СправочникОбъект.УстановитьСсылкуНового(Ссылка);

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

Ключевые выводы и рекомендации

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

  2. Последовательность вызовов: Методы УстановитьСсылкуНового() и Записать() должны быть вызваны последовательно для одного и того же объекта. УстановитьСсылкуНового() всегда предшествует Записать() для нового объекта.

  3. Использование ОбменДанными.Загрузка = Истина: При программном создании и записи объектов (особенно связанных) для обхода стандартных проверок и обработчиков, которые могут быть неактуальны или мешать, мы можем временно установить свойство Объект.ОбменДанными.Загрузка = Истина перед вызовом Записать(). Не забудьте вернуть его в исходное состояние, если это требуется для дальнейшей логики.

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

  5. Генерация ссылок: Для получения новой ссылки рекомендуется использовать МенеджерОбъекта.ПолучитьСсылку(Новый УникальныйИдентификатор()) для явной гарантии создания уникального идентификатора.

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

← На главную