Как получить реквизиты ссылки из структуры на клиенте в 1С:Разбор ошибки и лучшие практики

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

В процессе разработки и поддержки конфигураций на платформе 1С:Предприятие 8.3 (особенно при переходе с обычных форм на управляемые) программисты часто сталкиваются с ошибками обращения к реквизитам объектов. Типичный сценарий: на сервере формируется массив структур, содержащих ссылки на элементы справочников, а при попытке прочитать свойства этих ссылок на клиенте система аварийно завершает работу с ошибкой «Поле объекта не обнаружено». В этой статье мы подробно разберем, почему возникает эта проблема, как устроены ссылочные типы данных на клиенте и какие методы решения являются наиболее производительными и архитектурно правильными.

Выясняем причину: почему код падает с ошибкой?

Рассмотрим ситуацию, с которой столкнулся автор темы. В унаследованном коде выполнялся запрос на сервере, результаты которого упаковывались в массив структур и отправлялись на клиент:


// Код на сервере:
Массив = Новый Массив();
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Запись = Новый Структура;
    Запись.Вставить("Договор", Выборка.Договор);
    Запись.Вставить("Контрагент", Выборка.Контрагент);
    Массив.Добавить(Запись);
КонецЦикла;
// Массив передается на клиент...

На стороне клиента в цикле происходило обращение к реквизиту ссылки через точку:


// Код на клиенте:
Для Каждого Строка Из Массив Цикл
    Контр = Строка.Договор.Владелец; // Здесь падает ошибка!
КонецЦикла;

Давайте проанализируем, почему это происходит. Начинающие разработчики могут предположить, что в структуре хранится не ссылка, а строка или какой-то другой базовый тип. На самом деле в структуре действительно находится полноценная ссылка (тип СправочникСсылка.ДоговорыКонтрагентов). Однако в управляемом приложении (на тонком и веб-клиенте) возможности работы со ссылочными типами существенно ограничены.

Клиентская ссылка — это крайне «легковесный» объект. Для экономии сетевого трафика и оперативной памяти платформа не передает на клиент всю информацию об объекте базы данных. Ссылка на клиенте содержит только три вещи:

  1. Уникальный идентификатор (GUID/UUID) записи в СУБД.
  2. Тип метаданных (например, Справочник.ДоговорыКонтрагентов).
  3. Строковое кэшированное представление (например, наименование договора), которое используется для отображения в элементах формы.

Поскольку у тонкого клиента нет прямого доступа к базе данных, обращение вида Строка.Договор.Владелец физически невозможно выполнить локально. Платформа генерирует ошибку «Поле объекта не обнаружено», так как у клиентской ссылки нет физических свойств (реквизитов) объекта.

Для сравнения: в устаревшем толстом клиенте в обычном приложении такое обращение «через точку» работало. Но за кулисами платформа выполняла неявный, крайне неоптимальный запрос к серверу и СУБД для получения всего объекта в локальный кэш. В управляемых формах эта механика полностью заблокирована ради быстродействия системы.

Опасность обращения «через точку» на сервере

Некоторые участники обсуждения рекомендуют просто передавать ссылку на сервер и получать владельца там. Однако здесь кроется еще одна серьезная ловушка для производительности. Обращение к реквизитам ссылки через точку на сервере (например, ДоговорСсылка.Владелец) является классическим антипаттерном.

Когда вы обращаетесь к реквизиту ссылки через точку в коде 1С на сервере, платформа выполняет неявный SQL-запрос и считывает из базы данных весь объект целиком — со всеми его многочисленными реквизитами, табличными частями и длинными текстовыми описаниями. Если выполнять такой код в цикле по массиву из сотен или тысяч элементов, система начнет лавинообразно считывать избыточные данные, что приведет к критическим задержкам в работе интерфейса.

Оптимальные способы решения проблемы

Рассмотрим несколько правильных подходов к решению этой задачи, упорядоченных по степени эффективности.

Способ 1. Получение всех данных в исходном запросе (Best Practice)

Самый производительный и правильный способ — модифицировать исходный запрос на сервере так, чтобы он сразу извлекал все необходимые свойства объектов. Таким образом, на клиент будет передаваться готовая, «плоская» структура данных, не требующая никаких дополнительных обращений к серверу.

Рассмотрим пример реализации:


