В практике разработки на платформе 1С:Предприятие часто возникает задача передачи данных между различными формами. Традиционно для этого используются механизмы временного хранилища и глобальных оповещений. Однако при попытке извлечь эти данные из внешних обработок или других объектов конфигурации разработчики сталкиваются со сложностями: обработчики событий не срабатывают, а адреса временного хранилища оказываются недоступными.
В этой статье мы подробно разберем, как устроен этот механизм, проанализируем неочевидные ошибки, из-за которых могут происходить утечки памяти на сервере, выясним, почему не отрабатывает процедура ОбработкаОповещения, и рассмотрим наиболее изящные и современные архитектурные решения этой задачи.
Рассмотрим сценарий, описанный в конфигурации «Учет в управляющих компаниях ЖКХ, ТСЖ и ЖСК». Из документа открывается вспомогательная общая форма для ввода сведений о собственности. При ее закрытии данные передаются обратно в документ следующим образом:
&НаСервере
Функция ПолучитьАдресВХранилище()
СтруктураДанных = Новый Структура;
СтруктураДанных.Вставить("Жилец", Жилец);
СтруктураДанных.Вставить("Собственник", Собственник);
СтруктураДанных.Вставить("ФИО", ФИОСобственника);
СтруктураДанных.Вставить("ОснованиеПолученияВСобственность", ОснованиеПолученияВСобственность);
СтруктураДанных.Вставить("ВидДокумента", ВидДокумента);
СтруктураДанных.Вставить("НомерДокумента", НомерДокумента);
СтруктураДанных.Вставить("ДатаДокумента", ДатаДокумента);
Возврат ПоместитьВоВременноеХранилище(СтруктураДанных, Новый УникальныйИдентификатор);
КонецФункции
Процедура ПриЗакрытии(ЗавершениеРаботы)
АдресВХранилище = ПолучитьАдресВХранилище();
Оповестить("ПередачаСведенийОСобственностиПриУпрощенномУчете", АдресВХранилище);
КонецПроцедуры
В форме документа-родителя это оповещение перехватывается стандартным обработчиком:
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ПередачаСведенийОСобственностиПриУпрощенномУчете" Тогда
Если ТипЗнч(Параметр) = Тип("Строка") Тогда
СтруктураСведений = ПолучитьИзВременногоХранилища(Параметр);
ЗаписатьСведенияОСобственности(СтруктураСведений);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Давайте разберем слабые места этой схемы и выясним, почему попытка «вклиниться» во временное хранилище из внешней обработки — не всегда правильный путь.
Обратим внимание на строку в коде вспомогательной формы:
ПоместитьВоВременноеХранилище(СтруктураДанных, Новый УникальныйИдентификатор);
Использование конструкции Новый УникальныйИдентификатор в качестве второго параметра — это серьезная архитектурная ошибка. Давайте разберем, как работает временное хранилище в 1С:
ЭтаФорма.УникальныйИдентификатор), платформа автоматически очищает эти данные из памяти сервера сразу же, как только форма закрывается.Если пользователи будут активно работать с базой данных, открывая и закрывая эту форму десятки раз в день, неиспользуемые структуры данных будут накапливаться в оперативной памяти сервера rphost. При работе в многопользовательском режиме это неизбежно приведет к деградации производительности сервера приложений 1С.
Правильное решение: всегда привязывайте адрес к уникальному идентификатору формы-владельца или самой закрывающейся формы, если это предусмотрено логикой передачи:
ПоместитьВоВременноеХранилище(СтруктураДанных, ЭтаФорма.УникальныйИдентификатор);
Попытки разработчика добавить процедуру ОбработкаОповещения в модуль формы своей внешней обработки часто заканчиваются неудачей — точка останова внутри процедуры просто не срабатывает. Выясним причины этого поведения.
Для того чтобы форма внешней обработки могла перехватить глобальное событие, должны выполняться два обязательных условия:
ОбработкаОповещения в коде модуля. Необходимо открыть форму внешней обработки в конфигураторе, перейти на панель свойств формы, найти событие «ОбработкаОповещения» и связать его с вашей процедурой в модуле. Только тогда платформа зарегистрирует обработчик.Оповестить() работает по принципу «выстрелил и забыл». Оповещение доставляется мгновенно и только тем формам, которые уже созданы и открыты в текущем сеансе на клиенте. Если вы открыли форму внешней обработки после того, как вспомогательное окно закрылось и отправило сигнал, ваша обработка ничего не получит — событие уже ушло.Пытаться перехватить данные «на лету» в момент их передачи через оперативную память — это избыточное усложнение архитектуры. Временное хранилище создается лишь для кратковременного транзита информации между диалоговыми окнами.
Посмотрим на процедуру, которая вызывается в документе после получения оповещения:
Процедура ЗаписатьСведенияОСобственности(СтруктураСведений)
Если Не СтруктураСведений.Свойство("Собственник") Тогда
Возврат;
КонецЕсли;
СтрокиСобственника = Объект.Главная.НайтиСтроки(Новый Структура("Проживающий", СтруктураСведений.Жилец));
Если Не СтрокиСобственника.Количество() = 0 Тогда
СтрокаСобственника = СтрокиСобственника.Получить(0);
СтрокаСобственника.ОснованиеПолученияВСобственность = СтруктураСведений.ОснованиеПолученияВСобственность;
СтрокаСобственника.ВидДокументаСобственности = СтруктураСведений.ВидДокумента;
СтрокаСобственника.НомерДокументаСобственности = СтруктураСведений.НомерДокумента;
СтрокаСобственника.ДатаДокументаСобственности = СтруктураСведений.ДатаДокумента;
// ... заполнение других реквизитов ...
КонецЕсли;
КонецПроцедуры
Как мы видим, данные переносятся непосредственно в табличную часть Главная самого документа, из которого вызывалась форма. Следовательно, при проведении или записи документа эти сведения сохраняются в базе данных навсегда (в самом документе или связанных регистрах сведений).
Для построения отчета или извлечения этих данных во внешней обработке нам не нужно перехватывать временное хранилище. Достаточно написать стандартный запрос к табличной части документа. Рассмотрим пример такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Ссылка.Ссылка КАК ДокументСсылка,
| Ссылка.Дата КАК ДатаДокумента,
| Главная.Проживающий КАК Жилец,
| Главная.ОснованиеПолученияВСобственность КАК Основание,
| Главная.ВидДокументаСобственности КАК ВидДокумента,
| Главная.НомерДокументаСобственности КАК НомерДокумента,
| Главная.ДатаДокументаСобственности КАК ДатаДокументаСобственности
|ИЗ
| Документ.НачислениеУслугЖКХ.Главная КАК Главная
|ГДЕ
| Ссылка.Проведен"; // Укажите имя вашего вида документа
РезультатЗапроса = Запрос.Выполнить();
Именно этот подход гарантирует стабильность работы решения, так как чтение данных происходит напрямую из транзакционной базы данных, а не из изменчивой оперативной памяти клиентских сеансов.
Если вы хотите полностью отказаться от ненадежного связывания форм через временное хранилище и глобальное событие Оповестить(), используйте современный стандарт разработки 1С — механизм асинхронных вызовов через объект ОписаниеОповещения.
Рассмотрим, как переписать этот функционал по стандартам БСП (Библиотеки стандартных подсистем):
1. Открытие вспомогательной формы из модуля документа:
Вместо простого открытия мы передаем объект-оповещение, указывающий, какую процедуру нужно выполнить, когда вспомогательное окно закроется.
&НаКлиенте
Процедура ВвестиСведенияОСобственности(Команда)
ПараметрыФормы = Новый Структура;
// Передаем необходимые исходные данные во вспомогательную форму
ПараметрыФормы.Вставить("Жилец", Элементы.Главная.ТекущиеДанные.Проживающий);
ОповещениеЗавершения = Новый ОписаниеОповещения("ПослеВводаСведенийОСобственности", ЭтотОбъект);
ОткрытьФорму("ОбщаяФорма.ВводСведенийОСобственности", ПараметрыФормы, ЭтотОбъект, , , , ОповещениеЗавершения);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВводаСведенийОСобственности(Результат, ДополнительныеПараметры) Экспорт
Если ТипЗнч(Результат) = Тип("Структура") Тогда
// Мы получили чистые данные напрямую без использования Временного Хранилища!
ЗаписатьСведенияОСобственностиНаСервере(Результат);
КонецЕсли;
КонецПроцедуры
2. Возврат данных из вспомогательной общей формы:
Во вспомогательной форме нам вообще больше не требуется создавать временное хранилище и засорять серверные ресурсы. При закрытии формы мы передаем сформированную структуру прямо в метод Закрыть().
&НаКлиенте
Процедура ЗаписатьИЗакрыть(Команда)
СтруктураРезультата = Новый Структура;
СтруктураРезультата.Вставить("Жилец", Объект.Жилец);
СтруктураРезультата.Вставить("Собственник", Объект.Собственник);
СтруктураРезультата.Вставить("ОснованиеПолученияВСобственность", Объект.ОснованиеПолученияВСобственность);
СтруктураРезультата.Вставить("ВидДокумента", Объект.ВидДокумента);
СтруктураРезультата.Вставить("НомерДокумента", Объект.НомерДокумента);
СтруктураРезультата.Вставить("ДатаДокумента", Объект.ДатаДокумента);
// Закрываем форму и автоматически передаем структуру в callback-процедуру родителя
Закрыть(СтруктураРезультата);
КонецПроцедуры
Преимущества паттерна ОписаниеОповещения: