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