При разработке в 1С:Предприятии 8.3 в режиме управляемых форм, многие из нас сталкиваются с необходимостью обмениваться данными между клиентским и серверным контекстом. Одним из распространенных сценариев является запрос данных с сервера и их последующее использование на клиенте, например, для заполнения полей формы или элементов табличной части. Однако, при попытке напрямую передать объект ТаблицаЗначений с сервера на клиент, мы можем получить ошибку: "Ошибка отображения типов: Отсутствует отображение для типа '{http://v8.1c.ru/8.1/data/core}ValueTable' по причине: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа.". Давайте разберемся, почему возникает эта ошибка и как корректно решить эту задачу — поможет пошаговая отладка и просмотр переменных в 1С.
Платформа 1С:Предприятие 8.3, особенно в режиме управляемых форм, спроектирована с учетом работы в условиях потенциально низкой скорости соединения, например, через веб-клиент или мобильное приложение. Для оптимизации трафика, обеспечения производительности (поможет анализ производительности и оптимизация запросов 1С) и безопасности, не все типы данных могут быть напрямую переданы между сервером и клиентом. ТаблицаЗначений является "мутабельным" (изменяемым) типом данных, который не может быть просто так сериализован и десериализован для передачи на клиент. Клиентское приложение не "знает" о таком объекте, как ТаблицаЗначений, в своем контексте, поэтому попытка передать его напрямую приводит к ошибке.
Рассмотрим несколько распространенных и эффективных способов, как корректно передавать данные с сервера на клиент, избегая указанной ошибки, и какой из них выбрать в зависимости от вашей конкретной задачи — поможет нейросетевой помощник для написания кода 1С.
Если ваш запрос с сервера гарантированно возвращает только одну строку данных, самым простым и эффективным способом будет преобразовать эту строку в Структуру. Объект Структура является допустимым типом для передачи между клиентом и сервером.
Структуру и заполнить ее значениями из этой строки.Давайте посмотрим на модифицированный пример функции на сервере из исходного сообщения:
&НаСервере
Функция ЗапроситьДанныеПоНоменклатуреНаСервере(ВыбранноеЗначение)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| паУслугиИЦеныЗахарова.Количество КАК Количество,
| паУслугиИЦеныЗахарова.Стоимость КАК Стоимость,
| паУслугиИЦеныЗахарова.КодДохода КАК КодДохода
|ИЗ
| Справочник.паУслугиИЦеныЗахарова КАК паУслугиИЦеныЗахарова
|ГДЕ
| паУслугиИЦеныЗахарова.Номенклатура = &Номенклатура";
Запрос.УстановитьПараметр("Номенклатура", ВыбранноеЗначение);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() Тогда
// Если нашли одну строку, преобразуем ее в Структуру
Возврат Новый Структура("Количество, Стоимость, КодДохода",
Выборка.Количество,
Выборка.Стоимость,
Выборка.КодДохода);
Иначе
// Если ничего не найдено, можно вернуть Неопределено или пустую структуру
Возврат Новый Структура("Количество, Стоимость, КодДохода", 0, 0, "");
КонецЕсли;
КонецФункции
Структуру, мы можем легко получить доступ к ее элементам по именам и использовать их для заполнения полей формы.
&НаКлиенте
Процедура УслугиОЭПНоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
Результат = ЗапроситьДанныеПоНоменклатуреНаСервере(ВыбранноеЗначение);
// Предположим, что текущая строка табличной части доступна как Элементы.ИмяТабличнойЧасти.ТекущиеДанные
// и имеет реквизиты Количество, Стоимость, КодДохода
Если Результат <> Неопределено Тогда
Элементы.ИмяТабличнойЧасти.ТекущиеДанные.Количество = Результат.Количество;
Элементы.ИмяТабличнойЧасти.ТекущиеДанные.Стоимость = Результат.Стоимость;
Элементы.ИмяТабличнойЧасти.ТекущиеДанные.КодДохода = Результат.КодДохода;
КонецЕсли;
КонецПроцедуры
Также, для получения реквизитов одного объекта (если ВыбранноеЗначение является ссылкой на объект, а не просто значением для фильтра запроса), можно использовать служебные функции, например, ОбщегоНазначения.ЗначенияРеквизитовОбъекта, как было предложено в форуме. Это позволяет получить структуру с нужными реквизитами без ручного написания запроса.
&НаСервере
Функция ПолучитьДанныеОбъекта(СсылкаОбъекта)
// Предполагаем, что СсылкаОбъекта - это ссылка на элемент справочника
// или любой другой объект, который имеет эти реквизиты.
// Если "паУслугиИЦеныЗахарова" является справочником,
// то можно передать ссылку на его элемент.
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СсылкаОбъекта, "Количество,Стоимость,КодДохода");
КонецФункции
Если ваш запрос может возвращать несколько строк данных, и эти данные необходимо отобразить на клиенте (например, в динамическом списке или для ручного заполнения табличной части), мы используем МассивСтруктур. Этот тип данных, как и Структура, корректно передается между клиентом и сервером.
ТаблицуЗначений, а затем преобразуем ее в МассивСтруктур. Каждая строка таблицы значений становится отдельной Структурой внутри массива.Посмотрим на пример функции преобразования, которая может быть размещена в общем модуле:
&НаСервере
Функция ТаблицуЗначенийВМассив(ТаблЗнач) Экспорт
Результат = Новый Массив;
Для Каждого ТекСтр Из ТаблЗнач Цикл
СтруктураСтроки = Новый Структура;
Для Каждого ТекКол Из ТаблЗнач.Колонки Цикл
СтруктураСтроки.Вставить(ТекКол.Имя, ТекСтр[ТекКол.Имя])
КонецЦикла;
Результат.Добавить(СтруктураСтроки)
КонецЦикла;
Возврат Результат
КонецФункции
// Пример использования в функции, возвращающей данные по номенклатуре:
&НаСервере
Функция ЗапроситьНесколькоДанныхПоНоменклатуреНаСервере(ВыбранноеЗначение)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| паУслугиИЦеныЗахарова.Количество КАК Количество,
| паУслугиИЦеныЗахарова.Стоимость КАК Стоимость,
| паУслугиИЦеныЗахарова.КодДохода КАК КодДохода
|ИЗ
| Справочник.паУслугиИЦеныЗахарова КАК паУслугиИЦеныЗахарова
|ГДЕ
| паУслугиИЦеныЗахарова.Номенклатура = &Номенклатура
|УПОРЯДОЧИТЬ ПО
| паУслугиИЦеныЗахарова.Наименование"; // Добавим условие для примера, что может быть несколько записей
Запрос.УстановитьПараметр("Номенклатура", ВыбранноеЗначение);
РезультатЗапроса = Запрос.Выполнить().Выгрузить(); // Получаем ТаблицуЗначений
Возврат ТаблицуЗначенийВМассив(РезультатЗапроса); // Преобразуем в МассивСтруктур
КонецФункции
МассивСтруктур и можем перебрать его в цикле, чтобы заполнить, например, табличную часть формы или другой элемент управления.
&НаКлиенте
Процедура ЗаполнитьТабличнуюЧастьПоНоменклатуре(ВыбранноеЗначение)
МассивДанных = ЗапроситьНесколькоДанныхПоНоменклатуреНаСервере(ВыбранноеЗначение);
// Очищаем существующую табличную часть перед заполнением
Элементы.ИмяТабличнойЧасти.Очистить();
Для Каждого СтруктураСтроки Из МассивДанных Цикл
НоваяСтрока = Элементы.ИмяТабличнойЧасти.Добавить();
НоваяСтрока.Количество = СтруктураСтроки.Количество;
НоваяСтрока.Стоимость = СтруктураСтроки.Стоимость;
НоваяСтрока.КодДохода = СтруктураСтроки.КодДохода;
// ... и так далее для всех нужных полей
КонецЦикла;
КонецПроцедуры
Зачастую, если данные нужны для заполнения полей формы или табличной части, самым эффективным и рекомендованным подходом является выполнение всей логики заполнения непосредственно на сервере — пригодится анализ структуры вызовов и серверной логики 1С. Это позволяет минимизировать объем передаваемых данных и количество клиент-серверных вызовов, что положительно сказывается на производительности.
ПриИзменении для поля табличной части или ОбработкаВыбора), мы вызываем серверную процедуру.Рассмотрим пример для исходной задачи, где при изменении номенклатуры в табличной части нужно заполнить количество и стоимость:
// В модуле формы документа
// Серверная процедура, которая принимает текущую строку табличной части
// (или ее идентификатор) и обновляет ее.
&НаСервере
Процедура ОбновитьДанныеСтрокиТабличнойЧастиНаСервере(СтрокаТабличнойЧасти, ВыбранноеЗначениеНоменклатуры)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| паУслугиИЦеныЗахарова.Количество КАК Количество,
| паУслугиИЦеныЗахарова.Стоимость КАК Стоимость,
| паУслугиИЦеныЗахарова.КодДохода КАК КодДохода
|ИЗ
| Справочник.паУслугиИЦеныЗахарова КАК паУслугиИЦеныЗахарова
|ГДЕ
| паУслугиИЦеныЗахарова.Номенклатура = &Номенклатура";
Запрос.УстановитьПараметр("Номенклатура", ВыбранноеЗначениеНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() Тогда
СтрокаТабличнойЧасти.Количество = Выборка.Количество;
СтрокаТабличнойЧасти.Стоимость = Выборка.Стоимость;
СтрокаТабличнойЧасти.КодДохода = Выборка.КодДохода;
Иначе
// Очищаем или устанавливаем значения по умолчанию, если ничего не найдено
СтрокаТабличнойЧасти.Количество = 0;
СтрокаТабличнойЧасти.Стоимость = 0;
СтрокаТабличнойЧасти.КодДохода = Неопределено;
КонецЕсли;
КонецПроцедуры
// Клиентская процедура, вызывающая серверную
&НаКлиенте
Процедура УслугиОЭПНоменклатураПриИзменении(Элемент) // Или ОбработкаВыбора
// Получаем текущую строку табличной части, в которой произошло изменение
ТекущаяСтрока = Элементы.ИмяТабличнойЧасти.ТекущиеДанные;
// Вызываем серверную процедуру, передавая текущую строку и выбранное значение
ОбновитьДанныеСтрокиТабличнойЧастиНаСервере(ТекущаяСтрока, ТекущаяСтрока.Номенклатура);
// Предполагается, что ТекущаяСтрока.Номенклатура уже содержит выбранное значение
КонецПроцедуры
В этом примере, Элементы.ИмяТабличнойЧасти.ТекущиеДанные передает на сервер объект типа "Строка табличной части", который является допустимым для передачи и может быть изменен на сервере.
Для передачи очень больших объемов данных, двоичных данных, файлов или специфических объектов, которые невозможно напрямую сериализовать, мы используем ВременноеХранилище.
ТаблицуЗначений, а затем выполнить с ней на клиенте какие-то специфические манипуляции, которые нельзя сделать на сервере (что редкость).ПоместитьВоВременноеХранилище(). Эта функция возвращает адрес данных во временном хранилище.ПолучитьИзВременногоХранилища().Пример использования ВременногоХранилища:
// На сервере
&НаСервере
Функция ПолучитьАдресТаблицыЗначений()
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Наименование,
| Спр.Код
|ИЗ
| Справочник.ЛюбойСправочник КАК Спр";
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Помещаем ТаблицуЗначений во временное хранилище
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДанных, УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
// На клиенте
&НаКлиенте
Процедура ИспользоватьДанныеИзХранилища()
Адрес = ПолучитьАдресТаблицыЗначений();
// Для получения данных из временного хранилища на клиенте,
// все равно потребуется серверный вызов, так как
// ПолучитьИзВременногоХранилища() - это серверная функция.
// Поэтому обычно ВременноеХранилище используется для передачи
// между различными серверными вызовами или между клиентом и сервером
// для данных, которые клиент не может напрямую обрабатывать, но может
// передать адрес другому серверному вызову.
// Если же вам нужно именно на клиенте обработать ТЗ, то это не прямой путь.
// Скорее, это для передачи файлов, картинок и т.п.
// Пример получения на сервере (например, в другой серверной процедуре):
// &НаСервере
// Процедура ОбработатьДанныеИзХранилищаНаСервере(АдресХранилища)
// ПолученныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
// Если ТипЗнч(ПолученныеДанные) = Тип("ТаблицаЗначений") Тогда
// // Делаем что-то с таблицей значений
// КонецЕсли;
// КонецПроцедуры
КодФункции
Как мы видим, для получения самой ТаблицыЗначений на клиент через ВременноеХранилище, нам все равно потребуется серверный вызов ПолучитьИзВременногоХранилища. Это означает, что ТаблицаЗначений никогда не покидает серверный контекст, а на клиент передается лишь ее адрес. Это подтверждает, что для прямого использования данных на клиенте, Структура или МассивСтруктур являются более подходящими решениями.
Помимо выбора правильных типов данных, существуют и другие важные аспекты оптимизации клиент-серверного взаимодействия, которые мы должны учитывать:
&НаСервере): При таком вызове на сервер передаются не только явно указанные параметры, но и все измененные данные формы. Это удобно, так как сервер видит актуальное состояние формы, но может приводить к избыточному трафику и снижению производительности, особенно на медленных каналах связи.&НаСервереБезКонтекста): При таком вызове передаются только те данные, которые явно указаны в параметрах функции или процедуры. Это "легкий" вызов, который рекомендуется использовать, когда нет необходимости в передаче всего контекста формы. Например, если нам нужно просто получить справочную информацию по идентификатору, не затрагивая другие элементы формы. Для вашего примера с запросом данных по номенклатуре &НаСервереБезКонтекста был бы предпочтительнее, если только вы не планируете изменять реквизиты объекта формы, а лишь возвращать данные.Итак, мы проанализировали причины ошибки "Значение недопустимого типа" и рассмотрели различные способы решения проблемы передачи данных между клиентом и сервером в 1С:Предприятии 8.3. Для вашей конкретной задачи, где по изменению номенклатуры в табличной части требуется обновить другие колонки, наиболее оптимальным решением будет прямое заполнение данных на сервере, передавая текущую строку табличной части в серверную процедуру. Если же вам действительно нужно получить набор данных на клиент для дальнейшей обработки, то используйте Структуру (для одной строки) или МассивСтруктур (для нескольких строк).