Данные запроса на клиенте: почему выборка не проходит и чем её заменить

· 10 мин

Содержание

Выборку из результата запроса передать с сервера на клиент нельзя: объект ВыборкаИзРезультатаЗапроса живёт только на сервере и не сериализуется. На клиент уходят данные, а не сама выборка. Обойдите выборку на сервере, сложите нужные поля в Массив из Структура и верните его из серверной функции. Второй путь — записать результат в реквизит формы типа «Таблица значений» через ЗначениеВРеквизитФормы.

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

Как устроен обмен между клиентом и сервером

В клиент-серверном варианте код формы выполняется в двух местах. Процедуры с директивой &НаКлиенте работают на компьютере пользователя. Процедуры с &НаСервере и &НаСервереБезКонтекста — в рабочем процессе сервера 1С.

Каждый переход с клиента на сервер — это сетевой вызов. Параметры и возвращаемое значение сериализуются, передаются по сети и восстанавливаются на другой стороне. Отсюда два следствия:

  • 📦 передать можно только значение, тип которого существует на клиенте и умеет сериализоваться;
  • 🕒 каждый вызов сервера стоит времени, особенно через интернет и в веб-клиенте;
  • 🔁 между вызовами сервер не хранит ваши переменные: открытая выборка не «доживёт» до следующего обращения.

Какие типы проходят, а какие нет

ТипПередаётся на клиентКомментарий
Строка, Число, Дата, Булево, Неопределено✅ дабазовые значения
Ссылки (СправочникСсылка, ДокументСсылка и т.п.)✅ дареквизиты через точку на клиенте не прочитать
Массив, Структура, Соответствие✅ даесли все вложенные значения тоже передаваемые
ФиксированныйМассив, ФиксированнаяСтруктура✅ даудобно для данных «только на чтение»
СписокЗначений✅ даподходит для простых списков выбора
ДвоичныеДанные✅ дафайлы, картинки
ВыборкаИзРезультатаЗапроса, РезультатЗапроса, Запрос❌ неттолько сервер
ТаблицаЗначений, ДеревоЗначений❌ нетна тонком клиенте типов нет; для формы есть реквизиты
СправочникОбъект, ДокументОбъект❌ нетна клиент идут через данные формы

Если серверная функция вернёт непередаваемый тип, платформа выдаст ошибку в момент возврата значения. Компилятор её не ловит: синтаксический контроль пройдёт, а упадёт уже при работе.

Запрос и выборка: что выполняется на сервере

Запрос всегда выполняется на сервере. Дальше результат можно получить двумя способами, и от выбора зависит расход памяти.

МетодЧто даётКогда брать
Результат.Выбрать()курсор, читает строки по однойобход, упаковка, расчёт итогов
Результат.Выгрузить()ТаблицаЗначений целиком в памятинужен поиск, сортировка, свёртка
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам)обход по итогамзапросы с ИТОГИ ПО

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

Базовая схема передачи данных выборки на клиент выглядит так:

&НаКлиенте
Процедура ЗаполнитьОстатки(Команда)
    Строки = ПолучитьОстаткиНаСервере(Склад);
    Для Каждого Строка Из Строки Цикл
        // Строка.Номенклатура — ссылка, Строка.Наименование — строка
        Сообщить(Строка.Наименование + ": " + Формат(Строка.Количество, "ЧГ=0"));
    КонецЦикла;
КонецПроцедуры

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

    Результат = Новый Массив;
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Строка = Новый Структура("Номенклатура, Наименование, Количество");
        ЗаполнитьЗначенияСвойств(Строка, Выборка);
        Результат.Добавить(Строка);
    КонецЦикла;

    Возврат Результат;
КонецФункции

Имя регистра в примере условное: подставьте регистр вашей конфигурации. Главное здесь — три приёма:

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

Не берите в структуру лишние поля. Каждое поле умножается на число строк, а объём передачи растёт пропорционально.

Таблица значений: как довезти её до формы

