Как передать данные из ТаблицыЗначений с сервера на клиент в 1С:Предприятие 8.3 и избежать ошибки "Значение недопустимого типа"?

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

При разработке в 1С:Предприятии 8.3 в режиме управляемых форм, многие из нас сталкиваются с необходимостью обмениваться данными между клиентским и серверным контекстом. Одним из распространенных сценариев является запрос данных с сервера и их последующее использование на клиенте, например, для заполнения полей формы или элементов табличной части. Однако, при попытке напрямую передать объект ТаблицаЗначений с сервера на клиент, мы можем получить ошибку: "Ошибка отображения типов: Отсутствует отображение для типа '{http://v8.1c.ru/8.1/data/core}ValueTable' по причине: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа.". Давайте разберемся, почему возникает эта ошибка и как корректно решить эту задачу — поможет пошаговая отладка и просмотр переменных в 1С.

Почему возникает ошибка "Значение недопустимого типа" при передаче ТаблицыЗначений?

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

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

Решение 1: Возврат Структуры (для одной строки данных)

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

  1. Когда применять: Этот подход идеален, когда по выбранной номенклатуре или другому значению необходимо получить набор свойств (количество, цена, код дохода) для одной конкретной записи.
  2. Как преобразовать на сервере: После выполнения запроса и получения одной строки, мы можем создать новую Структуру и заполнить ее значениями из этой строки.

Давайте посмотрим на модифицированный пример функции на сервере из исходного сообщения:


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

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

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


&НаСервере
Функция ПолучитьДанныеОбъекта(СсылкаОбъекта)
    // Предполагаем, что СсылкаОбъекта - это ссылка на элемент справочника
    // или любой другой объект, который имеет эти реквизиты.
    // Если "паУслугиИЦеныЗахарова" является справочником,
    // то можно передать ссылку на его элемент.
    Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СсылкаОбъекта, "Количество,Стоимость,КодДохода");
КонецФункции

Решение 2: Использование МассиваСтруктур (для нескольких строк данных)

Если ваш запрос может возвращать несколько строк данных, и эти данные необходимо отобразить на клиенте (например, в динамическом списке или для ручного заполнения табличной части), мы используем МассивСтруктур. Этот тип данных, как и Структура, корректно передается между клиентом и сервером.

  1. Когда применять: Этот метод подходит, когда требуется передать на клиент список однотипных записей, каждая из которых представляет собой набор свойств. Например, при формировании списка товаров для выбора или для предварительного заполнения новой табличной части.
  2. Как преобразовать на сервере: На сервере мы выполняем запрос (удобно через отладка программного кода и запросов в 1С), получаем ТаблицуЗначений, а затем преобразуем ее в МассивСтруктур. Каждая строка таблицы значений становится отдельной Структурой внутри массива.

Посмотрим на пример функции преобразования, которая может быть размещена в общем модуле:


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

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

&НаКлиенте
Процедура ЗаполнитьТабличнуюЧастьПоНоменклатуре(ВыбранноеЗначение)
    МассивДанных = ЗапроситьНесколькоДанныхПоНоменклатуреНаСервере(ВыбранноеЗначение);
    
    // Очищаем существующую табличную часть перед заполнением
    Элементы.ИмяТабличнойЧасти.Очистить(); 

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

Решение 3: Прямое заполнение данных на сервере (оптимальный подход)

Зачастую, если данные нужны для заполнения полей формы или табличной части, самым эффективным и рекомендованным подходом является выполнение всей логики заполнения непосредственно на сервере — пригодится анализ структуры вызовов и серверной логики 1С. Это позволяет минимизировать объем передаваемых данных и количество клиент-серверных вызовов, что положительно сказывается на производительности.

  1. Когда применять: Этот подход наиболее уместен, когда мы выбираем значение в одной колонке табличной части (например, номенклатуру), и нужно автоматически обновить другие колонки этой же строки (количество, цена, код дохода). Также подходит для заполнения реквизитов объекта или формы.
  2. Преимущества:
    • Высокая производительность: Меньше данных передается по сети, меньше операций сериализации/десериализации.
    • Упрощение кода: Вся логика работы с данными объекта или табличной частью сосредоточена в одном месте на сервере.
    • Уменьшение трафика: На клиент передается только команда обновить форму, а не массив данных.
  3. Механизм:
    1. На клиенте, при изменении значения (например, в обработчике события ПриИзменении для поля табличной части или ОбработкаВыбора), мы вызываем серверную процедуру.
    2. В серверную процедуру передаем ссылку на текущий объект, ссылку на текущую строку табличной части (если это табличная часть) или другие идентификаторы, необходимые для нахождения нужных полей.
    3. На сервере выполняем запрос, получаем данные и непосредственно обновляем реквизиты объекта или текущую строку табличной части.

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


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

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

