Данные запроса на клиенте: почему выборка не проходит и чем её заменить
Содержание
Выборку из результата запроса передать с сервера на клиент нельзя: объект ВыборкаИзРезультатаЗапроса живёт только на сервере и не сериализуется. На клиент уходят данные, а не сама выборка. Обойдите выборку на сервере, сложите нужные поля в Массив из Структура и верните его из серверной функции. Второй путь — записать результат в реквизит формы типа «Таблица значений» через ЗначениеВРеквизитФормы.
Это правило платформы 1С 8.3 для управляемого приложения: тонкого клиента , веб-клиента и мобильного клиента. В толстом клиенте обычного приложения запрос можно выполнить прямо на клиенте, но для управляемых форм ограничение действует всегда, независимо от конфигурации.
Как устроен обмен между клиентом и сервером
В клиент-серверном варианте код формы выполняется в двух местах. Процедуры с директивой &НаКлиенте работают на компьютере пользователя. Процедуры с &НаСервере и &НаСервереБезКонтекста — в рабочем процессе сервера 1С.
Каждый переход с клиента на сервер — это сетевой вызов. Параметры и возвращаемое значение сериализуются, передаются по сети и восстанавливаются на другой стороне. Отсюда два следствия:
- 📦 передать можно только значение, тип которого существует на клиенте и умеет сериализоваться;
- 🕒 каждый вызов сервера стоит времени, особенно через интернет и в веб-клиенте;
- 🔁 между вызовами сервер не хранит ваши переменные: открытая выборка не «доживёт» до следующего обращения.
Какие типы проходят, а какие нет
| Тип | Передаётся на клиент | Комментарий |
|---|---|---|
| Строка, Число, Дата, Булево, Неопределено | ✅ да | базовые значения |
Ссылки (СправочникСсылка, ДокументСсылка и т.п.) | ✅ да | реквизиты через точку на клиенте не прочитать |
Массив, Структура, Соответствие | ✅ да | если все вложенные значения тоже передаваемые |
ФиксированныйМассив, ФиксированнаяСтруктура | ✅ да | удобно для данных «только на чтение» |
СписокЗначений | ✅ да | подходит для простых списков выбора |
ДвоичныеДанные | ✅ да | файлы, картинки |
ВыборкаИзРезультатаЗапроса, РезультатЗапроса, Запрос | ❌ нет | только сервер |
ТаблицаЗначений, ДеревоЗначений | ❌ нет | на тонком клиенте типов нет; для формы есть реквизиты |
СправочникОбъект, ДокументОбъект | ❌ нет | на клиент идут через данные формы |
Если серверная функция вернёт непередаваемый тип, платформа выдаст ошибку в момент возврата значения. Компилятор её не ловит: синтаксический контроль пройдёт, а упадёт уже при работе.
Запрос и выборка: что выполняется на сервере
Запрос всегда выполняется на сервере. Дальше результат можно получить двумя способами, и от выбора зависит расход памяти.
| Метод | Что даёт | Когда брать |
|---|---|---|
Результат.Выбрать() | курсор, читает строки по одной | обход, упаковка, расчёт итогов |
Результат.Выгрузить() | ТаблицаЗначений целиком в памяти | нужен поиск, сортировка, свёртка |
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) | обход по итогам | запросы с ИТОГИ ПО |
Выбрать() экономнее: строки подтягиваются по мере обхода. Выгрузить() сразу строит таблицу, и на сотнях тысяч строк это заметно по памяти рабочего процесса.
Базовая схема передачи данных выборки на клиент выглядит так:
&НаКлиенте
Процедура ЗаполнитьОстатки(Команда)
Строки = ПолучитьОстаткиНаСервере(Склад);
Для Каждого Строка Из Строки Цикл
// Строка.Номенклатура — ссылка, Строка.Наименование — строка
Сообщить(Строка.Наименование + ": " + Формат(Строка.Количество, "ЧГ=0"));
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьОстаткиНаСервере(Склад)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| ПРЕДСТАВЛЕНИЕ(Остатки.Номенклатура) КАК Наименование,
| Остатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Склад", Склад);
Результат = Новый Массив;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Строка = Новый Структура("Номенклатура, Наименование, Количество");
ЗаполнитьЗначенияСвойств(Строка, Выборка);
Результат.Добавить(Строка);
КонецЦикла;
Возврат Результат;
КонецФункции
Имя регистра в примере условное: подставьте регистр вашей конфигурации. Главное здесь — три приёма:
- Функция помечена
&НаСервереБезКонтекста, поэтому на сервер не уходят данные формы. - Представление ссылки берётся в запросе через
ПРЕДСТАВЛЕНИЕ(). На клиенте прочитатьНоменклатура.Наименованиенельзя, а лишний вызов сервера ради каждого имени — дорого. ЗаполнитьЗначенияСвойствкопирует поля выборки в структуру по совпадающим именам. Псевдонимы в запросе должны совпадать с ключами структуры.
Не берите в структуру лишние поля. Каждое поле умножается на число строк, а объём передачи растёт пропорционально.
Таблица значений: как довезти её до формы
Сама ТаблицаЗначений на тонкий клиент не попадает. Зато у формы может быть реквизит этого типа. На клиенте он виден как коллекция ДанныеФормыКоллекция, и его можно вывести в элемент «Таблица» без всякого кода на клиенте.
Способ 1. Реквизит формы
- В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например
ТаблицаОстатков, с типом «Таблица значений». - Добавьте ему колонки с теми же именами и типами, что у полей запроса.
- Перетащите реквизит в дерево элементов: появится таблица на форме.
- Заполните его в серверной процедуре с контекстом:
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()
Запрос = Новый Запрос(ТекстЗапросаОстатков());
Запрос.УстановитьПараметр("Склад", Склад);
Таблица = Запрос.Выполнить().Выгрузить();
ЗначениеВРеквизитФормы(Таблица, "ТаблицаОстатков");
КонецПроцедуры
Обратное преобразование на сервере — РеквизитФормыВЗначение("ТаблицаОстатков"). Получите обычную ТаблицаЗначений и сможете её искать, сворачивать, сортировать.
Колонки, которых нет в реквизите, при загрузке просто не попадут в форму. Если колонка в реквизите есть, а в запросе нет — она останется пустой. Ошибки в обоих случаях не будет, поэтому проверяйте имена.
Способ 2. Массив структур
Если таблица нужна не для показа, а для обработки на клиенте, превратите её в массив структур. Универсальная функция для серверного общего модуля:
Функция ТаблицуВМассивСтруктур(Таблица) Экспорт
Имена = Новый Массив;
Для Каждого Колонка Из Таблица.Колонки Цикл
Имена.Добавить(Колонка.Имя);
КонецЦикла;
СписокКолонок = СтрСоединить(Имена, ",");
Результат = Новый Массив;
Для Каждого СтрокаТаблицы Из Таблица Цикл
Элемент = Новый Структура(СписокКолонок);
ЗаполнитьЗначенияСвойств(Элемент, СтрокаТаблицы);
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
В конфигурациях на Библиотеке стандартных подсистем есть готовая функция ОбщегоНазначения.ТаблицаЗначенийВМассив. Если БСП подключена, берите её, а не пишите свою.
Что выбрать
| Задача | Способ |
|---|---|
| Показать строки пользователю, дать редактировать | реквизит формы «Таблица значений» |
| Обойти строки на клиенте, посчитать, сравнить | массив структур |
| Передать данные только для чтения | ФиксированныйМассив из ФиксированнаяСтруктура |
| Список значений для выбора (до сотни позиций) | СписокЗначений |
| Большой справочник с прокруткой | динамический список, без ручной передачи |
Большие объёмы: читать порциями
Десятки тысяч строк одним вызовом — плохая идея. Ответ сервера весит мегабайты, форма подвисает, а через медленный канал вызов может оборваться по таймауту.
Держать выборку открытой между вызовами нельзя: сервер не сохраняет состояние вашей функции. Поэтому порции читают по ключу — запоминают последнюю полученную ссылку и в следующем запросе берут строки после неё.
&НаСервереБезКонтекста
Функция ПолучитьПорцию(ПоследняяСсылка, РазмерПорции)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 500
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка > &ПоследняяСсылка
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Ссылка";
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ПЕРВЫЕ 500",
"ПЕРВЫЕ " + Формат(РазмерПорции, "ЧГ=0"));
Запрос.УстановитьПараметр("ПоследняяСсылка", ПоследняяСсылка);
Результат = Новый Массив;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Элемент = Новый Структура("Ссылка, Наименование");
ЗаполнитьЗначенияСвойств(Элемент, Выборка);
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
На клиенте цикл выглядит так:
ПоследняяСсылка = ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка");
Пока Истина Цикл
Порция = ПолучитьПорцию(ПоследняяСсылка, 500);
Если Порция.Количество() = 0 Тогда
Прервать;
КонецЕсли;
// обработка порции
ПоследняяСсылка = Порция[Порция.Количество() - 1].Ссылка;
КонецЦикла;
Почему не ПЕРВЫЕ N со смещением: в языке запросов 1С нет OFFSET, а пропуск строк через вложенные запросы на больших таблицах работает всё медленнее с каждой страницей. Отбор Ссылка > &ПоследняяСсылка с сортировкой по ссылке читает каждую порцию за одинаковое время.
Сортировка в запросе и ключ продолжения должны совпадать. Если упорядочить по наименованию, а продолжать по ссылке, строки будут теряться и дублироваться.
Когда порции не нужны вообще
Если данные просто показываются списком, используйте динамический список. Он сам подгружает с сервера только видимые строки и следующую страницу при прокрутке. Ручная передача тут лишняя.
Как ускорить передачу и сколько это даёт
Скорость определяют число вызовов сервера и объём данных в каждом. Оба показателя надо сокращать.
- 🔢 Один вызов вместо цикла. Классическая ошибка — на клиенте перебирать строки и для каждой звать серверную функцию. 300 строк — 300 сетевых обращений. Соберите всё в один запрос и верните одним массивом.
- 🧳 Без контекста.
&НаСерверегоняет на сервер и обратно все данные формы, включая табличные части. Если функции нужен только склад и дата — передайте их параметрами и пометьте&НаСервереБезКонтекста. - ✂️ Только нужные поля. Не пишите
ВЫБРАТЬ *ради двух колонок. Структура из пяти полей на 10 000 строк — это 50 000 значений в пакете. - 🏷️ Представления в запросе.
ПРЕДСТАВЛЕНИЕ()и нужные реквизиты ссылок получайте сразу в запросе, а не дозапросами с клиента. - 🧮 Агрегация на сервере. Если клиенту нужна сумма или количество — посчитайте
СУММА()в запросе и верните одно число. - 📌 Фиксированные коллекции. Для справочных данных, которые форма только читает,
ФиксированнаяСтруктуразащищает от случайной правки и хорошо подходит для кеширования.
Проверить эффект удобно замером производительности. В конфигураторе запустите отладку, включите Отладка → Замер производительности, выполните действие в форме и остановите замер. В результатах видно, сколько раз вызывалась серверная функция и сколько времени заняли строки кода.
Временное хранилище вместо прямого возврата
Для тяжёлых данных и длительных операций есть временное хранилище. На сервере вы кладёте значение и получаете адрес — обычную строку:
Адрес = ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
Строку адреса легко вернуть на клиент. Сами данные клиент заберёт ПолучитьИзВременногоХранилища(Адрес) тогда, когда они реально понадобятся. Значение при этом тоже должно быть передаваемым на клиент: массив, структура, ДвоичныеДанные.
Этот приём закрывает три сценария:
- 📄 Передача файла: на сервере формируете
ДвоичныеДанные, на клиенте сохраняете. - ⏳ Фоновое задание: тяжёлый запрос считается в фоне, результат ложится в хранилище, форма опрашивает готовность и не висит.
- 🗂️ Повторное использование: данные, подготовленные при открытии формы, хранятся по адресу и не пересчитываются при каждом обращении.
Второй параметр УникальныйИдентификатор формы привязывает данные к её жизни: форма закрылась — хранилище освободится. Без него значение живёт до конца сеанса или до явного удаления через УдалитьИзВременногоХранилища.
Ошибки при передаче данных и их обработка
Ошибки в этой теме бывают двух видов: платформа не может передать значение, или серверный код упал при выполнении запроса.
Перехват исключений
Исключение, выброшенное на сервере, долетает до клиента. Его можно поймать конструкцией Попытка … Исключение вокруг вызова серверной функции:
&НаКлиенте
Процедура Обновить(Команда)
Попытка
Строки = ПолучитьОстаткиНаСервере(Склад);
Исключение
Сообщить("Не удалось получить остатки: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат;
КонецПопытки;
// работа со строками
КонецПроцедуры
Пользователю показывайте краткое представление. Подробное — ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) — пишите в журнал регистрации на сервере через ЗаписьЖурналаРегистрации. Там стек вызовов, и по нему разработчик найдёт строку с ошибкой.
На сервере проверяйте граничные случаи до обхода:
- ❓
Результат.Пустой()— если строк нет, сразу возвращайте пустой массив, а неНеопределено. Клиенту не придётся проверять тип. - 🚫 Не глушите исключение пустым блоком
Исключение … КонецПопытки. Ошибка исчезнет, а форма покажет неполные данные без предупреждения. - 🔐 Нехватка прав на чтение таблицы — тоже исключение на сервере. Сообщение о нём лучше перехватить и показать понятным текстом.
Частые вопросы
Можно ли вернуть на клиент РезультатЗапроса и вызвать Выбрать() там?
РезультатЗапроса так же, как выборка, существует только на сервере. Обход делается на сервере, на клиент идут готовые данные.Работает ли это ограничение в файловой базе?
Сколько строк можно безопасно вернуть за один вызов?
Чем Соответствие лучше массива структур?
Можно ли передать на клиент дерево значений?
ЗначениеВРеквизитФормы. На клиенте оно будет коллекцией данных формы.