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