При разработке в 1С:Предприятии часто возникает необходимость передавать данные между различными серверными вызовами одной и той же управляемой формы. Эта задача кажется простой, но становится весьма нетривиальной, когда речь заходит о несериализуемых объектах или о сохранении конкретного экземпляра объекта со всеми его внутренними переменными между вызовами. В этом руководстве мы подробно рассмотрим, как работают механизмы обмена данными на сервере, какие стандартные подходы существуют и как можно подойти к решению сложных случаев.
Давайте вместе разберемся в этой непростой теме, чтобы ваши серверные процедуры могли эффективно обмениваться необходимой информацией.
Для начала, очень важно осознать, как платформа 1С:Предприятие организует взаимодействие между клиентом и сервером в контексте управляемых форм. Управляемые формы, в отличие от форм обычных приложений, создаются и значительная часть их данных заполняется именно на сервере. Клиентское приложение по сути является лишь "отображением" этой серверной формы.
Ключевой момент, который был озвучен в обсуждении и который имеет фундаментальное значение для нашей задачи, заключается в следующем:
rphost), в котором была создана форма. Платформа не "мигрирует" форму между различными рабочими процессами при серверных вызовах, связанных с контекстом этой формы. Это критически важно, поскольку это означает, что если вы в одной серверной процедуре формы создали некий объект и сохранили его в контексте формы (например, в реквизите формы), то в другой серверной процедуре этой же формы, вызванной из клиентской части, вы получите доступ к тому же самому экземпляру объекта.Этот факт о немигрируемости формы и сохранении ее контекста является основой для любых решений, направленных на удержание состояния между серверными вызовами одной формы, даже если речь идет о сложных объектах.
Сериализация — это процесс преобразования структуры данных или объекта в последовательность битов (например, для XML, JSON или внутреннего формата 1С), что позволяет сохранить их состояние или передать их по сети. Десериализация — это обратный процесс восстановления объекта или структуры данных из этой последовательности.
В 1С не все типы данных могут быть сериализованы, или они сериализуются не полностью. Возможность сериализации для многих объектов указывается в синтакс-помощнике. Разберем основные моменты:
СправочникОбъект, ДокументОбъект): Сами по себе прикладные объекты не сериализуются в полном объеме, как единый "снимок" их внутреннего состояния. Это означает, что их экспортные переменные, особенно если они содержат несериализуемые типы (например, COM-соединения), не будут сохранены или переданы при стандартной сериализации. Сериализуются только *данные* объекта, которые могут быть представлены в реквизитах формы или в форматах типа XML/JSON.ТаблицаЗначений и ДеревоЗначений: Эти объекты являются серверными и сериализуются для передачи между сервером и клиентом. Однако на клиенте они не существуют как объекты типов ТаблицаЗначений или ДеревоЗначений. Платформа преобразует их данные в формат, пригодный для работы на клиенте (например, ДанныеФормыКоллекция).COMОбъект): Объекты, такие как COM-соединения, не являются сериализуемыми. Попытка сохранить их напрямую между серверными вызовами стандартными средствами (например, в реквизитах формы или временном хранилище) приведет к ошибкам.Важно понимать, что когда вы передаете параметры с клиента на сервер (и обратно), всегда передаются *копии* параметров. Если параметр указан с модификатором Знач, его значение передается только при вызове и не возвращается обратно. Это фундаментальный принцип, который мы должны учитывать.
Существует несколько стандартных и наиболее часто используемых способов для обмена данными между серверными процедурами формы:
Это самый простой и очевидный способ для хранения сериализуемых данных между серверными вызовами. Данные, помещенные в реквизит формы, сохраняются в контексте этой формы на сервере и остаются доступными в последующих серверных вызовах. Например, если у вас есть реквизит формы типа "Строка" или "Число", вы можете записать в него значение в одной серверной процедуре и прочитать в другой.
Пример:
// В первой серверной процедуре (например, по Кнопке1)
&НаСервере
Процедура Кнопка1НаСервере()
ЭтотОбъект.МойРеквизит = "Какие-то данные";
КонецПроцедуры
// Во второй серверной процедуре (например, по Кнопке2)
&НаСервере
Процедура Кнопка2НаСервере()
Сообщить("Получены данные: " + ЭтотОбъект.МойРеквизит);
КонецПроцедуры
Этот подход работает отлично для всех сериализуемых типов данных.
Временное хранилище предназначено для краткосрочного хранения данных на сервере в рамках одного пользовательского сеанса. Данные могут храниться как в памяти, так и во временных файлах сервера, не попадая в основную базу данных.
ПоместитьВоВременноеХранилище(), который возвращает уникальный адрес. Для получения данных используется метод ПолучитьИзВременногоХранилища() по этому адресу.Пример:
// В первой серверной процедуре
&НаСервере
Процедура Кнопка1НаСервере()
МоиДанные = Новый Структура("Ключ1, Ключ2", "Значение1", 123);
АдресХранилища = ПоместитьВоВременноеХранилище(МоиДанные, ЭтаФорма.УникальныйИдентификатор);
ЭтаФорма.РеквизитДляАдреса = АдресХранилища; // Сохраняем адрес в реквизите формы
КонецПроцедуры
// Во второй серверной процедуре
&НаСервере
Процедура Кнопка2НаСервере()
Если Не ЗначениеЗаполнено(ЭтаФорма.РеквизитДляАдреса) Тогда
Сообщить("Данные не найдены во временном хранилище.");
Возврат;
КонецЕсли;
МоиДанныеИзХранилища = ПолучитьИзВременногоХранилища(ЭтаФорма.РеквизитДляАдреса);
Сообщить("Полученные данные: " + МоиДанныеИзХранилища.Ключ1 + ", " + МоиДанныеИзХранилища.Ключ2);
КонецПроцедуры
Временное хранилище поддерживает практически любые сериализуемые данные.
РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы()Эти методы позволяют преобразовывать реквизиты формы в прикладные объекты и обратно. Они незаменимы, когда вы работаете с объектами информационной базы (например, СправочникОбъект, ДокументОбъект) на форме.
РеквизитФормыВЗначение(): Этот метод преобразует реквизит формы в прикладной объект на сервере. Важно понимать, что он создает копию объекта, заполняя ее данными из формы (включая еще не записанные в базу изменения). Это не ссылка на исходный объект в памяти.ЗначениеВРеквизитФормы(): Выполняет обратное преобразование прикладного объекта в реквизит формы. Изменения, внесенные в объект, будут перенесены в реквизиты формы.Пример:
// Предположим, у нас есть основной реквизит формы 'Объект' типа СправочникОбъект.<любойСправочник>
// В первой серверной процедуре
&НаСервере
Процедура Кнопка1НаСервере()
// Получаем копию объекта из реквизитов формы
ОбъектИзФормы = РеквизитФормыВЗначение("Объект");
// Вносим изменения в эту копию
ОбъектИзФормы.Наименование = "Новое наименование (из первой процедуры)";
// Сохраняем измененные данные обратно в реквизиты формы
ЗначениеВРеквизитФормы(ОбъектИзФормы, "Объект");
КонецПроцедуры
// Во второй серверной процедуре
&НаСервере
Процедура Кнопка2НаСервере()
// Снова получаем копию объекта из реквизитов формы
ОбъектИзФормы = РеквизитФормыВЗначение("Объект");
// Здесь мы увидим изменения, сделанные в первой процедуре, так как они были сохранены в реквизитах формы
Сообщить("Наименование объекта: " + ОбъектИзФормы.Наименование);
КонецПроцедуры
Этот подход хорошо работает для данных, которые могут быть представлены реквизитами объекта. Однако, как отмечал автор темы, он не позволяет сохранить *единый экземпляр* объекта с его внутренним состоянием, включая несериализуемые экспортные переменные.
Для долгосрочного хранения данных, а также для передачи сложных, несериализуемых или требующих сохранения между сеансами данных, самым очевидным и надежным способом является их запись в базу данных. Это обеспечивает их персистентность и доступность из разных сеансов и рабочих процессов. Если данные действительно должны "пережить" клиентский сеанс или быть доступными из других сеансов, это лучший вариант.
Например, можно создать временный регистр сведений или вспомогательный справочник для хранения промежуточных данных, а затем использовать ссылки на эти данные.
Исходная проблема автора заключалась в сохранении *конкретного экземпляра* объекта (например, СправочникОбъект) со всеми его свойствами, включая несериализуемые экспортные переменные (как в примере с COM-соединением), между разными серверными вызовами одной и той же формы. Стандартные методы, которые мы рассмотрели, не позволяют этого сделать в полной мере, так как они работают с сериализуемыми данными или создают копии объектов.
Однако, как мы уже выяснили, серверный экземпляр формы живет в рамках одного рабочего процесса. Это открывает возможности для сохранения состояния внутри этого процесса.
Если мы не говорим о передаче объекта между клиентом и сервером (тогда нужна сериализация), а о сохранении объекта *внутри* сервера между вызовами формы, которые всегда происходят в одном и том же рабочем процессе, то теоретически мы можем хранить ссылку на объект в реквизите формы произвольного типа. Это, собственно, и является основой "нетривиального решения", о котором говорил автор, хотя он и не поделился его кодом.
Предполагается, что вы создаете реквизит формы с типом Произвольный. В первой серверной процедуре вы инициализируете ваш сложный объект (например, СправочникОбъект) и присваиваете его этому реквизиту. В последующих серверных процедурах вы обращаетесь к этому же реквизиту, чтобы получить тот же самый экземпляр объекта. Платформа 1С действительно позволяет хранить ссылки на серверные объекты в реквизитах формы произвольного типа, и эти ссылки будут указывать на один и тот же экземпляр, пока форма жива на сервере. Однако, если этот объект содержит другие несериализуемые объекты (например, COMОбъект), вы не сможете передать этот реквизит на клиент или сохранить его во временное хранилище без ошибок.
Важно: Такой подход работает только в рамках серверных вызовов *одной и той же формы* в рамках *одного и того же сеанса*. Он не применим для передачи объекта в другой сеанс, другую форму или для сохранения после перезапуска рабочего процесса.
Один из участников обсуждения (сообщение 31) предложил практический подход, используемый в типовых конфигурациях, который позволяет "сохранить" сложный объект между серверными вызовами, даже если он содержит несериализуемые данные или мы хотим сохранить его экспортные переменные:
COMОбъект, или любое другое внутреннее состояние, которое не сериализуется напрямую вместе с объектом.ВременноеХранилище: Эту структуру помещаем во временное хранилище. Адрес временного хранилища сохраняем в реквизите формы.СправочникОбъект) преобразуем в реквизит формы с помощью ЗначениеВРеквизитФормы().РеквизитФормыВЗначение().Пример реализации концепции (без детального кода COM-объекта, так как он не показан):
Предположим, у нас есть справочник "МоиОбъекты" и в его модуле объекта есть экспортная переменная МояПеременная, которую мы хотим сохранить.
Модуль объекта справочника "МоиОбъекты":
// В модуле объекта справочника "МоиОбъекты"
Перем МояПеременная Экспорт;
В форме, в первой серверной процедуре:
// Предположим, у нас есть реквизит формы 'ОбъектМоиОбъекты' типа СправочникОбъект.МоиОбъекты
// и реквизит формы 'АдресДопДанных' типа Строка
&НаСервере
Процедура ЗаполнитьИСохранитьДанныеОбъектаНаСервере()
// 1. Получаем объект из реквизита формы (или создаем новый)
ОбъектМоиОбъекты = РеквизитФормыВЗначение("ОбъектМоиОбъекты");
Если ОбъектМоиОбъекты.ЭтоНовый() Тогда
ОбъектМоиОбъекты.Наименование = "Тестовый объект";
ОбъектМоиОбъекты.Записать(); // Пример записи нового объекта
КонецЕсли;
// Инициализируем некую переменную объекта (например, сложный или динамический объект)
// В данном случае, это просто строка для примера, но может быть и ссылка на COM,
// которую нужно будет восстанавливать специальным образом
ОбъектМоиОбъекты.МояПеременная = "Значение, которое хотим сохранить";
// Если МояПеременная была COM-объектом, здесь мы бы создавали COM-соединение
// 2. Формируем структуру с дополнительными данными
ДополнительныеДанные = Новый Структура();
ДополнительныеДанные.Вставить("ЗначениеМоейПеременной", ОбъектМоиОбъекты.МояПеременная);
// Добавляем другие несериализуемые или важные внутренние состояния, если есть
// 3. Помещаем структуру во временное хранилище
АдресХранилища = ПоместитьВоВременноеХранилище(ДополнительныеДанные, ЭтаФорма.УникальныйИдентификатор);
ЭтаФорма.АдресДопДанных = АдресХранилища; // Сохраняем адрес в реквизите формы
// 4. Сохраняем (обновляем) данные объекта обратно в реквизит формы
ЗначениеВРеквизитФормы(ОбъектМоиОбъекты, "ОбъектМоиОбъекты");
Сообщить("Данные и ссылка на доп. данные сохранены.");
КонецПроцедуры
В форме, во второй серверной процедуре:
&НаСервере
Процедура ПолучитьИВосстановитьДанныеОбъектаНаСервере()
// 1. Восстанавливаем объект из реквизита формы
ОбъектМоиОбъекты = РеквизитФормыВЗначение("ОбъектМоиОбъекты");
Если Не ЗначениеЗаполнено(ЭтаФорма.АдресДопДанных) Тогда
Сообщить("Адрес дополнительных данных не найден.");
Возврат;
КонецЕсли;
// 2. Получаем дополнительные данные из временного хранилища
ДополнительныеДанныеИзХранилища = ПолучитьИзВременногоХранилища(ЭтаФорма.АдресДопДанных);
Если ДополнительныеДанныеИзХранилища = Неопределено Тогда
Сообщить("Дополнительные данные не найдены во временном хранилище.");
Возврат;
КонецЕсли;
// 3. Заполняем экспортные переменные объекта
ОбъектМоиОбъекты.МояПеременная = ДополнительныеДанныеИзХранилища.ЗначениеМоейПеременной;
// Если это был COM-объект, здесь мы бы не просто присваивали значение,
// а восстанавливали бы COM-соединение, используя параметры из ДополнительныеДанныеИзХранилища.
Сообщить("Объект восстановлен. Значение МояПеременная: " + ОбъектМоиОбъекты.МояПеременная);
КонецПроцедуры
Этот подход позволяет "сохранить" и "восстановить" состояние объекта между вызовами, даже если часть этого состояния не сериализуется напрямую, за счет его раздельной обработки.
В особо сложных случаях, когда требуется управлять долгоживущими несериализуемыми объектами, или выполнять длительные операции, сохраняя состояние между вызовами, могут быть рассмотрены более сложные архитектурные решения:
COM-соединение). Взаимодействие с этим фоновым заданием будет осуществляться через сериализуемые параметры или другие механизмы обмена сообщениями (например, через регистр сведений, куда записываются команды для фонового задания, а фоновое задание в свою очередь записывает результаты). Это позволяет сохранять объект в одном рабочем процессе, а извне получать только указания и параметры, что с ним делать.Такие подходы значительно усложняют архитектуру, но предоставляют максимальную гибкость для работы с любыми типами объектов и сохранения их состояния.
Давайте подведем основные выводы из нашего обсуждения:
РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы(). Эти методы надежны и просты в использовании для сериализуемых данных.Обмен данными между серверными процедурами формы в 1С:Предприятии — это не всегда прямолинейная задача, особенно при работе со специфическими типами объектов. Однако глубокое понимание принципов работы платформы и умение комбинировать доступные механизмы позволяет эффективно решать даже самые нетривиальные задачи. Надеемся, что этот подробный разбор поможет вам в вашей работе!