Передача данных запроса с сервера в управляемую форму и внешним системам
Содержание
Результат запроса в 1С 8.3 нельзя вернуть на клиент «как есть»: объекты РезультатЗапроса, ВыборкаИзРезультатаЗапроса и ТаблицаЗначений существуют только на сервере. Рабочих способов четыре: упаковать строки в Массив из Структура, загрузить таблицу в реквизит формы, положить данные во временное хранилище или вывести их в ТабличныйДокумент. Для внешних систем результат отдают через HTTP-сервис в формате JSON.
Ниже код для каждого варианта, правила выбора и разбор ошибок, которые возникают при передаче данных между сервером и клиентом. Всё относится к платформе 8.3 и управляемым формам; в обычных формах толстого клиента 8.2 правила другие.
Почему запрос нельзя выполнить прямо на клиенте
Тонкий клиент и веб-клиент не имеют доступа к базе данных. Объект Запрос на клиенте просто недоступен: попытка написать Новый Запрос в процедуре с директивой &НаКлиенте даст ошибку ещё при проверке модуля.
Поэтому любая выборка данных выглядит так:
- Клиентская процедура вызывает серверную.
- Сервер выполняет запрос и превращает результат в значение, которое можно сериализовать.
- Платформа передаёт это значение по сети на клиент.
Каждый такой вызов — отдельный сетевой обмен. На локальной сети он почти незаметен, а через интернет или веб-клиент десяток вызовов подряд уже даёт ощутимую задержку.
Какие типы проходят границу клиент-сервер
| Можно вернуть на клиент | Нельзя вернуть на клиент |
|---|---|
| Строка, Число, Дата, Булево, Неопределено | Запрос, РезультатЗапроса |
Ссылки на объекты (СправочникСсылка.*, ДокументСсылка.*) | ВыборкаИзРезультатаЗапроса |
Массив, Структура, Соответствие и их фиксированные версии | ТаблицаЗначений, ДеревоЗначений |
СписокЗначений | СправочникОбъект, ДокументОбъект |
ТабличныйДокумент, ХранилищеЗначения | Менеджеры (Справочники.*, РегистрыНакопления.*) |
Коллекция проходит границу только тогда, когда всё её содержимое тоже передаётся. Структура, внутри которой лежит ТаблицаЗначений, на клиент не уйдёт.
Серверный вызов из формы: директивы и общие модули
В 1С нет метода с названием вроде «вызвать серверную процедуру». Сервер вызывают обычным вызовом функции, у которой стоит директива компиляции.
Директивы в модуле формы
- 🔹
&НаСервере— функция видит реквизиты формы. Платформа передаёт на сервер данные формы и возвращает их обратно. Удобно, но тяжелее. - 🔹
&НаСервереБезКонтекста— данные формы не передаются. Вызов лёгкий, но реквизиты формы внутри недоступны: всё нужное передавайте параметрами. - 🔹
&НаКлиенте— код работает на компьютере пользователя.
Если функции не нужны реквизиты формы, ставьте &НаСервереБезКонтекста. На форме с большой табличной частью разница в скорости заметна сразу.
Общий модуль с флагом «Вызов сервера»
Чтобы вызывать серверный код из клиентских общих модулей или из нескольких форм, создайте общий модуль. В его свойствах отметьте флаги Сервер и Вызов сервера. Экспортные функции такого модуля можно вызывать с клиента напрямую:
// Общий модуль РаботаСОстаткамиВызовСервера (Сервер + Вызов сервера)
Функция ОстаткиПоСкладу(Склад) Экспорт
// здесь выполняется запрос
// возвращать только передаваемые типы
КонецФункции
Флаг Вызов сервера делает функцию доступной любому клиентскому коду. Проверяйте права и входные параметры внутри функции, а не на клиенте.
Способ 1. Массив структур из результата запроса
Самый универсальный приём. Сервер обходит выборку и складывает каждую строку в Структура, а структуры — в Массив. Такой массив свободно передаётся на клиент.
&НаКлиенте
Процедура ПоказатьОстатки(Команда)
Строки = ОстаткиНаСервере(Объект.Склад);
Для Каждого Строка Из Строки Цикл
Сообщить(СтрШаблон("%1: %2", Строка.Номенклатура, Строка.Количество));
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОстаткиНаСервере(Склад)
Запрос = Новый Запрос;
// имя регистра — пример, подставьте регистр своей конфигурации
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Склад", Склад);
Результат = Новый Массив;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Строка = Новый Структура("Номенклатура, Количество");
ЗаполнитьЗначенияСвойств(Строка, Выборка);
Результат.Добавить(Строка);
КонецЦикла;
Возврат Результат;
КонецФункции
ЗаполнитьЗначенияСвойств копирует поля выборки в структуру по совпадающим именам. Поэтому псевдонимы в запросе (КАК Количество) должны совпадать с ключами структуры.
Выборка или выгрузка
На сервере результат можно обойти двумя путями:
- 📄
Запрос.Выполнить().Выбрать()— курсор. Строки читаются по одной, память расходуется экономно. Подходит, когда строк много и вы их преобразуете. - 📦
Запрос.Выполнить().Выгрузить()— вся выборка сразу вТаблицаЗначений. Удобно для сортировки, свёртки и загрузки в реквизит формы.
Для одного значения не нужен ни цикл, ни таблица:
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Количество;
КонецЕсли;
Возврат 0;
Проверить, вернул ли запрос хоть что-то, можно без обхода: Запрос.Выполнить().Пустой().
Способ 2. Таблица запроса в реквизите формы
Если данные надо показать пользователю в таблице, массив не нужен. Создайте реквизит формы с типом ТаблицаЗначений, добавьте колонки с теми же именами, что и поля запроса, и выведите его на форму.
На клиенте такой реквизит виден как ДанныеФормыКоллекция: строки можно читать и менять, а платформа сама синхронизирует их с сервером.
&НаСервере
Процедура ЗаполнитьТоварыНаСервере()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ... КАК Номенклатура, ... КАК Количество ИЗ ...";
Таблица = Запрос.Выполнить().Выгрузить();
Товары.Загрузить(Таблица);
КонецПроцедуры
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьТоварыНаСервере();
// после возврата строки Товары уже доступны на клиенте
КонецПроцедуры
Здесь нужна именно директива &НаСервере: процедура меняет реквизит формы. Вместо Товары.Загрузить(Таблица) можно написать ЗначениеВРеквизитФормы(Таблица, "Товары") — результат тот же.
Обратное преобразование — РеквизитФормыВЗначение("Товары") — возвращает на сервере обычную ТаблицаЗначений, когда с данными надо поработать запросом или методами коллекции.
Для просмотра больших списков вместо реквизита-таблицы используйте реквизит типа ДинамическийСписок с произвольным запросом. Он подгружает строки порциями по мере прокрутки и не тянет на клиент всю выборку.
Отчёт через СКД: результат в табличном документе
Когда нужен не набор строк, а готовый отчёт с группировками и итогами, запрос оформляют схемой компоновки данных. Сервер собирает отчёт в ТабличныйДокумент, а он передаётся на клиент целиком.
&НаСервереБезКонтекста
Функция СформироватьНаСервере(ПараметрыОтчета)
Схема = Отчеты.ОстаткиТоваров.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Настройки = Схема.НастройкиПоУмолчанию;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = КомпоновщикМакета.Выполнить(Схема, Настройки);
Процессор = Новый ПроцессорКомпоновкиДанных;
Процессор.Инициализировать(Макет);
Документ = Новый ТабличныйДокумент;
Вывод = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
Вывод.УстановитьДокумент(Документ);
Вывод.Вывести(Процессор);
Возврат Документ;
КонецФункции
Имя отчёта и макета здесь условные. На клиенте полученный документ показывают методом Документ.Показать() или кладут в реквизит формы типа ТабличныйДокумент.
Если результат СКД нужен не для печати, а для обработки, есть второй процессор вывода — ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений. Он выводит данные в ТаблицаЗначений, а её дальше превращают в массив структур, как в способе 1. Для этого макет компонуют с типом генератора для коллекции значений: четвёртый параметр метода Выполнить — Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений").
Временное хранилище для тяжёлых результатов
Временное хранилище — ячейка на сервере, к которой клиент обращается по адресу-строке. В неё можно положить даже ТаблицаЗначений, а на клиент передаётся только адрес.
&НаСервере
Функция ПодготовитьДанные()
Таблица = Запрос.Выполнить().Выгрузить();
Возврат ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецФункции
Вторым параметром передают УникальныйИдентификатор формы: данные живут, пока открыта форма, и удаляются после её закрытия. Без этого параметра значение удаляется вскоре после завершения серверного вызова, поэтому для данных формы идентификатор лучше передавать всегда.
Когда хранилище полезно:
- ⏳ данные готовит фоновое задание, а форма забирает их, когда задание закончится;
- 🔁 одну и ту же выборку используют несколько серверных вызовов подряд — не нужно повторять запрос;
- 📥 нужно передать файл или двоичные данные: на клиенте их получают через
ПолучитьИзВременногоХранилища(Адрес).
Не кладите в хранилище таблицу ради того, чтобы прочитать её на клиенте: на клиенте ПолучитьИзВременногоХранилища работает только с передаваемыми типами, а ТаблицаЗначений к ним не относится. Для таблиц хранилище — способ передать данные между серверными вызовами, а не на клиент.
HTTP-сервис: результат запроса в JSON для внешних систем
Если «клиент» — сайт, мобильное приложение или другая база, результат отдают через HTTP-сервис. Его создают в конфигураторе в ветке Общие → HTTP-сервисы, задают корневой URL, шаблон адреса и метод GET. Затем базу публикуют на веб-сервере (IIS или Apache) с включённой публикацией HTTP-сервисов.
Обработчик метода выполняет запрос и сериализует результат:
Функция ТоварыGET(Запрос)
Лимит = Запрос.ПараметрыЗапроса.Получить("limit");
Лимит = ?(Лимит = Неопределено, 100, Мин(Число(Лимит), 1000));
Запрос1С = Новый Запрос;
Запрос1С.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления";
Данные = Новый Массив;
Выборка = Запрос1С.Выполнить().Выбрать();
Пока Выборка.Следующий() И Данные.Количество() < Лимит Цикл
Элемент = Новый Структура;
Элемент.Вставить("id", Строка(Выборка.Ссылка.УникальныйИдентификатор()));
Элемент.Вставить("name", Выборка.Наименование);
Элемент.Вставить("sku", Выборка.Артикул);
Данные.Добавить(Элемент);
КонецЦикла;
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Данные);
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(Запись.Закрыть(), КодировкаТекста.UTF8);
Возврат Ответ;
КонецФункции
Три момента, на которых спотыкаются чаще всего:
ЗаписатьJSONне умеет сериализовать ссылки. Передавайте уникальный идентификатор строкой, как в примере, или используйтеСериализаторXDTO.- Поле
Артикулесть не в каждой конфигурации — подставьте свои реквизиты. - Ограничивайте объём ответа: параметр
limitиПЕРВЫЕв запросе защищают сервер от выгрузки всего справочника одним обращением.
Другая база 1С забирает такой ответ через HTTPСоединение и HTTPЗапрос, а тело разбирает объектом ЧтениеJSON и функцией ПрочитатьJSON.
Как передавать большие выборки без тормозов
Медленная форма обычно страдает не от запроса, а от объёма данных и числа вызовов. Что помогает:
| Приём | Когда применять | Эффект |
|---|---|---|
| Один серверный вызов вместо нескольких | Форма при открытии делает 3–5 вызовов подряд | Меньше сетевых обменов |
&НаСервереБезКонтекста | Функции не нужны реквизиты формы | Не передаются данные формы |
Только нужные поля в ВЫБРАТЬ | Запрос тянет * или лишние реквизиты | Меньше трафик и память |
Порции через ПЕРВЫЕ N | Тысячи строк для API или выгрузки | Ответ не растёт без предела |
ДинамическийСписок | Просмотр больших списков | Строки подгружаются по мере прокрутки |
| Фоновое задание + хранилище | Запрос идёт дольше нескольких секунд | Интерфейс не зависает |
Порции по ключу
Для постраничной выдачи надёжнее сортировать по уникальному полю и запоминать последнее значение, чем пропускать строки:
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 500
| Док.Ссылка КАК Ссылка,
| Док.Дата КАК Дата
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
| Док.Дата > &ПоследняяДата
|УПОРЯДОЧИТЬ ПО
| Док.Дата";
Если у нескольких документов одинаковая дата, добавьте в условие и сортировку вторую колонку, иначе строки на стыке порций потеряются.
Долгий запрос в фоне
Запрос на минуту нельзя выполнять в обычном серверном вызове: интерфейс замрёт. Запустите его фоновым заданием через ФоновыеЗадания.Выполнить("ИмяМодуля.ИмяМетода", Параметры), результат положите во временное хранилище, а форма пусть проверяет задание обработчиком ожидания. В типовых конфигурациях на БСП для этого есть готовый общий модуль ДлительныеОперации — используйте его, а не собственную обвязку.
Ошибки при возврате данных и их обработка
Ошибки здесь двух видов: платформа отказывается передать значение, или сам запрос падает на сервере.
Перехват исключения на сервере
На сервере оберните выполнение запроса в Попытка … Исключение, запишите подробности в журнал регистрации, а на клиент верните понятный результат:
&НаСервереБезКонтекста
Функция ДанныеНаСервере(Параметр)
Ответ = Новый Структура("Успех, Данные, Ошибка", Ложь, Новый Массив, "");
Попытка
// выполнение запроса и заполнение Ответ.Данные
Ответ.Успех = Истина;
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Выгрузка данных", УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(Инфо));
Ответ.Ошибка = КраткоеПредставлениеОшибки(Инфо);
КонецПопытки;
Возврат Ответ;
КонецФункции
Функции КраткоеПредставлениеОшибки и ПодробноеПредставлениеОшибки есть в глобальном контексте платформы 8.3. Пользователю показывайте краткое представление, а подробное со стеком вызовов оставляйте в журнале — там могут быть имена модулей и тексты запросов.
Не глушите исключение пустым блоком Исключение … КонецПопытки. Клиент получит пустой массив и решит, что данных нет, хотя запрос упал.
В HTTP-сервисе тот же приём: в ветке Исключение создайте HTTPСервисОтвет(500) и положите в тело JSON с полем ошибки. Внешняя система по коду ответа поймёт, что произошёл сбой, а не пришёл пустой список.