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