Почему запрос не работает в форме документа 1С и как это исправить?

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

Мы сталкиваемся с распространенной ситуацией в разработке на платформе 1С:Предприятие 8.3, когда при попытке выполнить запрос непосредственно из формы документа возникает ошибка "Тип не определен (Запрос)". Эта проблема вызывает недоумение у многих начинающих разработчиков, и мы разберем ее суть, выясним причину и предложим эффективные решения.

Мы рассмотрим сценарий, когда требуется получить данные, например, цену номенклатуры, из справочника Номенклатура для табличной части документа ПриходнаяНакладная. Изначально, пытаясь использовать объект Запрос напрямую в клиентском коде формы, мы получаем ошибку, указывающую на то, что система не знает такого типа.

Понимание Клиент-Серверной Архитектуры в 1С:Предприятие

Прежде чем перейти к решению, нам важно глубоко понять, как устроено взаимодействие между клиентской и серверной частями в 1С:Предприятие. Платформа 1С:Предприятие 8.3, особенно при использовании тонкого или веб-клиента, работает по принципу клиент-серверной архитектуры. Это означает, что приложение разделено на две основные части:

  1. Клиентское приложение: Это та часть, которая запускается на компьютере пользователя. Она отвечает за отображение интерфейса, взаимодействие с пользователем и выполнение логики, не требующей доступа к базе данных.
  2. Сервер 1С:Предприятия: Это центральный компонент, который обрабатывает запросы от клиентских приложений, взаимодействует с сервером баз данных (например, PostgreSQL, MS SQL Server) и выполняет основную бизнес-логику, требующую работы с данными.

В этой архитектуре крайне важно разделение контекстов выполнения кода. Для управления тем, где будет выполняться код, используются специальные директивы компиляции:

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

Почему возникает ошибка "Тип не определен (Запрос)"?

Теперь, когда мы понимаем клиент-серверную архитектуру, становится ясно, почему возникает ошибка "Тип не определен (Запрос)". В исходном коде, приведенном в сообщении 1, объект Запрос пытаются создать и выполнить непосредственно в клиентской процедуре формы:


СтрокаТЧ = Элементы.Материалы.ТекущиеДанные;
Запрос = Новый Запрос; // <-- Здесь возникает ошибка!
Запрос.Текст = "ВЫБРАТЬ ...";
// ...

Система 1С:Предприятие выдает ошибку "Тип не определен (Запрос)" потому, что объект Новый Запрос недоступен для создания и использования в клиентском контексте. Методы и объекты, предназначенные для работы с базой данных, такие как Запрос, МенеджерЗаписи, Объект.Записать() (при модификации данных) и другие, могут быть использованы только на сервере. Клиент просто "не знает" о существовании такого типа объекта.

Настоятельно рекомендуем регулярно обращаться к Синтакс-Помощнику (открывается по кнопке F1 в конфигураторе), чтобы проверять доступность методов и объектов. Синтакс-Помощник четко указывает, в каком контексте (Клиент, Сервер, Толстый клиент, Внешнее соединение) доступен тот или иной элемент языка или объект платформы. Это базовый инструмент для каждого разработчика 1С.

Решение: Выполнение запроса на сервере

Единственно правильным решением для выполнения запроса из формы документа является перенос логики работы с запросом на сервер. Мы должны создать на сервере отдельную функцию или процедуру, которая будет получать необходимые данные из базы данных, а затем вызывать эту серверную функцию из клиентского кода.

