Почему возникает ошибка при передаче СписокЗначений на сервер в 1С, но не при передаче Массива?

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

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

В основе проблемы лежит концепция мутабельности. Мутабельные (изменяемые) типы данных – это объекты, чье внутреннее состояние может быть изменено после их создания. В 1С:Предприятии платформа стремится обеспечить целостность данных и предотвратить их несанкционированное изменение в процессе передачи между клиентом и сервером. Если объект может измениться "пока он находится на сервере", то такая передача не пройдет, чтобы избежать расхождений в состоянии данных на разных сторонах.

Какие типы данных относятся к мутабельным в 1С?

В языке программирования 1С к мутабельным типам обычно относятся сложные составные объекты, которые могут иметь внутренние связи или быть изменены после создания, что может привести к непредсказуемому поведению при передаче между контекстами. К ним относятся:

  1. Объект: Под этим подразумеваются объекты баз данных (справочники, документы и т.д.), которые хранят данные и имеют ссылки на другие объекты.
  2. Форма: Объекты формы, которые непосредственно управляют отображением и взаимодействием с пользователем.
  3. СписокЗначений: Этот тип является центральным в нашей дискуссии и вызывает ошибки при передаче на сервер.
  4. ТаблицаЗначений: Еще один сложный табличный тип, который по своей природе является изменяемым.

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

Почему СписокЗначений мутабелен, а Массив нет?

На первый взгляд, и СписокЗначений, и Массив кажутся похожими: они оба представляют собой коллекции элементов, и их длина может динамически меняться. Однако их внутренняя реализация и назначение в 1С существенно различаются, что и определяет их мутабельность в контексте клиент-серверного взаимодействия.

Мутабельность СписокЗначений

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

  1. Хранение дополнительных свойств: СписокЗначений хранит не только сами значения, но и их представления (строки, картинки, флажки и т.д.). Эти представления используются для отображения элементов в различных элементах управления, таких как поля ввода, списки выбора, или при выгрузке в другие форматы. Изменение представления элемента СписокЗначений изменяет сам список, не создавая новый объект. Это делает его "изменчивым" в понимании 1С, так как изменение одного из его компонентов может повлиять на его поведение и отображение.
  2. Возможности поиска и сортировки: СписокЗначений обладает встроенными методами для поиска элементов по значению, представлению или даже по данным, а также для сортировки. Эти функциональные возможности могут требовать более сложной внутренней структуры и состояния, которое платформа должна контролировать.
  3. Предполагаемое использование временных таблиц: Существует предположение, что СписокЗначений для своей работы может использовать временные структуры или даже механизмы, схожие с временными таблицами базы данных (подобно ТаблицеЗначений), особенно если список большой или используется для интерактивного взаимодействия. Такие механизмы могут влиять на его мутабельность и усложнять гарантию целостности при передаче.

Таким образом, когда мы передаем СписокЗначений, платформа видит сложный объект с потенциально изменяемым состоянием и связанными с ним метаданными (представлениями), что и вызывает ошибку "передачи мутабельного значения".

Немутабельность Массива

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

  1. Прямое хранение значений: Элементы Массива представляют собой непосредственные значения (или ссылки на другие объекты), а не структуру с дополнительными свойствами, как у СписокЗначений. Массив — это просто контейнер для упорядоченного набора элементов.
  2. Отсутствие метаданных: Массив не имеет встроенных представлений, флагов или других метаданных для каждого элемента, которые могли бы быть изменены независимо от самого значения. Изменение элемента массива не приводит к изменению "представления" или других метаданных самого массива, так как таковых просто нет.
  3. Простота сериализации: Из-за своей относительно простой структуры Массив легче сериализовать и десериализовать между клиентским и серверным контекстами, так как платформа может просто скопировать значения элементов, не беспокоясь о их "представлении" или внутреннем состоянии, которое могло бы измениться.

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

