Как исправить ошибку "Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа" в 1С:Предприятии?

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

Ошибка "Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа" является одной из наиболее распространённых при разработке на платформе 1С:Предприятие в режиме управляемого приложения. Она указывает на то, что система не смогла корректно сериализовать или десериализовать данные, которые вы пытались передать между клиентским и серверным контекстами. Давайте разберем причины возникновения этой ошибки и предложим эффективные методы её устранения.

Что такое ошибка XDTO и почему она возникает?

Платформа 1С использует механизм XDTO (XML Data Transfer Objects) для обмена данными между клиентом и сервером. Когда вы вызываете серверную функцию из клиента (или наоборот), все параметры функции и её возвращаемое значение должны быть преобразованы в формат XDTO. Если какой-либо тип данных не может быть сериализован (преобразован в XDTO-формат) или десериализован (восстановлен из XDTO-формата), возникает данная ошибка. Чаще всего это происходит по следующим причинам:

  1. Передача несериализуемых типов данных: Некоторые типы объектов, такие как непосредственно объекты документов, справочников (полученные методом ПолучитьОбъект()), элементы форм (например, ДанныеФормыЭлементКоллекции), и другие сложные объекты, не предназначены для прямой передачи через границу клиент-сервер.
  2. Модификация типа параметра на сервере: Если на клиент передана ссылка, а на сервере вы преобразуете её в объект (например, Док = Док.ПолучитьОбъект();), и этот параметр затем неявно или явно пытается вернуться на клиент, система будет пытаться сериализовать объект, что приводит к ошибке.
  3. Недопустимые символы в XML: Реже, но возможно, что в данных, которые вы пытаетесь передать, присутствуют символы, недопустимые в XML, что также может вызвать ошибку XDTO.
  4. Особенности работы с данными форм: Объекты типа ДанныеФормыЭлементКоллекции требуют особого подхода, так как их прямая передача на сервер невозможна.

Рассмотрим типовые сценарии возникновения ошибки и их решения

Сценарий 1: Изменение параметра-ссылки на объект на сервере

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

Пример проблемного кода из сообщения 1:


// Фрагмент кода на клиенте
&НаКлиенте
Процедура ВыполнитьОбработку(Команда)
    ПроверкаКоличество = ПроверкаКоличество(Объект.Документ); // Объект.Документ - это Ссылка
    // ...
КонецПроцедуры

// Фрагмент кода на сервере
&НаСервере
Функция ПроверкаКоличество(Док)
    Док = Док.ПолучитьОбъект; // Здесь параметр Док (Ссылка) перезаписывается Объектом
    // ...
    Возврат КоличествоОС; // Функция возвращает число, но Док-то изменился!
КонецФункции

В данном примере проблема заключается в строке Док = Док.ПолучитьОбъект;. Изначально параметр Док является ссылкой, которая успешно передается с клиента на сервер. Но после выполнения этой строки, переменная Док перестает быть ссылкой и становится объектом документа. Хотя функция и возвращает числовое значение КоличествоОС, платформа при завершении серверного вызова пытается сериализовать все параметры, которые могли измениться, чтобы синхронизировать их с клиентским контекстом. В нашем случае она пытается сериализовать Док, который теперь является объектом, а объекты напрямую передавать между клиентом и сервером нельзя.

Решение 1.1: Использование ключевого слова Знач

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

Пример исправления:


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

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

Решение 1.2: Создание локальной переменной для объекта

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

Пример исправления:


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

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

Сценарий 2: Передача элементов форм (ДанныеФормыЭлементКоллекции) на сервер

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

Пример проблемного кода из сообщения 10:


// Фрагмент кода на клиенте
&НаКлиенте
Процедура ЗаполнитьОстатки(Команда)
    // Стр = Элементы.Списание.ТекущиеДанные; // Это ДанныеФормыЭлементКоллекции
    Для Каждого Стр Из Объект.Списание Цикл // Стр здесь - это ДанныеФормыЭлементКоллекции
        ЗаполнитьОстаткиНаСервере(Стр); // Передача несериализуемого типа
    КонецЦикла;
КонецПроцедуры

// Фрагмент кода на сервере
&НаСервере
Процедура ЗаполнитьОстаткиНаСервере(Стр) // Получает ДанныеФормыЭлементКоллекции
    Стр=Стр.ПолучитьОбъект; // Ошибка уже произошла до этой строки, при попытке передачи
    // ...
КонецПроцедуры

Ошибка "Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'" прямо указывает на то, что платформа не знает, как преобразовать этот тип в XDTO. Попытка вызвать Стр.ПолучитьОбъект() на сервере даже не будет достигнута, так как ошибка происходит ещё на этапе передачи параметра с клиента на сервер.

Решение 2.1: Передача необходимых данных в виде структуры

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

Пример исправления:


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

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

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

Важное замечание: Изменения в данных формы (например, СтрФормы.СуммаСписаний = ...) должны производиться только на клиенте. Серверные функции не должны напрямую манипулировать элементами формы. Если вам нужно обновить данные формы на клиенте на основе результатов серверной обработки, серверная функция должна вернуть необходимые данные (например, в структуре или таблице значений), а клиентская функция применит эти данные к форме.

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

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

  1. Передавайте только необходимые и сериализуемые данные: Мы должны минимизировать объем информации, передаваемой между клиентом и сервером. Чем меньше данных, тем быстрее и стабильнее будет работать приложение. Передавайте только ссылки на объекты, простые типы (числа, строки, булевы, даты), структуры, массивы из простых типов. Избегайте передачи сложных объектов форм или объектов базы данных (полученных через ПолучитьОбъект()).
  2. Используйте ключевое слово Знач: Всегда явно указывайте Знач для параметров серверных функций, если вы не ожидаете, что измененный на сервере параметр будет возвращен по ссылке на клиент (что, как мы выяснили, часто приводит к ошибкам). Это гарантирует передачу копии значения, и изменения внутри серверной функции не повлияют на клиентскую переменную.
  3. Избегайте передачи объектов через границу клиент-сервер: Результаты функций ПолучитьОбъект() или ЭтотОбъект() должны использоваться только на той стороне, где они были созданы (как правило, на сервере). Не пытайтесь передать сам объект (например, ДокументОбъект) через границу клиент-сервер. Вместо этого передавайте ссылки на объекты.
  4. Четкое разделение ответственности: Мы должны четко разграничивать логику клиента и сервера. Клиент отвечает за пользовательский интерфейс, сбор и отображение данных. Сервер отвечает за бизнес-логику, работу с базой данных, проведение документов, выполнение запросов и сложные вычисления.
  5. Временное хранилище для больших данных: Если вам нужно передать большой объем данных (например, таблицу значений с множеством строк) с сервера на клиент или обратно, используйте временное хранилище. Это позволяет передать лишь адрес данных во временном хранилище, а сами данные будут получены по этому адресу, что оптимизирует производительность и снижает вероятность ошибок XDTO.
  6. Активно используйте отладчик: Всегда используйте отладчик 1С для просмотра типов и значений переменных на каждой стороне (клиент/сервер) перед и после вызова серверной функции. Это поможет точно определить, какой тип данных вызывает проблему и на каком этапе.
  7. Обновление платформы: Убедитесь, что вы используете актуальную версию платформы 1С:Предприятие. В некоторых версиях могут быть исправлены ошибки, связанные с XDTO или сериализацией определенных типов данных.

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

← На главную