Как исправить ошибку подбора и передачи таблицы значений на тонком клиенте в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При переходе с толстого клиента на тонкий клиент (управляемое приложение) разработчики часто сталкиваются с неожиданным поведением системы и ошибками, которых раньше не возникало. Две самые популярные проблемы в этой области — это попытка обратиться к реквизитам ссылочного типа "через точку" непосредственно на клиенте и попытка передать ТаблицуЗначений напрямую с сервера на клиент. Давайте подробно разберем, почему возникают эти ошибки и как правильно переписать код, чтобы он стабильно работал в тонком клиенте.

Выясняем причины ошибок: почему это работало в толстом клиенте и ломается в тонком?

Для начала проанализируем ситуацию, описанную в первом сообщении. Код автора выглядел следующим образом:


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

На тонком клиенте этот код сразу вызывает ошибку "Поле объекта не обнаружено". Дело в том, что в тонком клиенте переменная ссылочного типа (например, СправочникСсылка.ОсновныеСредства) содержит только ссылку — уникальный идентификатор объекта (GUID) и его представление для отображения. Самих данных объекта (его реквизитов) на клиенте физически нет. При обращении "через точку" ВыбранноеЗначение.ОсновноеСредство платформа пытается прочитать реквизит, которого на клиенте не существует.

В старом толстом клиенте платформа неявно (скрыто от разработчика) делала запрос к серверу, чтобы получить это значение. В тонком клиенте такие неявные вызовы жестко заблокированы для обеспечения высокой производительности системы.

Вторая ошибка возникает, когда автор пытается перенести получение реквизита на сервер, но возвращает результат выполнения запроса в виде ТаблицыЗначений:


Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа.
по причине:
Ошибка отображения типов:
Отсутствует отображение для типа 'ValueTable'

Запомните важное правило архитектуры 1С:Предприятия: тип данных ТаблицаЗначений является мутабельным (изменяемым) типом и существует исключительно на сервере. Его невозможно передать на клиент напрямую, даже если упаковать внутрь структуры или массива. При попытке передать его через границу "сеть - клиент" платформа пытается выполнить XDTO-преобразование (сериализацию) и выдает ошибку.

Ниже мы подробно разберем три наиболее эффективных способа решения этой задачи.

Решение 1. Использование контекстного серверного вызова (Рекомендуемый способ)

Самый простой и логичный способ — перенести всю логику обработки и заполнения табличной части документа на сервер в контексте формы. При контекстном вызове (директива &НаСервере) нам полностью доступен реквизит формы Объект. Это избавляет нас от необходимости передавать какие-либо таблицы данных между клиентом и сервером.

Рассмотрим, как будет выглядеть правильный код формы документа:


&НаКлиенте
Процедура ЗемельныеУчасткиОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    
    // Передаем полученное значение (ссылку) на сервер для обработки
    ЗаполнитьТабличнуюЧастьНаСервере(ВыбранноеЗначение);
    
КонецПроцедуры

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

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

Решение 2. Преобразование таблицы значений в массив структур (Для неконтекстных вызовов)

Если по каким-то причинам вам крайне важно использовать неконтекстный вызов сервера (директива &НаСервереБезКонтекста), чтобы не гонять тяжелый контекст всей формы на сервер и обратно, то возвращать данные придется в разрешенном для клиента формате. Таким форматом является Массив, наполненный объектами типа Структура.

Давайте по шагам разберем этот процесс:

  1. На клиенте вызываем неконтекстную функцию сервера, передавая туда ссылку.
  2. На сервере получаем реквизит ссылки, выполняем запрос и выгружаем результат в ТаблицуЗначений.
  3. Перебираем строки таблицы и каждую строку упаковываем в Структуру, где ключи — это имена колонок, а значения — данные ячеек.
  4. Добавляем структуры в Массив и возвращаем его на клиент.
  5. На клиенте обходим полученный массив в цикле и заполняем строки табличной части формы.

Посмотрим на пример реализации данного алгоритма:


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

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

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

Решение 3. Работа через реквизит формы типа "Таблица значений"

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

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

Проанализируем пример, когда на форме создан реквизит ТзВТч (тип: ТаблицаЗначений):


&НаСервере
Процедура ЗаполнитьРеквизитФормыНаСервере(ОбъектЭксплуатации)
    
    Запрос = Новый Запрос;
    Запрос.Текст = "..."; // Ваш текст запроса
    Запрос.УстановитьПараметр("ОбъектЭксплуатации", ОбъектЭксплуатации);
    
    ТаблицаДляФормы = Запрос.Выполнить().Выгрузить();
    
    // Если структура колонок реквизита формы ТзВТч и ТаблицыДляФормы совпадает:
    ЗначениеВРеквизитФормы(ТаблицаДляФормы, "ТзВТч");
    
КонецПроцедуры

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

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

← На главную