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