ТаблицаЗначений недоступна на тонком клиенте: какие структуры данных брать вместо неё

· 10 мин

Содержание

На тонком и веб-клиенте 1С 8.3 таблицу значений заменяют одним из четырёх способов. Нужна простая выборка строк для логики на клиенте — возвращайте с сервера массив структур. Данные показываются пользователю в таблице на форме — держите их в реквизите формы типа ТаблицаЗначений: на клиенте он превращается в ДанныеФормыКоллекция со строками, поиском и добавлением. Иерархия — реквизит типа ДеревоЗначений. Большой объём, который клиенту не нужен целиком, — оставьте на сервере во временном хранилище и передавайте только адрес.

Табличный документ тоже работает на клиенте, но как буфер для ввода и вставки из Excel, а не как контейнер для расчётов.

Почему ТаблицаЗначений не работает в клиентском коде

Тип ТаблицаЗначений доступен на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет. Отсюда три типовых ситуации:

  • ❌ Новый ТаблицаЗначений внутри процедуры с директивой &НаКлиенте — ошибка при выполнении.
  • ❌ Серверная функция возвращает таблицу значений в клиентскую процедуру — платформа не может передать значение типа, которого нет на клиенте.
  • ❌ Код написан под обычное приложение и толстый клиент, а базу перевели на управляемые формы. Раньше всё работало, теперь падает.

Проверить, доступен ли тип в контексте, можно в синтакс-помощнике: у каждого типа и метода внизу описания есть строка «Доступность». Для ТаблицаЗначений там нет тонкого клиента и веб-клиента. Для Массив, Структура, Соответствие, ТабличныйДокумент — есть.

Ограничение касается не только самой таблицы. На клиенте недоступны Запрос, ДеревоЗначений, объекты справочников и документов. Ссылку на клиент передать можно, а прочитать её реквизит через точку (Товар.Артикул) — уже нет: за реквизитом придётся сходить на сервер.

Из этого следует главное правило: расчёт по таблице выполняйте на сервере, на клиент отдавайте только результат. Альтернативы ниже — про то, в какой обёртке этот результат удобно держать.

Массив структур: лёгкая замена таблицы значений на клиенте

Самый частый вариант. Каждая строка — Структура с нужными полями, все строки — в Массив. Оба типа доступны в любом контексте и сериализуются при серверном вызове без проблем.

Как сформировать на сервере

&НаСервереБезКонтекста
Функция ОстаткиТоваров(Склад)

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

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

    Возврат Результат;

КонецФункции

Имя регистра и его измерения у вас будут свои — пример показывает схему. Артикул выбран в самом запросе, чтобы клиенту не пришлось обращаться к реквизиту ссылки.

Универсальное преобразование готовой таблицы

Если таблица значений уже получена (из запроса, из внешней обработки, из чужого кода), её переводят в массив структур по колонкам:

&НаСервереБезКонтекста
Функция ТаблицаВМассив(Таблица)

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

    Возврат Результат;

КонецФункции

В конфигурациях на БСП такая функция уже есть: ОбщегоНазначения.ТаблицаЗначенийВМассив. Свою писать не нужно.

Что теряется по сравнению с таблицей

  • 🔍 Нет НайтиСтроки и Найти. Поиск — циклом или через Соответствие.
  • 📊 Нет Свернуть, Итог, Сортировать. Группировку и сортировку делайте в запросе.
  • 🧩 Нет описания колонок. Набор полей в каждой структуре держится только на дисциплине кода.

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

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

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

Реквизит формы: коллекция строк, которая живёт на клиенте

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

На сервере с ним работают как с обычной таблицей, а на клиенте он доступен как ДанныеФормыКоллекция. У коллекции есть почти всё, что нужно в интерфейсе:

Действие на клиентеМетод ДанныеФормыКоллекция
Добавить строкуДобавить(), Вставить(Индекс)
Удалить строкуУдалить(Строка), Очистить()
Найти строки по отборуНайтиСтроки(Новый Структура("Товар", Товар))
Число строкКоличество()
Строка по идентификаторуНайтиПоИдентификатору(Ид)
ПерестановкаСдвинуть(Строка, Смещение)

Перевод между полноценной таблицей и реквизитом выполняется только на сервере: РеквизитФормыВЗначение("Товары") и ЗначениеВРеквизитФормы(Таблица, "Товары"). Для табличной части объекта на форме — Объект.Товары.Выгрузить() и Загрузить() в серверной процедуре.

&НаСервере
Процедура ЗаполнитьТоварыНаСервере()
    Таблица = РеквизитФормыВЗначение("Товары");
    Таблица.Очистить();
    // ... заполнение запросом, свёртка, сортировка
    Таблица.Сортировать("Номенклатура");
    ЗначениеВРеквизитФормы(Таблица, "Товары");
КонецПроцедуры

Реквизит формы — самый удобный и самый «тяжёлый» вариант. При каждом вызове процедуры с директивой &НаСервере платформа передаёт на сервер данные формы и возвращает изменения. Таблица на 20 000 строк в реквизите превращает каждый такой вызов в заметную паузу.

Дерево значений на клиенте: иерархия без серверного объекта

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

Реквизит формы типа ДеревоЗначений

На клиенте он становится ДанныеФормыДерево. Обход идёт через ПолучитьЭлементы(): у корня — коллекция верхнего уровня, у каждого элемента — его подчинённые. Родителя возвращает ПолучитьРодителя().

