Как передать таблицу в бесконтекстную серверную процедуру и избежать ошибок отображения типов в 1С

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

При разработке на управляемых формах в платформе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью передать данные с клиента на сервер для последующей обработки. Особый интерес вызывают неконтекстные вызовы с директивой компиляции &НаСервереБезКонтекста, так как они позволяют минимизировать объем передаваемых данных и не гонять тяжелый контекст всей управляемой формы туда и обратно.

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

Разбираем ошибку: «Нельзя изменять поле, содержащее объект данных формы»

Рассмотрим стандартную ситуацию. Разработчик пытается передать табличную часть объекта в бесконтекстную процедуру для обработки:


&НаКлиенте
Процедура ЗаменаВозвратов(Команда)
    МассивИндексов = Элементы.Реализации.ВыделенныеСтроки;
    ЗаменаВозвратовНаСервере(МассивИндексов, Объект.Реализации);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаменаВозвратовНаСервере(МассивИндексов, Таблица)
    // Здесь мы пытаемся что-то сделать с таблицей
КонецПроцедуры

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

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

В нашем случае переменная на клиенте — это Объект.Реализации, которая имеет тип ДанныеФормыКоллекция. Платформа строго запрещает прямую перезапись (присвоение нового значения) реквизитов формы на клиенте. Попытка неявного обратного присваивания при выходе из серверной процедуры расценивается платформой как попытка деструктивного изменения системного объекта данных формы, что и приводит к ошибке.

Разбираем ошибку: «Ошибка отображения типов»

Часто разработчики пытаются обойти первую проблему внутри серверной процедуры, выгружая данные формы в полноценную таблицу значений для удобства обработки:


&НаСервереБезКонтекста
Процедура ЗаменаВозвратовНаСервере(МассивИндексов, Таблица)
    Таблица = Таблица.Выгрузить(); // Ошибка возникает при завершении процедуры
КонецПроцедуры

В результате выполнение кода прерывается с сообщением «Ошибка отображения типов: Отсутствует отображение для типа...».

Выясним причину этого поведения. На сервере 1С доступен полноценный объект ТаблицаЗначений. Метод Выгрузить() у объекта типа ДанныеФормыКоллекция преобразует данные и возвращает именно ТаблицуЗначений. Переменная Таблица на сервере меняет свой тип.

Поскольку параметр все еще передается по ссылке, при выходе из процедуры платформа пытается передать измененную переменную Таблица (которая теперь является ТаблицейЗначений) обратно на клиент. Но на тонком или веб-клиенте типа данных ТаблицаЗначений физически не существует! Клиентская среда не умеет работать с этим типом, сериализатор платформы не может сопоставить типы данных, и в результате мы получаем ошибку отображения типов.

Решение проблемы с помощью ключевого слова Знач

Самый простой и быстрый способ победить обе эти ошибки в рамках одного вызова — использовать модификатор передачи параметров Знач (передача параметра по значению). Посмотрим на пример кода:


&НаКлиенте
Процедура ЗаменаВозвратов(Команда)
    МассивИндексов = Элементы.Реализации.ВыделенныеСтроки;
    ЗаменаВозвратовНаСервере(МассивИндексов, Объект.Реализации);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаменаВозвратовНаСервере(МассивИндексов, Знач Таблица)
    // Теперь это работает без ошибок!
    ТаблицаЗначений = Таблица.Выгрузить(); 
    // Проводим обработку...
КонецПроцедуры

Почему это работает? Модификатор Знач указывает платформе, что параметр передается «в одну сторону». Локальная копия переменной на сервере создается, но при завершении работы процедуры платформа не пытается вернуть значение обратно на клиент. Соответственно, мы избегаем попытки перезаписи данных формы на клиенте и исключаем передачу неподдерживаемой ТаблицыЗначений обратно в клиентскую среду.

Архитектурный анализ: скрытая угроза передачи ДанныеФормыКоллекция по значению

Хотя использование Знач решает технические проблемы с ошибками, с точки зрения стандартов разработки и оптимизации производительности 1С, передача больших коллекций данных формы в бесконтекстный серверный вызов — это плохая практика.

Давайте разберем по шагам, что происходит при таком вызове:

  1. Платформа берет объект ДанныеФормыКоллекция (всю вашу табличную часть со всеми строками и колонками) на клиенте.
  2. Сериализует его во временный XML/JSON-пакет.
  3. Передает этот пакет по сети на сервер.
  4. На сервере десериализует данные обратно в объект.

Если в табличной части вашего документа содержится несколько сотен или тысяч строк, этот процесс вызовет колоссальную нагрузку на сеть, процессор сервера и клиента, а также приведет к заметным задержкам в интерфейсе для пользователя. При этом контекстный вызов &НаСервере оптимизирован самой платформой: она передает только измененные данные формы, автоматически синхронизируя контекст.

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

Оптимальные альтернативные варианты решения задачи

Рассмотрим два профессиональных подхода, которые соответствуют стандартам разработки фирмы «1С».

Вариант А. Передача массива легких структур (Рекомендуется)

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

Разберем этот вариант на практическом примере:


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

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

Вариант Б. Чтение данных запросом из базы данных по ссылкам

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

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


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

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

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

← На главную