Сама ТаблицаЗначений на тонкий клиент не попадает. Зато у формы может быть реквизит этого типа. На клиенте он виден как коллекция ДанныеФормыКоллекция, и его можно вывести в элемент «Таблица» без всякого кода на клиенте.

Способ 1. Реквизит формы

  1. В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например ТаблицаОстатков, с типом «Таблица значений».
  2. Добавьте ему колонки с теми же именами и типами, что у полей запроса.
  3. Перетащите реквизит в дерево элементов: появится таблица на форме.
  4. Заполните его в серверной процедуре с контекстом:
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()
    Запрос = Новый Запрос(ТекстЗапросаОстатков());
    Запрос.УстановитьПараметр("Склад", Склад);
    Таблица = Запрос.Выполнить().Выгрузить();
    ЗначениеВРеквизитФормы(Таблица, "ТаблицаОстатков");
КонецПроцедуры

Обратное преобразование на сервере — РеквизитФормыВЗначение("ТаблицаОстатков"). Получите обычную ТаблицаЗначений и сможете её искать, сворачивать, сортировать.

Колонки, которых нет в реквизите, при загрузке просто не попадут в форму. Если колонка в реквизите есть, а в запросе нет — она останется пустой. Ошибки в обоих случаях не будет, поэтому проверяйте имена.

Способ 2. Массив структур

Если таблица нужна не для показа, а для обработки на клиенте, превратите её в массив структур. Универсальная функция для серверного общего модуля:

Функция ТаблицуВМассивСтруктур(Таблица) Экспорт
    Имена = Новый Массив;
    Для Каждого Колонка Из Таблица.Колонки Цикл
        Имена.Добавить(Колонка.Имя);
    КонецЦикла;
    СписокКолонок = СтрСоединить(Имена, ",");

    Результат = Новый Массив;
    Для Каждого СтрокаТаблицы Из Таблица Цикл
        Элемент = Новый Структура(СписокКолонок);
        ЗаполнитьЗначенияСвойств(Элемент, СтрокаТаблицы);
        Результат.Добавить(Элемент);
    КонецЦикла;
    Возврат Результат;
КонецФункции

В конфигурациях на Библиотеке стандартных подсистем есть готовая функция ОбщегоНазначения.ТаблицаЗначенийВМассив. Если БСП подключена, берите её, а не пишите свою.

Что выбрать

ЗадачаСпособ
Показать строки пользователю, дать редактироватьреквизит формы «Таблица значений»
Обойти строки на клиенте, посчитать, сравнитьмассив структур
Передать данные только для чтенияФиксированныйМассив из ФиксированнаяСтруктура
Список значений для выбора (до сотни позиций)СписокЗначений
Большой справочник с прокруткойдинамический список, без ручной передачи

Большие объёмы: читать порциями

Десятки тысяч строк одним вызовом — плохая идея. Ответ сервера весит мегабайты, форма подвисает, а через медленный канал вызов может оборваться по таймауту.

Держать выборку открытой между вызовами нельзя: сервер не сохраняет состояние вашей функции. Поэтому порции читают по ключу — запоминают последнюю полученную ссылку и в следующем запросе берут строки после неё.

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

    Результат = Новый Массив;
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Элемент = Новый Структура("Ссылка, Наименование");
        ЗаполнитьЗначенияСвойств(Элемент, Выборка);
        Результат.Добавить(Элемент);
    КонецЦикла;
    Возврат Результат;
КонецФункции

На клиенте цикл выглядит так:

ПоследняяСсылка = ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка");
Пока Истина Цикл
    Порция = ПолучитьПорцию(ПоследняяСсылка, 500);
    Если Порция.Количество() = 0 Тогда
        Прервать;
    КонецЕсли;
    // обработка порции
    ПоследняяСсылка = Порция[Порция.Количество() - 1].Ссылка;
КонецЦикла;