&НаКлиенте
Процедура СнятьОтметкиРекурсивно(Элементы)
    Для Каждого Элемент Из Элементы Цикл
        Элемент.Пометка = Ложь;
        СнятьОтметкиРекурсивно(Элемент.ПолучитьЭлементы());
    КонецЦикла;
КонецПроцедуры

// Вызов: СнятьОтметкиРекурсивно(Дерево.ПолучитьЭлементы());

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

Вложенные массивы структур

Если дерево не показывается на форме, хватит структуры с полем Строки, где лежит массив таких же структур. Сформировать её на сервере можно рекурсивным обходом ДеревоЗначений или выборкой запроса с ИТОГИ ПО ... ИЕРАРХИЯ и Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией).

Глубокая вложенность в массиве структур неудобна для поиска. Если нужен быстрый доступ к узлу по ссылке, держите рядом плоское Соответствие «ссылка → структура узла».

Табличный документ как буфер данных на клиенте

ТабличныйДокумент доступен в тонком и веб-клиенте. Хранить в нём рабочие данные для расчётов неудобно: значения лежат в ячейках, обращение идёт по координатам, типы приходится проверять самим. Но у него есть своя ниша.

Где табличный документ на форме действительно выручает:

  • 📥 Вставка из Excel. Пользователь копирует диапазон из таблицы и вставляет в поле табличного документа на форме. Колонки и строки сохраняются.
  • 📝 Свободный ввод большого количества строк без открытия формы каждой строки.
  • 👁 Предпросмотр результата загрузки до записи в базу.

Чтение ячеек на клиенте — через Область с адресом R1C1. Размеры заполненной части дают ВысотаТаблицы и ШиринаТаблицы.

&НаКлиенте
Функция ПрочитатьБуфер(ТабДок)

    Результат = Новый Массив;
    Для НомерСтроки = 2 По ТабДок.ВысотаТаблицы Цикл
        Адрес = "R" + Формат(НомерСтроки, "ЧГ=0");
        Артикул    = СокрЛП(ТабДок.Область(Адрес + "C1").Текст);
        Количество = СокрЛП(ТабДок.Область(Адрес + "C2").Текст);
        Если ПустаяСтрока(Артикул) Тогда
            Продолжить;
        КонецЕсли;
        Результат.Добавить(Новый Структура("Артикул, Количество", Артикул, Количество));
    КонецЦикла;

    Возврат Результат;

КонецФункции

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

Не передавайте сам табличный документ на сервер ради пары колонок. Он передаётся целиком, с оформлением и параметрами ячеек. Соберите из него компактный массив структур на клиенте и отправьте одним вызовом.

Временное хранилище: данные остаются на сервере

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

&НаСервере
Процедура ПодготовитьДанныеНаСервере()
    Таблица = ПолучитьБольшуюТаблицу(); // ваша серверная функция
    АдресДанных = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ИтогПоТовару(Адрес, Товар)
    Таблица = ПолучитьИзВременногоХранилища(Адрес);
    Строки = Таблица.НайтиСтроки(Новый Структура("Номенклатура", Товар));
    Сумма = 0;
    Для Каждого Строка Из Строки Цикл
        Сумма = Сумма + Строка.Сумма;
    КонецЦикла;
    Возврат Сумма;
КонецФункции

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

Подходит, когда таблица большая, на клиенте нужны точечные ответы по ней, а заново выполнять запрос дорого.

Сравнение способов: скорость и сценарии

Точных миллисекунд здесь не будет: они зависят от числа строк, колонок, канала до сервера и типа клиента. Порядок затрат при этом стабилен. Почти всегда больше всего времени уходит на серверные вызовы и объём передаваемых данных, а не на работу с коллекцией в памяти клиента.

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

Как замерить на своей базе. Для одного участка кода хватит пары строк:

Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
// проверяемый код
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Заняло, мс: " + Формат(Длительность, "ЧГ=0"));

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

Как ускорить клиентский код: практические приёмы

Приёмы, которые дают выигрыш чаще всего. Первые три обычно важнее выбора коллекции.

  1. Директива &НаСервереБезКонтекста вместо &НаСервере везде, где процедуре не нужны реквизиты формы. Данные формы тогда не передаются вообще. Параметры и результат передайте явно.
  2. Один серверный вызов вместо десяти. Не дёргайте сервер в цикле по строкам ради одного реквизита. Соберите все ссылки в массив, отправьте один раз, получите Соответствие «ссылка → данные».
  3. Выбирайте поля в запросе. Артикул, единица, цена — сразу колонками выборки. Тогда клиенту не нужно обращаться к реквизитам ссылок.
  4. Не держите в реквизите формы то, что не видит пользователь. Служебную таблицу на 50 000 строк — во временное хранилище или в серверную переменную модуля объекта, а не в реквизит.
  5. Свёртка и сортировка — в запросе. СГРУППИРОВАТЬ ПО и УПОРЯДОЧИТЬ ПО на стороне СУБД выполняются быстрее, чем то же самое кодом.
  6. Для длинных списков — динамический список. Если нужно показать журнал или большой справочник, реквизит ДинамическийСписок читает данные порциями по мере прокрутки, а не грузит всё в форму.
  7. Длительные расчёты — в фоне. Механизм длительных операций БСП запускает обработку фоновым заданием, а результат возвращает через временное хранилище. Клиент при этом не висит.
Что нужно сделать с данными на клиенте?

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

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