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