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