Рассмотрим шаги для реализации этого подхода:

  1. Создание серверной функции: Мы создадим новую функцию в модуле формы, которая будет помечена директивой &НаСервере или &НаСервереБезКонтекста. Рекомендуем использовать &НаСервереБезКонтекста, если функции не требуется доступ к реквизитам самой формы, а только к переданным параметрам.
  2. Размещение кода запроса на сервере: Внутри этой серверной функции мы разместим весь код, связанный с созданием, настройкой и выполнением запроса.
  3. Определение параметров для запроса: Серверная функция должна принимать в качестве параметров все данные, необходимые для выполнения запроса (например, ссылку на номенклатуру, для которой нужно получить цену).
  4. Возврат результата на клиент: После выполнения запроса и обработки его результата, серверная функция должна вернуть полученные данные (например, цену) обратно в клиентский код.
  5. Вызов серверной функции с клиента: Из клиентской процедуры (например, обработчика события ПриИзменении для поля Материал в табличной части) мы будем вызывать эту серверную функцию, передавая ей необходимые параметры.
  6. Использование результата на клиенте: Полученный от сервера результат (цену) мы присвоим соответствующему реквизиту табличной части формы.

Детальный разбор примера правильной реализации

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


&НаКлиенте
Процедура МатериалыМатериалПриИзменении(Элемент)
    СтрокаТЧ = Элементы.Материалы.ТекущиеДанные;
    // Вызываем серверную функцию для получения цены
    СтрокаТЧ.Цена = ПолучитьЦенуНоменклатурыНаСервере(СтрокаТЧ.Материал); // Здесь СтрокаТЧ.Материал - это ссылка на элемент справочника Номенклатура
    СтрокаТЧ.Сумма = СтрокаТЧ.Цена * СтрокаТЧ.Количество;
КонецПроцедуры

&НаСервереБезКонтекста // Или &НаСервере, если нужен контекст формы
Функция ПолучитьЦенуНоменклатурыНаСервере(НоменклатураСсылка)
    РезультатЦена = 0;
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("ТекущаяСсылка", НоменклатураСсылка);
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Номенклатура.Цена КАК Цена
        |ИЗ
        |    Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |    Номенклатура.Ссылка = &ТекущаяСсылка";
    
    Выборка = Запрос.Выполнить().Выбрать(); // Обратите внимание на () после Выполнить
    
    Если Выборка.Следующий() Тогда // И на () после Следующий
        РезультатЦена = Выборка.Цена;
    КонецЕсли;
    
    Возврат РезультатЦена;
КонецФункции

Разберем этот код по шагам:

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

Этот подход обеспечивает корректное взаимодействие с базой данных, соблюдая правила клиент-серверной архитектуры 1С:Предприятие.

Альтернативные способы получения данных без запроса

Хотя выполнение запроса на сервере является наиболее гибким и мощным способом получения данных, в некоторых простых случаях можно обойтись без явного создания объекта Запрос.

Если в вашей табличной части уже имеется поле типа СправочникСсылка.Номенклатура (например, колонка Материал), вы можете напрямую обратиться к свойствам объекта, на который указывает эта ссылка. Например, вы можете написать:


&НаКлиенте
Процедура МатериалыМатериалПриИзменении(Элемент)
    СтрокаТЧ = Элементы.Материалы.ТекущиеДанные;
    Если СтрокаТЧ.Материал.Пустая() Тогда
        СтрокаТЧ.Цена = 0;
    Иначе
        // Обращение к свойству Цена объекта Справочник.Номенклатура
        // Это вызывает неявный серверный вызов для загрузки объекта,
        // если он не был загружен ранее на клиент.
        СтрокаТЧ.Цена = СтрокаТЧ.Материал.Цена;
    КонецЕсли;
    СтрокаТЧ.Сумма = СтрокаТЧ.Цена * СтрокаТЧ.Количество;
КонецПроцедуры

В этом случае, когда мы обращаемся к СтрокаТЧ.Материал.Цена, платформа 1С:Предприятие автоматически выполняет неявный серверный вызов для загрузки объекта Справочник.Номенклатура на клиент, если его данные еще не были доступны. Это решение кажется проще, так как не требует явного написания серверной функции и текста запроса. Однако, оно имеет свои особенности:

Мы выбираем этот способ, если задача получения данных проста и сводится к доступу к базовым реквизитам объекта, а ссылку на этот объект мы уже имеем на клиенте. Однако, для более сложных сценариев или при необходимости получения данных из нескольких таблиц, серверный запрос остается основным и наиболее правильным инструментом.

← На главную