Особенности поведения СписокЗначений в обычном и управляемом приложении

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

  1. В обычном приложении: В этом режиме СписокЗначений чаще всего считается мутабельным, что приводит к ошибкам при попытке прямой передачи на сервер. Это связано с архитектурой обычных форм, где многие объекты форм и элементы управления имеют прямую привязку к данным. Платформа в этом случае более строго контролирует изменения данных между клиентом и сервером, чтобы избежать рассинхронизации. Обычные формы исторически более тесно связаны с серверным контекстом и не имеют такой четкой границы сериализации данных, как управляемые формы.
  2. В управляемом приложении: Механизм работы с формами и данными в управляемом приложении существенно отличается. Передача данных между клиентом и сервером осуществляется через механизмы сериализации и десериализации данных. В этом контексте СписокЗначений может не рассматриваться как мутабельный объект, если он используется для хранения простых данных. Это происходит потому, что при передаче он "обезличивается" и преобразуется в сериализованный вид. Однако, важно отметить, что общие правила по передаче мутабельных значений все равно применимы: если СписокЗначений содержит ссылки на объекты базы данных или другие мутабельные типы, проблема может возникнуть снова, так как сама платформа может пытаться "схлопнуть" эти ссылки или объекты в процессе сериализации, что не всегда допустимо. То есть, даже в управляемых формах, если внутри СписокЗначений лежат изменяемые объекты, то проблема может проявиться. Если же СписокЗначений содержит только примитивные типы (числа, строки, булево) или другие немутабельные типы (например, даты, Массивы простых типов), то проблем с передачей в управляемых формах может и не быть.

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

Рекомендуемые практики и обходные пути для передачи мутабельных значений

Теперь, когда мы понимаем причины проблемы, давайте рассмотрим эффективные способы ее решения. Наша цель – передать необходимые данные с клиента на сервер, избегая ошибок мутабельности.

1. Преобразование в немутабельный тип

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

Рассмотрим, как мы можем это сделать. Мы создадим новый Массив и добавим в него элементы из нашего СписокЗначений.


&НаКлиенте
Процедура ПередатьСписокНаСервер_КакМассивПростыхЗначений()
    // Создаем СписокЗначений с несколькими элементами
    СписокЗн = Новый СписокЗначений();
    СписокЗн.Добавить("Ключ1", "Представление для Ключа1");
    СписокЗн.Добавить("Ключ2", "Представление для Ключа2");
    СписокЗн.Добавить("Ключ3", "Представление для Ключа3");

    // Создаем пустой Массив, который будет передан на сервер
    МассивДляПередачи = Новый Массив();
    
    // Перебираем элементы СписокЗначений и добавляем их значения в Массив
    Для Каждого ЭлементСписка Из СписокЗн Цикл
        МассивДляПередачи.Добавить(ЭлементСписка.Значение);
    КонецЦикла;

    // Теперь передаем Массив, который является немутабельным, на сервер
    РезультатНаСервере = ПолучитьДанныеСервераИзМассиваПростыхЗначений(МассивДляПередачи);
    Сообщить("Результат с сервера: " + РезультатНаСервере);
КонецПроцедуры

&НаСервере
Функция ПолучитьДанныеСервераИзМассиваПростыхЗначений(ПараметрМассив)
    // Здесь мы можем работать с переданным массивом на сервере
    Сообщить("На сервер получен массив из " + ПараметрМассив.Количество() + " элементов.");
    Сообщить("Содержимое массива:");
    Для Каждого ЭлементМассива Из ПараметрМассив Цикл
        Сообщить("- " + ЭлементМассива);
    КонецЦикла;
    Возврат "Данные (простые значения) успешно обработаны на сервере.";
КонецФункции

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


&НаКлиенте
Процедура ПередатьСписокНаСервер_КакМассивСтруктур()
    // Создаем СписокЗначений с несколькими элементами и их представлениями
    СписокЗн = Новый СписокЗначений();
    СписокЗн.Добавить("Ключ1", "Представление для Ключа1", Ложь, "ИмяФайлаКартинки1");
    СписокЗн.Добавить("Ключ2", "Представление для Ключа2", Истина, "ИмяФайлаКартинки2");

    // Создаем пустой Массив для структур
    МассивДляПередачи = Новый Массив();
    
    // Перебираем элементы СписокЗначений
    Для Каждого ЭлементСписка Из СписокЗн Цикл
        // Создаем структуру для каждого элемента, копируя необходимые свойства
        СтруктураЭлемента = Новый Структура("Значение, Представление, Пометка", 
                                             ЭлементСписка.Значение, 
                                             ЭлементСписка.Представление, 
                                             ЭлементСписка.Пометка);
        // Можем добавить и другие свойства, если они нужны
        // СтруктураЭлемента.Вставить("Картинка", ЭлементСписка.Картинка); 
        
        МассивДляПередачи.Добавить(СтруктураЭлемента);
    КонецЦикла;

    // Передаем Массив структур на сервер
    РезультатНаСервере = ПолучитьДанныеСервераИзМассиваСтруктур(МассивДляПередачи);
    Сообщить("Результат с сервера: " + РезультатНаСервере);
