Почему на клиенте недоступны реквизиты номенклатуры и метод ПолучитьОбъект()?

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

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

Анализ проблемы: поведение контекстов и ссылки на тонком клиенте

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


&НаКлиентеНаСервереБезКонтекста
Процедура ОбновитьДанныеИнформационнойПанели(Форма, ТекущаяСтрока)
    ТекущиеДанные = Форма.Объект.Товары.НайтиПоИдентификатору(ТекущаяСтрока);
    Если ТекущиеДанные <> Неопределено Тогда
        Если ЗначениеЗаполнено(ТекущиеДанные.Номенклатура) Тогда
            НаименованиеТовара = НоменклатураКлиентСервер.ПредставлениеНоменклатуры(
                ТекущиеДанные.Номенклатура,
                ТекущиеДанные.Характеристика,
                ТекущиеДанные.Серия);
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Внутри функции общего модуля НоменклатураКлиентСервер разработчик пытается получить артикул через объект ссылки:


Функция ПредставлениеНоменклатуры(НоменклатураПредставление, ХарактеристикаПредставление, СерияПредставление = "", НазначениеПредставление = "") Экспорт
    СтрПредставление = СокрЛП(НоменклатураПредставление);
    Если ТипЗнч(НоменклатураПредставление) = Тип("СправочникСсылка.Номенклатура") Тогда
        НоменклатураОбъект = НоменклатураПредставление.ПолучитьОбъект(); 
        СтрПредставление = СтрПредставление + " (" + СокрЛП(НоменклатураОбъект.Артикул) + ")";
    КонецЕсли;
    Возврат СтрПредставление;
КонецФункции

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

  1. Директива &НаКлиентеНаСервереБезКонтекста и клиент-серверные модули: Данная процедура и общий модуль с признаками «Клиент» и «Сервер» компилируются дважды и исполняются строго в том контексте, из которого были вызваны. Если вызов инициирован при открытии или подборе на сервере — код выполняется на сервере. Если вызов происходит при интерактивной активизации строки или клиентском пересчете — выполнение происходит на тонком клиенте.
  2. Ограничения типа СправочникСсылка на тонком клиенте: На стороне клиента ссылочный тип хранит только уникальный идентификатор (GUID) и текстовое представление. Метод ПолучитьОбъект() на тонком клиенте не существует в объектной модели платформы, что приводит к ошибке «Метод объекта не обнаружен».
  3. Флаг «Вызов сервера»: Установка флага «Вызов сервера» в свойствах модуля «Клиент (управляемое приложение) / Сервер» не заставляет функции модуля автоматически исполняться на сервере при клиентском вызове. Этот флаг предназначен только для чисто серверных модулей, к которым необходимо обращаться с клиента.
  4. Неоптимальность ПолучитьОбъект(): Даже в серверном контексте использование ПолучитьОбъект() ради чтения одного поля (артикула) считывает всю шапку и табличные части справочника в оперативную память, что создает избыточную нагрузку на СУБД.

Способ 1. Использование функций БСП для чтения реквизитов

Проанализируем наиболее универсальное решение в типовых конфигурациях (УТ 11, КА, ERP, УНФ, БП 3.0). Библиотека стандартных подсистем предоставляет оптимизированные методы чтения реквизитов по ссылке без получения объекта целиком. Они выполняют быстрый запрос к базе данных и корректно вызываются из любого контекста:


Функция ПредставлениеНоменклатуры(НоменклатураПредставление, ХарактеристикаПредставление, СерияПредставление = "", НазначениеПредставление = "") Экспорт
    СтрПредставление = СокрЛП(НоменклатураПредставление);
    
    Если ЗначениеЗаполнено(НоменклатураПредставление) И ТипЗнч(НоменклатураПредставление) = Тип("СправочникСсылка.Номенклатура") Тогда
        Артикул = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(НоменклатураПредставление, "Артикул");
        Если ЗначениеЗаполнено(Артикул) Тогда
            СтрПредставление = СтрПредставление + " (" + СокрЛП(Артикул) + ")";
        КонецЕсли;
    КонецЕсли;
    
    Возврат СтрПредставление;
КонецФункции

Если необходимо получить сразу несколько полей (например, артикул, код и ставку НДС), используем метод ОбщегоНазначения.ЗначенияРеквизитовОбъекта(), возвращающий структуру значений за одно обращение к базе данных.

Способ 2. Кэширование дополнительных данных в строке формы

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

  1. Добавим в табличную часть формы или связанную коллекцию реквизит Артикул (текстовый).
  2. При выборе номенклатуры или подборе на сервере заполняем реквизит ТекущиеДанные.Артикул.
  3. В процедуре обновления информационной панели на клиенте формируем представление чисто строковыми операциями без вызовов сервера:

&НаКлиентеНаСервереБезКонтекста
Процедура ОбновитьДанныеИнформационнойПанели(Форма, ТекущаяСтрока)
    ТекущиеДанные = Форма.Объект.Товары.НайтиПоИдентификатору(ТекущаяСтрока);
    Если ТекущиеДанные <> Неопределено Тогда
        Представление = Строка(ТекущиеДанные.Номенклатура);
        Если Не ПустаяСтрока(ТекущиеДанные.Артикул) Тогда
            Представление = Представление + " (" + СокрЛП(ТекущиеДанные.Артикул) + ")";
        КонецЕсли;
        Форма.ИнформационнаяПанель = Представление;
    КонецЕсли;
КонецПроцедуры

Способ 3. Разделение серверного и клиентского кода

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


Функция ПредставлениеНоменклатуры(НоменклатураСсылка, ХарактеристикаСсылка = Неопределено) Экспорт
    СтрПредставление = СокрЛП(НоменклатураСсылка);
    
    #Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
        // На сервере обращаемся к реквизиту напрямую через точку без получения объекта
        Если ЗначениеЗаполнено(НоменклатураСсылка) Тогда
            Артикул = НоменклатураСсылка.Артикул;
            Если ЗначениеЗаполнено(Артикул) Тогда
                СтрПредставление = СтрПредставление + " (" + СокрЛП(Артикул) + ")";
            КонецЕсли;
        КонецЕсли;
    #Иначе
        // На тонком клиенте вызываем серверную функцию для получения данных
        СтрПредставление = НоменклатураСервер.ПолучитьПолноеПредставление(НоменклатураСсылка, ХарактеристикаСсылка);
    #КонецЕсли
    
    Возврат СтрПредставление;
КонецФункции

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

← На главную