// Код на сервере:
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Договоры.Ссылка КАК Договор,
    |	Договоры.Владелец КАК КонтрагентДоговора,
    |	Взаиморасчеты.Контрагент КАК Контрагент
    |ИЗ
    |	РегистрНакопления.Взаиморасчеты КАК Взаиморасчеты
    |	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
    |		ПО Взаиморасчеты.Договор = Договоры.Ссылка";

Массив = Новый Массив();
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    Запись = Новый Структура;
    Запись.Вставить("Договор", Выборка.Договор);
    Запись.Вставить("КонтрагентДоговора", Выборка.КонтрагентДоговора);
    Запись.Вставить("Контрагент", Выборка.Контрагент);
    Массив.Добавить(Запись);
КонецЦикла;

При таком подходе на стороне клиента мы можем моментально получить контрагента договора без лишних вызовов сервера и без ошибок:


// Код на клиенте:
Для Каждого Строка Из Массив Цикл
    // Данные уже лежат в структуре, обращение происходит мгновенно
    Контр = Строка.КонтрагентДоговора; 
КонецЦикла;

Способ 2. Использование функций общего назначения БСП

Если ссылка уже находится на клиенте, и вам действительно понадобилось узнать значение одного или нескольких ее реквизитов (например, владельца конкретного договора), не пишите собственный серверный метод «с нуля» и тем более не используйте тяжелую конструкцию Ссылка.ПолучитьОбъект(). Для быстрого поиска готовых процедур и функций подойдёт справочник по методам БСП с примерами использования.

В составе Библиотеки стандартных подсистем (БСП) есть готовые высокооптимизированные функции общего назначения. Они выполняют точечный SQL-запрос к СУБД без чтения всего объекта в память:

Пример использования БСП на клиенте (через серверный вызов):


// На стороне клиента мы вызываем серверную функцию, передавая ссылку
&НаКлиенте
Процедура ОбработатьДоговор(ДоговорСсылка)
    Если ЗначениеЗаполнено(ДоговорСсылка) Тогда
        ВладелецДоговора = ПолучитьВладельцаДоговора(ДоговорСсылка);
    КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьВладельцаДоговора(СсылкаНаДоговор)
    // Используем оптимизированный метод БСП
    Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СсылкаНаДоговор, "Владелец");
КонецФункции

Способ 3. Использование внеконтекстных серверных вызовов

Если для получения данных вам все же требуется написать собственную логику на сервере, критически важно использовать правильную директиву компиляции — &НаСервереБезКонтекста. Для разбора нюансов работы клиент-серверных вызовов можно изучить материал об управлении местом выполнения кода в управляемых формах.

Давайте проанализируем разницу:

  1. При контекстном вызове (&НаСервере) платформа осуществляет сериализацию всей управляемой формы. Это значит, что по сети на сервер передаются все реквизиты формы, таблицы, элементы оформления, а затем измененный контекст возвращается обратно на клиент. Это создает колоссальную нагрузку на сеть.
  2. При внеконтекстном вызове (&НаСервереБезКонтекста) на сервер передаются только параметры, явно указанные в функции (например, сама ссылка Договор). Это работает в десятки раз быстрее и экономит ресурсы сервера.

Пример правильной реализации собственного серверного вызова:


&НаСервереБезКонтекста
Функция ПолучитьКонтрагентаИзДоговора(ДоговорСсылка)
    // Выполняем точечный запрос, чтобы не читать объект целиком
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Договоры.Владелец КАК Владелец
        |ИЗ
        |	Справочник.ДоговорыКонтрагентов КАК Договоры
        |ГДЕ
        |	Договоры.Ссылка = &Ссылка";
    
    Запрос.УстановитьПараметр("Ссылка", ДоговорСсылка);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Владелец;
    КонецЕсли;
    
    Возврат Неопределено;
КонецФункции

Что можно делать со ссылкой на клиенте без обращения к серверу?

Несмотря на то, что мы не можем получать реквизиты ссылки на клиенте напрямую, платформа позволяет выполнять с ней ряд полезных и быстрых операций без вызова сервера:

Резюмируя все вышесказанное: при проектировании клиент-серверного взаимодействия в 1С всегда придерживайтесь принципа «плоских данных». Старайтесь максимально готовить всю необходимую информацию на сервере еще на этапе выполнения запроса, минимизируйте число обращений к серверу из циклов и активно используйте оптимизированные методы БСП.

← На главную