Почему не ПЕРВЫЕ N со смещением: в языке запросов 1С нет OFFSET, а пропуск строк через вложенные запросы на больших таблицах работает всё медленнее с каждой страницей. Отбор Ссылка > &ПоследняяСсылка с сортировкой по ссылке читает каждую порцию за одинаковое время.

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

Когда порции не нужны вообще

Если данные просто показываются списком, используйте динамический список. Он сам подгружает с сервера только видимые строки и следующую страницу при прокрутке. Ручная передача тут лишняя.

Как ускорить передачу и сколько это даёт

Скорость определяют число вызовов сервера и объём данных в каждом. Оба показателя надо сокращать.

  • 🔢 Один вызов вместо цикла. Классическая ошибка — на клиенте перебирать строки и для каждой звать серверную функцию. 300 строк — 300 сетевых обращений. Соберите всё в один запрос и верните одним массивом.
  • 🧳 Без контекста. &НаСервере гоняет на сервер и обратно все данные формы, включая табличные части. Если функции нужен только склад и дата — передайте их параметрами и пометьте &НаСервереБезКонтекста.
  • ✂️ Только нужные поля. Не пишите ВЫБРАТЬ * ради двух колонок. Структура из пяти полей на 10 000 строк — это 50 000 значений в пакете.
  • 🏷️ Представления в запросе. ПРЕДСТАВЛЕНИЕ() и нужные реквизиты ссылок получайте сразу в запросе, а не дозапросами с клиента.
  • 🧮 Агрегация на сервере. Если клиенту нужна сумма или количество — посчитайте СУММА() в запросе и верните одно число.
  • 📌 Фиксированные коллекции. Для справочных данных, которые форма только читает, ФиксированнаяСтруктура защищает от случайной правки и хорошо подходит для кеширования.

Проверить эффект удобно замером производительности. В конфигураторе запустите отладку, включите Отладка → Замер производительности, выполните действие в форме и остановите замер. В результатах видно, сколько раз вызывалась серверная функция и сколько времени заняли строки кода.

Временное хранилище вместо прямого возврата

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

Адрес = ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);

Строку адреса легко вернуть на клиент. Сами данные клиент заберёт ПолучитьИзВременногоХранилища(Адрес) тогда, когда они реально понадобятся. Значение при этом тоже должно быть передаваемым на клиент: массив, структура, ДвоичныеДанные.

Этот приём закрывает три сценария:

  1. 📄 Передача файла: на сервере формируете ДвоичныеДанные, на клиенте сохраняете.
  2. ⏳ Фоновое задание: тяжёлый запрос считается в фоне, результат ложится в хранилище, форма опрашивает готовность и не висит.
  3. 🗂️ Повторное использование: данные, подготовленные при открытии формы, хранятся по адресу и не пересчитываются при каждом обращении.

Второй параметр УникальныйИдентификатор формы привязывает данные к её жизни: форма закрылась — хранилище освободится. Без него значение живёт до конца сеанса или до явного удаления через УдалитьИзВременногоХранилища.

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

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

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

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

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

&НаКлиенте
Процедура Обновить(Команда)
    Попытка
        Строки = ПолучитьОстаткиНаСервере(Склад);
    Исключение
        Сообщить("Не удалось получить остатки: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
        Возврат;
    КонецПопытки;
    // работа со строками
КонецПроцедуры

Пользователю показывайте краткое представление. Подробное — ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) — пишите в журнал регистрации на сервере через ЗаписьЖурналаРегистрации. Там стек вызовов, и по нему разработчик найдёт строку с ошибкой.

На сервере проверяйте граничные случаи до обхода:

  • ❓ Результат.Пустой() — если строк нет, сразу возвращайте пустой массив, а не Неопределено. Клиенту не придётся проверять тип.
  • 🚫 Не глушите исключение пустым блоком Исключение … КонецПопытки. Ошибка исчезнет, а форма покажет неполные данные без предупреждения.
  • 🔐 Нехватка прав на чтение таблицы — тоже исключение на сервере. Сообщение о нём лучше перехватить и показать понятным текстом.

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

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