В практике разработки на платформе 1С:Предприятие 8 начинающие программисты часто сталкиваются с ситуацией, когда после программного создания элемента справочника или документа попытка записать его в реквизит другой структуры или документа не приносит результата. В отладчике мы видим, что объект создан, все его поля заполнены, но целевой реквизит упорно показывает значение ПустаяСсылка. В этой статье мы подробно разберем, почему так происходит, изучим разницу между объектами и ссылками, а также научимся правильно манипулировать данными в памяти и базе данных.
Рассмотрим ситуацию, описанную в вопросе. Программист создает новый элемент справочника Номенклатура, заполняет его и записывает. Далее он пытается присвоить этот объект в поле СтрокаДанных.Номенклатура. На первый взгляд код выглядит логично, но он не работает. Давайте проанализируем причину.
В системе 1С существует жесткое разделение между типами данных СправочникОбъект и СправочникСсылка. Разберем их суть:
СправочникОбъект.Номенклатура): Это тип данных, представляющий собой всю совокупность реквизитов элемента, загруженную в оперативную память для редактирования. Только у объекта есть методы Записать(), Удалить() и возможность изменять значения реквизитов.СправочникСсылка.Номенклатура): Это "указатель" или "адрес" записи в базе данных. Физически это уникальный идентификатор (GUID). Ссылка — это легкий объект, который хранится в реквизитах других объектов (например, в табличных частях документов или в реквизитах других справочников).Когда мы определяем реквизит в метаданных (например, поле "Номенклатура" в табличной части), мы задаем ему тип СправочникСсылка.Номенклатура. Платформа ожидает получить именно GUID (адрес), а не весь набор данных объекта. Попытка передать туда СправочникОбъект — это попытка вставить в поле для "номера телефона" целого "человека".
Для того чтобы корректно передать данные, нам нужно обратиться к свойству Ссылка нашего объекта. Это свойство содержит тот самый уникальный идентификатор, который понятен реквизиту. Рассмотрим правильный алгоритм действий на примере кода:
НомОбъект = Справочники.Номенклатура.СоздатьЭлемент();
// Заполнение реквизитов нового объекта
НомОбъект.Наименование = "Новый товар";
// Важно: ссылка появляется только после первой записи объекта в БД (или принудительной установки)
НомОбъект.Записать();
// ПРАВИЛЬНОЕ ПРИСВОЕНИЕ:
СтрокаДанных.Номенклатура = НомОбъект.Ссылка;
Обратите внимание на последовательность. Мы сначала вызываем метод Записать(), чтобы объект физически появился в базе данных и получил свой уникальный адрес, а затем передаем этот адрес (Ссылка) в нужное нам поле.
Выясним причину, по которой 1С не выдает ошибку при попытке присвоить объект вместо ссылки. В многих языках программирования со строгой типизацией такая операция вызвала бы исключение (Exception) еще на этапе компиляции или выполнения. Однако в 1С реализован механизм, который иногда называют "динамическим приведением типов" или, в ироничном ключе, "родовой травмой платформы".
Проанализируем ситуацию: когда вы пытаетесь присвоить значение типа СправочникОбъект полю, которое имеет тип СправочникСсылка, система видит несовпадение типов. Вместо того чтобы остановить работу программы, платформа 1С просто сбрасывает значение реквизита в значение по умолчанию для этого типа — то есть в Пустую Ссылку. Это делает такие ошибки трудноотловимыми, так как код продолжает выполняться, но данные "молча" теряются.
Важное правило: Всегда проверяйте типы данных в Табло или через функцию ТипЗнч(), если видите, что реквизиты не заполняются при явном присваивании.
Иногда возникают ситуации, когда нам нужно передать ссылку на объект еще до того, как мы его окончательно записали. Например, при формировании сложных связанных структур данных в одной транзакции. Для этого в 1С существует метод УстановитьСсылкуНового().
Посмотрим на пример, как можно заранее зарезервировать "адрес" для объекта:
НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
// Генерируем новый идентификатор и назначаем его объекту
НоваяСсылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор());
НовыйОбъект.УстановитьСсылкуНового(НоваяСсылка);
// Теперь мы можем использовать НоваяСсылка еще ДО вызова Записать()
СтрокаДанных.Номенклатура = НоваяСсылка;
// В конце обязательно записываем объект, иначе ссылка в СтрокаДанных будет указывать в никуда (битая ссылка)
НовыйОбъект.Записать();
Этот метод требует осторожности: если объект так и не будет записан, вы получите "битую ссылку" (объект не найден) в базе данных.
Разберем еще один важный аспект, касающийся работы со ссылками. После того как вы успешно присвоили ссылку СтрокаДанных.Номенклатура = НомОбъект.Ссылка, помните о разнице в производительности при дальнейшем обращении к данным.
Если у вас в памяти уже есть НомОбъект, чтение его свойств (например, НомОбъект.Артикул) происходит мгновенно, так как данные уже загружены. Если же вы обращаетесь через ссылку (СтрокаДанных.Номенклатура.Артикул), платформа выполняет неявный запрос к базе данных, чтобы "разыменовать" ссылку и получить значение. В циклах это может привести к серьезным задержкам (проблема N+1 запроса). Поэтому хорошей практикой считается использование объекта для получения данных, пока он доступен в контексте выполнения кода.
Выясним, как контекст выполнения влияет на работу с объектами и ссылками. Это критически важно для управляемого приложения:
СправочникОбъект, ДокументОбъект и им подобные недоступны. Вы не можете передать НомОбъект с сервера на клиент.Поэтому, если ваша задача — создать элемент и вернуть его на форму (клиент), вы всегда должны возвращать именно Ссылка.
Проанализировав проблему, мы можем составить краткий чек-лист для корректной работы:
.Ссылка..ПолучитьОбъект().Соблюдение этих простых правил и понимание архитектуры типов 1С позволит вам избегать трудноловимых багов и писать более производительный код.