КонецПроцедуры

&НаСервере
Функция ПолучитьДанныеСервераИзМассиваСтруктур(ПараметрМассив)
    // На сервере мы получаем массив, состоящий из структур
    Сообщить("На сервер получен массив структур из " + ПараметрМассив.Количество() + " элементов.");
    Сообщить("Содержимое массива структур:");
    Для Каждого СтруктураЭлемента Из ПараметрМассив Цикл
        Сообщить("- Значение: " + СтруктураЭлемента.Значение + 
                 ", Представление: " + СтруктураЭлемента.Представление + 
                 ", Пометка: " + СтруктураЭлемента.Пометка);
    КонецЦикла;
    Возврат "Данные (структуры) успешно обработаны на сервере.";
КонецФункции

2. Использование временного хранилища

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

  1. Помещение во временное хранилище: На стороне, откуда мы хотим передать мутабельное значение, мы помещаем его во временное хранилище с помощью функции ПоместитьВоВременноеХранилище(). Эта функция возвращает уникальный адрес (строку), который затем может быть передан на другую сторону.
  2. Получение из временного хранилища: На принимающей стороне мы используем функцию ПолучитьИзВременногоХранилища(), передавая ей полученный адрес. Функция вернет объект, ранее помещенный в хранилище.

&НаКлиенте
Процедура ПередатьСписокЧерезВременноеХранилище()
    // Создаем мутабельный СписокЗначений
    СписокЗн = Новый СписокЗначений();
    СписокЗн.Добавить("Элемент А");
    СписокЗн.Добавить("Элемент Б");

    // Помещаем СписокЗначений во временное хранилище
    // АдресВХранилище - это строковый идентификатор, который можно передать на сервер
    АдресВХранилище = ПоместитьВоВременноеХранилище(СписокЗн);
    
    Сообщить("Список значений помещен во временное хранилище по адресу: " + АдресВХранилище);

    // Передаем адрес хранилища на сервер
    РезультатНаСервере = ПолучитьДанныеСервераИзВременногоХранилища(АдресВХранилище);
    Сообщить("Результат с сервера: " + РезультатНаСервере);
КонецПроцедуры

&НаСервере
Функция ПолучитьДанныеСервераИзВременногоХранилища(АдресВХранилище)
    Сообщить("На сервер получен адрес временного хранилища: " + АдресВХранилище);
    
    // Получаем СписокЗначений из временного хранилища по адресу
    СписокЗнНаСервере = ПолучитьИзВременногоХранилища(АдресВХранилище);

    Если СписокЗнНаСервере <> Неопределено Тогда
        Сообщить("Список значений успешно получен с сервера.");
        Для Каждого Элемент Из СписокЗнНаСервере Цикл
            Сообщить("- " + Элемент.Значение);
        КонецЦикла;
        Возврат "Данные из временного хранилища успешно обработаны на сервере.";
    Иначе
        Возврат "Не удалось получить данные из временного хранилища.";
    КонецЕсли;
КонецФункции

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

3. Сериализация в строку (XML или JSON)

Если данные могут быть представлены в виде структурированной строки (например, в формате XML или JSON), мы можем сериализовать мутабельный объект в строку на одной стороне и десериализовать его на другой. Этот подход особенно полезен для обмена данными с внешними системами, но также может быть использован и для клиент-серверного взаимодействия в 1С.

  1. Сериализация на клиенте: Используем объекты ЗаписьXML / ЧтениеXML или ЗаписьJSON / ЧтениеJSON для преобразования данных в строковое представление.
  2. Передача строки: Полученная строка (XML или JSON) является простым немутабельным типом данных и может быть безопасно передана на сервер.
  3. Десериализация на сервере: На сервере мы восстанавливаем исходный объект из полученной строки, используя соответствующие объекты ЧтениеXML / ЗаписьXML или ЧтениеJSON / ЗаписьJSON.

