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