Передача данных запроса с сервера в управляемую форму и внешним системам

· 10 мин

Содержание

Результат запроса в 1С 8.3 нельзя вернуть на клиент «как есть»: объекты РезультатЗапроса, ВыборкаИзРезультатаЗапроса и ТаблицаЗначений существуют только на сервере. Рабочих способов четыре: упаковать строки в Массив из Структура, загрузить таблицу в реквизит формы, положить данные во временное хранилище или вывести их в ТабличныйДокумент. Для внешних систем результат отдают через HTTP-сервис в формате JSON.

Ниже код для каждого варианта, правила выбора и разбор ошибок, которые возникают при передаче данных между сервером и клиентом. Всё относится к платформе 8.3 и управляемым формам; в обычных формах толстого клиента 8.2 правила другие.

Почему запрос нельзя выполнить прямо на клиенте

Тонкий клиент и веб-клиент не имеют доступа к базе данных. Объект Запрос на клиенте просто недоступен: попытка написать Новый Запрос в процедуре с директивой &НаКлиенте даст ошибку ещё при проверке модуля.

Поэтому любая выборка данных выглядит так:

  1. Клиентская процедура вызывает серверную.
  2. Сервер выполняет запрос и превращает результат в значение, которое можно сериализовать.
  3. Платформа передаёт это значение по сети на клиент.

Каждый такой вызов — отдельный сетевой обмен. На локальной сети он почти незаметен, а через интернет или веб-клиент десяток вызовов подряд уже даёт ощутимую задержку.

Какие типы проходят границу клиент-сервер

Можно вернуть на клиентНельзя вернуть на клиент
Строка, Число, Дата, Булево, НеопределеноЗапрос, РезультатЗапроса
Ссылки на объекты (СправочникСсылка.*, ДокументСсылка.*)ВыборкаИзРезультатаЗапроса
Массив, Структура, Соответствие и их фиксированные версииТаблицаЗначений, ДеревоЗначений
СписокЗначенийСправочникОбъект, ДокументОбъект
ТабличныйДокумент, ХранилищеЗначенияМенеджеры (Справочники.*, РегистрыНакопления.*)

Коллекция проходит границу только тогда, когда всё её содержимое тоже передаётся. Структура, внутри которой лежит ТаблицаЗначений, на клиент не уйдёт.

Серверный вызов из формы: директивы и общие модули

В 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);
	Возврат Ответ;
КонецФункции

Три момента, на которых спотыкаются чаще всего:

  1. ЗаписатьJSON не умеет сериализовать ссылки. Передавайте уникальный идентификатор строкой, как в примере, или используйте СериализаторXDTO.
  2. Поле Артикул есть не в каждой конфигурации — подставьте свои реквизиты.
  3. Ограничивайте объём ответа: параметр limit и ПЕРВЫЕ в запросе защищают сервер от выгрузки всего справочника одним обращением.

Другая база 1С забирает такой ответ через HTTPСоединение и HTTPЗапрос, а тело разбирает объектом ЧтениеJSON и функцией ПрочитатьJSON.

Как передавать большие выборки без тормозов

Медленная форма обычно страдает не от запроса, а от объёма данных и числа вызовов. Что помогает:

ПриёмКогда применятьЭффект
Один серверный вызов вместо несколькихФорма при открытии делает 3–5 вызовов подрядМеньше сетевых обменов
&НаСервереБезКонтекстаФункции не нужны реквизиты формыНе передаются данные формы
Только нужные поля в ВЫБРАТЬЗапрос тянет * или лишние реквизитыМеньше трафик и память
Порции через ПЕРВЫЕ NТысячи строк для API или выгрузкиОтвет не растёт без предела
ДинамическийСписокПросмотр больших списковСтроки подгружаются по мере прокрутки
Фоновое задание + хранилищеЗапрос идёт дольше нескольких секундИнтерфейс не зависает

Порции по ключу

Для постраничной выдачи надёжнее сортировать по уникальному полю и запоминать последнее значение, чем пропускать строки:

Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 500
|	Док.Ссылка КАК Ссылка,
|	Док.Дата КАК Дата
|ИЗ
|	Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
|	Док.Дата > &ПоследняяДата
|УПОРЯДОЧИТЬ ПО
|	Док.Дата";

Если у нескольких документов одинаковая дата, добавьте в условие и сортировку вторую колонку, иначе строки на стыке порций потеряются.

Долгий запрос в фоне

Запрос на минуту нельзя выполнять в обычном серверном вызове: интерфейс замрёт. Запустите его фоновым заданием через ФоновыеЗадания.Выполнить("ИмяМодуля.ИмяМетода", Параметры), результат положите во временное хранилище, а форма пусть проверяет задание обработчиком ожидания. В типовых конфигурациях на БСП для этого есть готовый общий модуль ДлительныеОперации — используйте его, а не собственную обвязку.

Ошибки при возврате данных и их обработка

Ошибки здесь двух видов: платформа отказывается передать значение, или сам запрос падает на сервере.

Диагностика: выберите симптом

Перехват исключения на сервере

На сервере оберните выполнение запроса в Попытка … Исключение, запишите подробности в журнал регистрации, а на клиент верните понятный результат:

&НаСервереБезКонтекста
Функция ДанныеНаСервере(Параметр)
	Ответ = Новый Структура("Успех, Данные, Ошибка", Ложь, Новый Массив, "");
	Попытка
		// выполнение запроса и заполнение Ответ.Данные
		Ответ.Успех = Истина;
	Исключение
		Инфо = ИнформацияОбОшибке();
		ЗаписьЖурналаРегистрации("Выгрузка данных", УровеньЖурналаРегистрации.Ошибка,,,
			ПодробноеПредставлениеОшибки(Инфо));
		Ответ.Ошибка = КраткоеПредставлениеОшибки(Инфо);
	КонецПопытки;
	Возврат Ответ;
КонецФункции

Функции КраткоеПредставлениеОшибки и ПодробноеПредставлениеОшибки есть в глобальном контексте платформы 8.3. Пользователю показывайте краткое представление, а подробное со стеком вызовов оставляйте в журнале — там могут быть имена модулей и тексты запросов.

Не глушите исключение пустым блоком Исключение … КонецПопытки. Клиент получит пустой массив и решит, что данных нет, хотя запрос упал.

В HTTP-сервисе тот же приём: в ветке Исключение создайте HTTPСервисОтвет(500) и положите в тело JSON с полем ошибки. Внешняя система по коду ответа поймёт, что произошёл сбой, а не пришёл пустой список.

Частые вопросы

Можно ли передать ТаблицуЗначений на клиент без преобразования?
Нет. В тонком и веб-клиенте этот тип недоступен. Используйте реквизит формы типа «Таблица значений» или массив структур.
Что быстрее: &НаСервере или &НаСервереБезКонтекста?
&НаСервереБезКонтекста, потому что не передаёт данные формы туда и обратно. На простой форме разница мала, на форме с большими табличными частями — заметна.
Передаётся ли на клиент Соответствие?
Да, если ключи и значения тоже передаваемых типов. Для ключей лучше брать строки или ссылки.
Как получить на клиенте представление ссылки без лишнего вызова сервера?
Добавьте в запрос поле ПРЕДСТАВЛЕНИЕ(Ссылка) и верните его строкой вместе со ссылкой. Тогда клиенту не придётся отдельно обращаться к серверу за наименованием.
Почему после серверного вызова пропали несохранённые изменения в форме?
Такого не бывает с &НаСервере: данные формы возвращаются на клиент вместе с изменениями. Если изменения пропали, скорее всего, серверная процедура перечитала объект из базы или перезаполнила реквизит запросом.