&НаКлиенте
Процедура ПередатьСписокЧерезJSON()
    СписокЗн = Новый СписокЗначений();
    СписокЗн.Добавить("Первое значение", "Первое представление");
    СписокЗн.Добавить("Второе значение", "Второе представление");

    // Создаем Массив структур для сериализации, так как СписокЗначений напрямую в JSON не сериализуется
    МассивСтруктур = Новый Массив();
    Для Каждого ЭлементСписка Из СписокЗн Цикл
        СтруктураЭлемента = Новый Структура("Значение, Представление", ЭлементСписка.Значение, ЭлементСписка.Представление);
        МассивСтруктур.Добавить(СтруктураЭлемента);
    КонецЦикла;

    // Сериализуем Массив структур в строку JSON
    ЗаписьJSON = Новый ЗаписьJSON();
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, МассивСтруктур);
    СтрокаJSON = ЗаписьJSON.Закрыть();
    
    Сообщить("Список значений сериализован в JSON-строку: " + СтрокаJSON);

    // Передаем JSON-строку на сервер
    РезультатНаСервере = ПолучитьДанныеСервераИзJSON(СтрокаJSON);
    Сообщить("Результат с сервера: " + РезультатНаСервере);
КонецПроцедуры

&НаСервере
Функция ПолучитьДанныеСервераИзJSON(СтрокаJSON)
    Сообщить("На сервер получена JSON-строка: " + СтрокаJSON);

    // Десериализуем JSON-строку обратно в Массив структур
    ЧтениеJSON = Новый ЧтениеJSON();
    ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
    МассивСтруктурНаСервере = ПрочитатьJSON(ЧтениеJSON);
    ЧтениеJSON.Закрыть();

    Если ТипЗнч(МассивСтруктурНаСервере) = Тип("Массив") Тогда
        Сообщить("Массив структур успешно десериализован на сервере.");
        Для Каждого СтруктураЭлемента Из МассивСтруктурНаСервере Цикл
            Сообщить("- Значение: " + СтруктураЭлемента.Значение + ", Представление: " + СтруктураЭлемента.Представление);
        КонецЦикла;
        Возврат "Данные из JSON успешно обработаны на сервере.";
    Иначе
        Возврат "Не удалось десериализовать данные из JSON.";
    КонецЕсли;
КонецФункции

Метод сериализации требует чуть больше кода, но обеспечивает гибкость и является стандартным способом обмена данными во многих сценариях.

4. Использование реквизитов формы (для контекстных функций)

В некоторых очень специфических случаях, особенно при работе с управляемыми формами, мы можем использовать реквизиты самой формы для обмена мутабельными данными. Если мы создадим реквизит формы с типом СписокЗначений, то при вызове серверной процедуры формы (контекстный вызов, например, с &НаСервереБезКонтекста) этот реквизит формы может быть доступен на сервере.

Однако, этот подход имеет серьезные ограничения:

  1. Он работает только для контекстных серверных вызовов формы, то есть когда серверная функция или процедура вызывается из модуля формы и имеет доступ к данным этой формы.
  2. Передача данных происходит неявно в рамках контекста формы. Это может быть менее прозрачно, чем явное преобразование или использование временного хранилища.
  3. Для неконтекстных серверных вызовов (&НаСервереБезКонтекста), или если мы хотим передать СписокЗначений в общий модуль на сервере, этот подход не подойдет.

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

В заключение, понимание концепции мутабельности и различий между типами СписокЗначений и Массив в 1С является ключом к избеганию ошибок при клиент-серверном взаимодействии. Выбирая подходящий метод для передачи данных, мы можем обеспечить стабильную и корректную работу наших приложений. Для более детального понимания клиент-серверного разделения подойдёт материал по управлению контекстом выполнения кода в управляемых формах.

← На главную