В этом примере, Элементы.ИмяТабличнойЧасти.ТекущиеДанные передает на сервер объект типа "Строка табличной части", который является допустимым для передачи и может быть изменен на сервере.

Решение 4: ВременноеХранилище (для больших объемов или специфических данных)

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

  1. Когда применять: Этот метод менее удобен для простых задач вроде вашей, но является мощным инструментом. Он подходит для ситуаций, когда необходимо передать "тяжелые" данные, которые не помещаются в обычный сериализатор или имеют сложную структуру. Например, если вы хотите передать огромную ТаблицуЗначений, а затем выполнить с ней на клиенте какие-то специфические манипуляции, которые нельзя сделать на сервере (что редкость).
  2. Как работает:
    1. На сервере данные помещаются во временное хранилище с помощью функции ПоместитьВоВременноеХранилище(). Эта функция возвращает адрес данных во временном хранилище.
    2. Адрес передается на клиент (это просто строка).
    3. На клиенте (или в другом серверном вызове) данные могут быть извлечены по этому адресу с помощью ПолучитьИзВременногоХранилища().

Пример использования ВременногоХранилища:


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

// На клиенте
&НаКлиенте
Процедура ИспользоватьДанныеИзХранилища()
    Адрес = ПолучитьАдресТаблицыЗначений();
    
    // Для получения данных из временного хранилища на клиенте,
    // все равно потребуется серверный вызов, так как 
    // ПолучитьИзВременногоХранилища() - это серверная функция.
    // Поэтому обычно ВременноеХранилище используется для передачи 
    // между различными серверными вызовами или между клиентом и сервером
    // для данных, которые клиент не может напрямую обрабатывать, но может
    // передать адрес другому серверному вызову.
    // Если же вам нужно именно на клиенте обработать ТЗ, то это не прямой путь.
    // Скорее, это для передачи файлов, картинок и т.п.
    
    // Пример получения на сервере (например, в другой серверной процедуре):
    // &НаСервере
    // Процедура ОбработатьДанныеИзХранилищаНаСервере(АдресХранилища)
    //     ПолученныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    //     Если ТипЗнч(ПолученныеДанные) = Тип("ТаблицаЗначений") Тогда
    //         // Делаем что-то с таблицей значений
    //     КонецЕсли;
    // КонецПроцедуры
КодФункции

Как мы видим, для получения самой ТаблицыЗначений на клиент через ВременноеХранилище, нам все равно потребуется серверный вызов ПолучитьИзВременногоХранилища. Это означает, что ТаблицаЗначений никогда не покидает серверный контекст, а на клиент передается лишь ее адрес. Это подтверждает, что для прямого использования данных на клиенте, Структура или МассивСтруктур являются более подходящими решениями.

Общие рекомендации по оптимизации клиент-серверного взаимодействия

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

  1. Минимизация серверных вызовов: Каждый вызов сервера, даже если он передает небольшой объем данных, несет накладные расходы на установление соединения, сериализацию/десериализацию и передачу по сети. Старайтесь объединять несколько операций, требующих обращения к серверу, в один серверный вызов.
  2. Уменьшение объема передаваемых данных: Передавайте только те данные, которые действительно необходимы на другой стороне. Не стоит передавать весь объект, если нужны только несколько его реквизитов. Это снижает сетевой трафик и ускоряет работу.
  3. Выбор между контекстными и внеконтекстными вызовами:
    • Контекстный вызов (&НаСервере): При таком вызове на сервер передаются не только явно указанные параметры, но и все измененные данные формы. Это удобно, так как сервер видит актуальное состояние формы, но может приводить к избыточному трафику и снижению производительности, особенно на медленных каналах связи.
    • Внеконтекстный вызов (&НаСервереБезКонтекста): При таком вызове передаются только те данные, которые явно указаны в параметрах функции или процедуры. Это "легкий" вызов, который рекомендуется использовать, когда нет необходимости в передаче всего контекста формы. Например, если нам нужно просто получить справочную информацию по идентификатору, не затрагивая другие элементы формы. Для вашего примера с запросом данных по номенклатуре &НаСервереБезКонтекста был бы предпочтительнее, если только вы не планируете изменять реквизиты объекта формы, а лишь возвращать данные.
  4. Кэширование: Платформа 1С использует многоуровневое кэширование данных. Мы должны это учитывать при проектировании логики. Если одни и те же данные часто запрашиваются, возможно, их стоит кэшировать на клиенте, чтобы избежать повторных серверных вызовов.

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

← На главную