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

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

В разработке на платформе 1С иногда встречаются "неожиданные эффекты", когда, казалось бы, простой и логичный код приводит к ошибке. Рассмотрим типичную ситуацию: есть серверная процедура с необязательным параметром, и при определенных условиях внутри этой процедуры параметру присваивается табличная часть документа. Это действие внезапно вызывает ошибку передачи данных между клиентом и сервером. Давайте разберемся, почему так происходит и как этого избежать.

Анализ проблемы: что происходит "под капотом"

Проанализируем исходный код, который приводит к проблеме. На клиенте вызывается серверная процедура, в которую передается только один обязательный параметр — ссылка на документ.


&НаКлиенте
Процедура ОбработатьДокумент(Док)
    ОбработатьДокументНаСервере(Док);
КонецПроцедуры

&НаСервере
Процедура ОбработатьДокументНаСервере(Док, ТабЧасть = Неопределено)
    Если ТабЧасть = Неопределено Тогда
        ТабЧасть = Док.ТабличнаяЧасть;
    КонецЕсли;
    // тут можно что-то обработать
КонецПроцедуры

При выполнении этого кода система выдает ошибку, текст которой может выглядеть примерно так:

{Форма.Форма.Форма(161)}: Ошибка при вызове метода контекста (ОбработатьДокументНаСервере) ОбработатьДокументНаСервере(Док); по причине: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. по причине: Ошибка преобразования данных XDTO: ... Отсутствует отображение для типа 'ДокументТабличнаяЧасть.Инвентаризация.Товары'

На первый взгляд, все логично: если второй параметр ТабЧасть не передан, мы получаем его из документа на сервере. Почему же возникает ошибка? Фокус кроется в механизме передачи параметров между клиентом и сервером по умолчанию.

По умолчанию параметры в 1С передаются по ссылке. В контексте клиент-серверного взаимодействия это означает, что платформа не только передает значение параметра с клиента на сервер, но и пытается вернуть его измененное значение с сервера обратно на клиент после завершения процедуры.

Рассмотрим по шагам, что происходит:

  1. Клиентский код ОбработатьДокументНаСервере(Док) — это, по сути, "синтаксический сахар". Под капотом платформа выполняет вызов с передачей значения по умолчанию для второго параметра: ОбработатьДокументНаСервере(Док, Неопределено).
  2. На сервере в процедуру приходит два параметра: Док (типа ДокументСсылка) и ТабЧасть (со значением Неопределено).
  3. Внутри процедуры выполняется условие Если ТабЧасть = Неопределено Тогда.
  4. Переменной ТабЧасть присваивается новое значение: Док.ТабличнаяЧасть. Теперь тип этой переменной — ДокументТабличнаяЧасть.
  5. Процедура завершает свою работу.
  6. Вот ключевой момент: так как параметр ТабЧасть был передан без специального указания (т.е. по ссылке), платформа пытается вернуть его новое значение обратно на клиент.
  7. Тип данных ДокументТабличнаяЧасть является "серверным" объектом. Он не может быть сериализован (преобразован в формат для передачи по сети) и отправлен на клиент. Платформа не знает, как "отобразить" этот тип на клиенте.
  8. В результате этой попытки возникает ошибка сериализации, которую мы и видим.

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

Решение 1: Правильный и рекомендуемый способ — `Знач`

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

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

Исправленный код будет выглядеть так:


&НаСервере
Процедура ОбработатьДокументНаСервере(Док, Знач ТабЧасть = Неопределено)
    Если ТабЧасть = Неопределено Тогда
        ТабЧасть = Док.ТабличнаяЧасть;
    КонецЕсли;
    // тут можно что-то обработать
КонецПроцедуры

Почему это лучшее решение:

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

Альтернативные, но менее изящные решения

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

Способ А: "Обнуление" параметра перед выходом

Идея этого способа — перед завершением процедуры вернуть параметру значение, которое можно передать на клиент (например, Неопределено).


&НаСервере
Процедура ОбработатьДокументНаСервере(Док, ТабЧасть = Неопределено)
    Если ТабЧасть = Неопределено Тогда
        ТабЧасть = Док.ТабличнаяЧасть;
    КонецЕсли;

    // тут можно что-то обработать

    // Возвращаем параметру исходный тип перед выходом
    ТабЧасть = Неопределено;
КонецПроцедуры

Этот метод работает, но он является "костылем". Он усложняет логику, заставляет писать лишнюю строку кода и не решает проблему производительности, так как передача данных обратно на клиент все равно инициируется (просто передается "безопасное" значение).

Способ Б: Процедура-обертка

Еще один вариант — создать промежуточную серверную процедуру-обертку, которая будет вызывать основную процедуру уже полностью в серверном контексте.


&НаКлиенте
Процедура ОбработатьДокумент(Док)
    ОбработатьТолькоДокументНаСервере(Док);
КонецПроцедуры

&НаСервере
Процедура ОбработатьТолькоДокументНаСервере(Док)
    // Вызов происходит уже в контексте сервера,
    // поэтому проблемы с передачей данных на клиент нет
    ОбработатьДокументНаСервере(Док);
КонецПроцедуры

&НаСервере
Процедура ОбработатьДокументНаСервере(Док, ТабЧасть = Неопределено)
    Если ТабЧасть = Неопределено Тогда
        ТабЧасть = Док.ТабличнаяЧасть;
    КонецЕсли;
    // тут можно что-то обработать
КонецПроцедуры

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

Выводы

Рассмотренная проблема — отличный пример того, как важно понимать тонкости работы клиент-серверной архитектуры в 1С. Главный урок, который мы можем извлечь:

Используйте Знач для всех входных параметров серверных вызовов, которые не должны возвращать значение обратно на клиент.

Это не только предотвратит "неожиданные" ошибки передачи несериализуемых типов, но и сделает ваш код чище, понятнее и производительнее.

← На главную