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