Временное хранилище 1С — служебная область в данных сеанса на сервере. Вы кладёте туда таблицу, структуру или двоичные данные и получаете строку-адрес. Дальше между клиентом и сервером, между формами и из фонового задания передаётся только адрес, а данные остаются на сервере. Ниже — как поместить значение и получить его обратно, сколько оно живёт с идентификатором формы и без него, как вернуть таблицу из фонового задания и когда чистить хранилище самому.
С соседями не путайте: тип ХранилищеЗначения записывается в базу вместе с объектом (о хранилище значения), а временные таблицы запроса живут в менеджере временных таблиц (о временных таблицах).
ПодборТоваровВДокументПродажи при закрытии кладёт корзину в хранилище с идентификатором формы-владельца и отдаёт через ОповеститьОВыборе структуру с ключом АдресТоваровВХранилище. Заказ клиента в ОбработкаВыбора забирает таблицу на сервере и дописывает строки в «Товары».КонтрольУникальности и передаёт найденные совпадения параметром АдресВременногоХранилищаТаблицаНайдено.АдресКартинки.ДлительныеОперации.ВыполнитьФункцию возвращает структуру с полем АдресРезультата, куда фоновое задание кладёт результат.С хранилищем работают четыре метода глобального контекста, доступные и на сервере, и на клиенте:
ПоместитьВоВременноеХранилище(Данные, Адрес) — кладёт значение и возвращает адрес временного хранилища, обычную строку;ПолучитьИзВременногоХранилища(Адрес) — возвращает значение по адресу;УдалитьИзВременногоХранилища(Адрес) — удаляет значение, не дожидаясь срока;ЭтоАдресВременногоХранилища(Адрес) — проверяет, что строка является адресом хранилища.Само хранилище находится на сервере, поэтому помещение и получение на клиенте — это обращение к серверу. Значение должно сериализоваться: таблица и дерево значений, структура, массив, двоичные данные, табличный документ подходят. Запрос и МенеджерВременныхТаблиц не сериализуются, и сохранить их так между серверными вызовами не выйдет.
Срок жизни значения задаёт второй параметр:
| Второй параметр | Когда значение удаляется |
|---|---|
| не указан | после следующего обращения к серверу: вызова общего модуля, контекстного или внеконтекстного вызова из формы, вызова из модуля команды, получения формы |
УникальныйИдентификатор формы | при закрытии этой формы |
Новый УникальныйИдентификатор, не связанный с формой | в конце сеанса пользователя |
| адрес, полученный ранее | новое значение ложится по тому же адресу; если адрес выдан с идентификатором формы, значение удалится при её закрытии |
В любом случае значение можно убрать раньше через УдалитьИзВременногоХранилища. Хранилище принадлежит сеансу: в сеансе другого пользователя по тому же адресу ничего нет, после перезапуска программы старый адрес бесполезен. Поэтому адрес держат в реквизите формы или в переменной и не записывают в реквизиты объектов, регистры и константы.
У каждой управляемой формы есть свойство УникальныйИдентификатор — GUID, который не меняется, пока форма открыта. Его передают вторым параметром, когда нужно временное хранилище формы. В модуле формы свойство пишется без Новый. В процедурах &НаСервереБезКонтекста и в общих модулях свойств формы нет, поэтому идентификатор передают параметром. Где ещё пригодится GUID — в статье про уникальный идентификатор.
Обработка с тяжёлым запросом: если сложить результат в реквизит формы, таблица поедет на клиент и обратно при каждом контекстном вызове. Удобнее держать её на сервере, а в форме хранить только адрес — реквизит АдресДанных типа «Строка». Справочник «Номенклатура» есть в УТ, КА, ERP и БП; в другой конфигурации подставьте свой.
// Модуль формы обработки. Реквизиты формы:
// АдресДанных - Строка - адрес таблицы во временном хранилище;
// СтрокаПоиска - Строка - часть наименования для поиска.
&НаКлиенте
Процедура Подготовить(Команда)
Количество = ПодготовитьНаСервере();
ПоказатьОповещениеПользователя("Данные подготовлены", , СтрШаблон("Строк в таблице: %1", Количество));
КонецПроцедуры
&НаСервере
Функция ПодготовитьНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления";
Таблица = Запрос.Выполнить().Выгрузить();
// Идентификатор формы: таблица доступна в следующих серверных вызовах,
// пока форма открыта, и удаляется при её закрытии
АдресДанных = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
Возврат Таблица.Количество();
КонецФункции
&НаКлиенте
Процедура НайтиПоНаименованию(Команда)
Если Не ЭтоАдресВременногоХранилища(АдресДанных) Тогда
ПоказатьПредупреждение(, "Сначала нажмите «Подготовить».");
Возврат;
ИначеЕсли ПустаяСтрока(СтрокаПоиска) Тогда
ПоказатьПредупреждение(, "Введите часть наименования.");
Возврат;
КонецЕсли;
// На сервер уходят две строки: адрес и текст поиска
Найдено = СтрокиПоНаименованию(АдресДанных, СтрокаПоиска);
Если Найдено = Неопределено Тогда
ПоказатьПредупреждение(, "Данных уже нет: нажмите «Подготовить» ещё раз.");
Возврат;
ИначеЕсли Найдено.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Ничего не найдено.");
Возврат;
КонецЕсли;
Для Каждого Элемент Из Найдено Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон("%1 (код %2)", Элемент.Наименование, Элемент.Код);
Сообщение.Сообщить();
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СтрокиПоНаименованию(Знач Адрес, Знач Подстрока)
Таблица = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
Возврат Неопределено; // значения по адресу уже нет
КонецЕсли;
Результат = Новый Массив;
Подстрока = НРег(СокрЛП(Подстрока));
Для Каждого СтрокаТаблицы Из Таблица Цикл
Если СтрНайти(НРег(СтрокаТаблицы.Наименование), Подстрока) = 0 Тогда
Продолжить;
КонецЕсли;
// Тонкому клиенту - массив структур: таблицы значений на клиенте нет
Результат.Добавить(Новый Структура("Код, Наименование",
СтрокаТаблицы.Код, СтрокаТаблицы.Наименование));
Если Результат.Количество() = 50 Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Разбор ключевых строк:
ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор) привязывает таблицу к форме: она доступна в следующих серверных вызовах, пока окно открыто.&НаСервереБезКонтекста: на сервер уходят две строки — адрес и текст поиска, данные формы не передаются.ПолучитьИзВременногоХранилища вернёт Неопределено.ТаблицаЗначений в тонком и веб-клиенте нет, поэтому там держат только адрес, а получить из временного хранилища саму таблицу нужно в серверной процедуре.Схема та же, что в подборе УТ, только результат возвращается через Закрыть() и описание оповещения. Владелец передаёт в форму подбора адрес уже выбранных строк и получает обратно адрес результата. Обработка ПодборНоменклатуры здесь своя, не типовая, а в документе есть табличная часть «Товары» с колонками «Номенклатура» и «Количество».
// Форма документа-владельца. Табличная часть Товары: Номенклатура, Количество.
&НаКлиенте
Процедура ОткрытьПодбор(Команда)
ПараметрыФормы = Новый Структура;
// Уже выбранные строки передаём адресом, а не таблицей
ПараметрыФормы.Вставить("АдресВыбранных", ВыбранныеВХранилище());
Оповещение = Новый ОписаниеОповещения("ПодборЗавершение", ЭтотОбъект);
ОткрытьФорму("Обработка.ПодборНоменклатуры.Форма", ПараметрыФормы, ЭтотОбъект, , , ,
Оповещение, РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
КонецПроцедуры
&НаСервере
Функция ВыбранныеВХранилище()
Таблица = Объект.Товары.Выгрузить();
Возврат ПоместитьВоВременноеХранилище(Таблица.Скопировать(, "Номенклатура, Количество"),
УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура ПодборЗавершение(Результат, ДополнительныеПараметры) Экспорт
// Окно закрыли без переноса - результата нет
Если ТипЗнч(Результат) = Тип("Строка") И ЭтоАдресВременногоХранилища(Результат) Тогда
ПеренестиПодобранныеНаСервере(Результат);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПеренестиПодобранныеНаСервере(Знач Адрес)
Подобранные = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(Подобранные) <> Тип("ТаблицаЗначений") Тогда
Возврат;
КонецЕсли;
// Документ может быть открыт долго - копию в хранилище не держим
УдалитьИзВременногоХранилища(Адрес);
Объект.Товары.Очистить();
Для Каждого Подобранная Из Подобранные Цикл
ЗаполнитьЗначенияСвойств(Объект.Товары.Добавить(), Подобранная);
КонецЦикла;
Модифицированность = Истина;
КонецПроцедуры
// Форма обработки ПодборНоменклатуры. Реквизит формы Подобранные -
// ТаблицаЗначений с колонками Номенклатура и Количество.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Не Параметры.Свойство("АдресВыбранных") Тогда
Возврат;
КонецЕсли;
Адрес = Параметры.АдресВыбранных;
Если ТипЗнч(Адрес) <> Тип("Строка") Или Не ЭтоАдресВременногоХранилища(Адрес) Тогда
Возврат;
КонецЕсли;
Выбранные = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(Выбранные) = Тип("ТаблицаЗначений") Тогда
ЗначениеВРеквизитФормы(Выбранные, "Подобранные");
УдалитьИзВременногоХранилища(Адрес); // передача разовая
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура Перенести(Команда)
// Эта форма сейчас закроется: результат привязываем к владельцу
// (третий параметр ОткрытьФорму), иначе значение удалится вместе с формой подбора
Адрес = ПоместитьРезультат(ВладелецФормы.УникальныйИдентификатор);
Закрыть(Адрес);
КонецПроцедуры
&НаСервере
Функция ПоместитьРезультат(Знач ИдентификаторВладельца)
Таблица = РеквизитФормыВЗначение("Подобранные");
Возврат ПоместитьВоВременноеХранилище(Таблица, ИдентификаторВладельца);
КонецФункции
Главное — с каким идентификатором кладётся результат. По Закрыть() форма подбора закрывается, и значения, привязанные к её идентификатору, удаляются вместе с ней. Поэтому результат кладётся с идентификатором владельца, взятым через ВладелецФормы, — так же сделано в УТ 11.4. Передача разовая: форма подбора удаляет входящую копию сразу после чтения, владелец — результат после разбора. Про параметры формы и оповещение о закрытии — в статье как открыть форму программно.
Фоновое задание работает в отдельном сеансе, и хранилище формы ему не видно: по документации платформы данные временного хранилища одного сеанса другому недоступны. Исключение одно — задание может записать результат по адресу, который заранее создал родительский сеанс. Отсюда порядок:
ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);Процедура задания — в общем модуле с флажком «Сервер». Пример ищет в таблице из первого листинга позиции с одинаковым наименованием:
// Общий модуль ДублиНоменклатуры: флажок «Сервер», остальные сняты.
// Процедура выполняется в фоновом задании.
Процедура НайтиДубли(Знач Таблица, Знач АдресРезультата) Экспорт
// Таблица пришла параметром задания: это копия, а не данные формы
Таблица.Колонки.Добавить("Ключ", Новый ОписаниеТипов("Строка"));
Для Каждого СтрокаТаблицы Из Таблица Цикл
СтрокаТаблицы.Ключ = НРег(СокрЛП(СтрокаТаблицы.Наименование));
КонецЦикла;
Таблица.Индексы.Добавить("Ключ");
// Сколько раз встречается каждое наименование
Повторы = Таблица.Скопировать(, "Ключ");
Повторы.Колонки.Добавить("Повторов", Новый ОписаниеТипов("Число"));
Повторы.ЗаполнитьЗначения(1, "Повторов");
Повторы.Свернуть("Ключ", "Повторов");
Дубли = Таблица.СкопироватьКолонки();
Отбор = Новый Структура("Ключ");
Для Каждого Повтор Из Повторы Цикл
Если Повтор.Повторов < 2 Тогда
Продолжить;
КонецЕсли;
Отбор.Ключ = Повтор.Ключ;
Для Каждого Найденная Из Таблица.НайтиСтроки(Отбор) Цикл
ЗаполнитьЗначенияСвойств(Дубли.Добавить(), Найденная);
КонецЦикла;
КонецЦикла;
Дубли.Колонки.Удалить("Ключ");
// Адрес подготовила форма: результат появится в её сеансе,
// когда задание завершится
ПоместитьВоВременноеХранилище(Дубли, АдресРезультата);
КонецПроцедуры
Запуск и ожидание — в модуле той же формы. Результат выводится в реквизит Дубли, таблицу значений с колонками «Ссылка», «Код», «Наименование»:
// Модуль той же формы. Реквизиты формы: АдресДанных и АдресРезультата - Строка,
// ИдентификаторЗадания - УникальныйИдентификатор,
// Дубли - ТаблицаЗначений (Ссылка, Код, Наименование).
&НаКлиенте
Процедура ПоискДублей(Команда)
// Адрес результата заполнен, пока идёт предыдущий поиск
Если ЭтоАдресВременногоХранилища(АдресРезультата) Тогда
ПоказатьПредупреждение(, "Поиск уже выполняется.");
Возврат;
ИначеЕсли Не ЗапуститьПоискНаСервере() Тогда
ПоказатьПредупреждение(, "Нет подготовленных данных: сначала нажмите «Подготовить».");
Возврат;
КонецЕсли;
ПодключитьОбработчикОжидания("ПроверитьВыполнение", 2);
КонецПроцедуры
&НаСервере
Функция ЗапуститьПоискНаСервере()
Если Не ЭтоАдресВременногоХранилища(АдресДанных) Тогда
Возврат Ложь;
КонецЕсли;
Таблица = ПолучитьИзВременногоХранилища(АдресДанных);
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
Возврат Ложь;
КонецЕсли;
// Пустое значение с идентификатором формы: адрес, куда задание
// положит результат
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(Таблица); // в задание уходит копия таблицы, не адрес
ПараметрыЗадания.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("ДублиНоменклатуры.НайтиДубли", ПараметрыЗадания, ,
"Поиск дублей номенклатуры");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
Возврат Истина;
КонецФункции
&НаКлиенте
Процедура ПроверитьВыполнение()
Состояние = СостояниеЗадания(ИдентификаторЗадания);
Если Состояние = "Выполняется" Тогда
Возврат;
КонецЕсли;
ОтключитьОбработчикОжидания("ПроверитьВыполнение");
Успешно = (Состояние = "Завершено");
ЗавершитьПоискНаСервере(Успешно);
Если Успешно Тогда
ПоказатьОповещениеПользователя("Поиск дублей завершён", ,
СтрШаблон("Найдено строк: %1", Дубли.Количество()));
Иначе
ПоказатьПредупреждение(, Состояние);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗадания(Знач Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Возврат "Задание не найдено";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Возврат "Выполняется";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат "Завершено";
ИначеЕсли Задание.ИнформацияОбОшибке <> Неопределено Тогда
Возврат КраткоеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
КонецЕсли;
Возврат "Задание отменено";
КонецФункции
&НаСервере
Процедура ЗавершитьПоискНаСервере(Знач Успешно)
// Результат задания доступен форме только после его завершения
Результат = Неопределено;
Если Успешно Тогда
Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
КонецЕсли;
// И при успехе, и при ошибке адрес больше не нужен
УдалитьИзВременногоХранилища(АдресРезультата);
АдресРезультата = "";
Если ТипЗнч(Результат) = Тип("ТаблицаЗначений") Тогда
ЗначениеВРеквизитФормы(Результат, "Дубли");
Иначе
Дубли.Очистить();
КонецЕсли;
КонецПроцедуры
Пока задание работает, читать адрес бесполезно: записанное заданием появится в сеансе формы только после завершения, поэтому сначала проверяется состояние. Параметры задания передаются копиями и должны сериализоваться, форму туда не передать. Повторный запуск отсекает проверка АдресРезультата: адрес заполнен, пока идёт поиск. Ключ задания, отмена и сообщения из фона разобраны в статье про фоновые задания. В конфигурациях на БСП ту же схему реализуют ДлительныеОперации.ВыполнитьФункцию и ДлительныеОперацииКлиент.ОжидатьЗавершение.
Значения с идентификатором формы платформа удаляет при её закрытии. Но пока форма открыта, каждый вызов ПоместитьВоВременноеХранилище(..., УникальныйИдентификатор) выдаёт новый адрес: пользователь пять раз нажал «Подготовить» — в сеансе пять копий таблицы, а нужна одна. Отсюда два правила: перезаписывать значение по старому адресу и удалять то, что уже не нужно.
// Модуль той же формы: без лишних копий в хранилище.
// Вызывается из серверных процедур формы: пишет значение по старому адресу,
// если он есть, иначе получает новый.
&НаСервереБезКонтекста
Функция ПоложитьВХранилище(Значение, Знач Адрес, Знач ИдентификаторФормы)
Если ТипЗнч(Адрес) = Тип("Строка") И ЭтоАдресВременногоХранилища(Адрес) Тогда
ПоместитьВоВременноеХранилище(Значение, Адрес);
Возврат Адрес;
КонецЕсли;
Возврат ПоместитьВоВременноеХранилище(Значение, ИдентификаторФормы);
КонецФункции
&НаКлиенте
Процедура ОчиститьДанные(Команда)
ОчиститьДанныеНаСервере();
ПоказатьОповещениеПользователя("Временные данные удалены");
КонецПроцедуры
&НаСервере
Процедура ОчиститьДанныеНаСервере()
// Форма остаётся открытой, а таблица уже не нужна: освобождаем сразу
Если ЭтоАдресВременногоХранилища(АдресДанных) Тогда
УдалитьИзВременногоХранилища(АдресДанных);
КонецЕсли;
// Пустой адрес: следующий вызов ПоложитьВХранилище получит новый
АдресДанных = "";
Дубли.Очистить();
КонецПроцедуры
В ПодготовитьНаСервере() из первого листинга замените строку с ПоместитьВоВременноеХранилище на АдресДанных = ПоложитьВХранилище(Таблица, АдресДанных, УникальныйИдентификатор);. После удаления адрес обнуляется, чтобы следующий вызов не писал по удалённому адресу, а получил новый.
Значение с Новый УникальныйИдентификатор живёт до конца сеанса и само раньше не исчезнет. В БСП об этом сказано прямо: если идентификатор формы не передан, хранилище очищают явно методом УдалитьИзВременногоХранилища (комментарии к ДлительныеОперации.ПараметрыВыполненияВФоне и ФайловаяСистемаКлиент.ПараметрыЗагрузкиФайла). Такой вариант нужен, когда формы ещё нет: команда готовит данные на сервере и открывает форму, а та забирает их в ПриСозданииНаСервере. Пара функций для этого случая:
// Общий модуль ВременныеДанныеСеанса: флажок «Сервер».
// Для данных, которые должны пережить закрытие формы
// или появиться раньше, чем откроется форма-получатель.
Функция ПоместитьНаВремяСеанса(Знач Данные) Экспорт
// GUID не принадлежит ни одной форме: значение живёт до конца сеанса
Возврат ПоместитьВоВременноеХранилище(Данные, Новый УникальныйИдентификатор);
КонецФункции
// Однократное чтение: получили значение и сразу освободили адрес.
Функция ЗабратьИУдалить(Знач Адрес) Экспорт
Если ТипЗнч(Адрес) <> Тип("Строка") Или Не ЭтоАдресВременногоХранилища(Адрес) Тогда
Возврат Неопределено;
КонецЕсли;
Данные = ПолучитьИзВременногоХранилища(Адрес);
Если Данные <> Неопределено Тогда
// Повторный вызов с тем же адресом вернёт Неопределено
УдалитьИзВременногоХранилища(Адрес);
КонецЕсли;
Возврат Данные;
КонецФункции
&НаСервереБезКонтекста или в общем модуле, где свойств формы нет. Передайте его параметром.ПолучитьИзВременногоХранилища вернула Неопределено, а код сразу обратился к таблице. Обычные причины: значение помещено без второго параметра и удалено после следующего обращения к серверу; форма, к которой оно было привязано, закрыта; результат фонового задания читают до его завершения. Проверяйте ТипЗнч.ПоместитьВоВременноеХранилище(Результат, ИдентификаторФормы) создаст новый адрес уже в сеансе задания, и форма о нём не узнает. Адрес создаёт форма, задание только пишет по нему.ХранилищеЗначения.Новый УникальныйИдентификатор, которые никто не удаляет.В отладчике: остановитесь там, где адрес уже известен, откройте «Вычислить выражение» (Shift+F9) и введите ПолучитьИзВременногоХранилища(АдресДанных). Сам адрес — строка вида e1cib/tempstorage/…, по ней видно только, что это адрес хранилища. Остальные приёмы — в статье об отладке 1С.
Нет. Хранилище принадлежит сеансу, и в чужом сеансе по этому адресу ничего не найдётся. Единственная передача между сеансами — запись результата фоновым заданием по адресу родительского сеанса. Данные для других пользователей сохраняют в базе: в регистре сведений или реквизите типа ХранилищеЗначения.
Для значений, привязанных к идентификатору этой формы, — нет, платформа удалит их сама. Явно удаляют значения с Новый УникальныйИдентификатор и большие таблицы, которые больше не нужны, пока форма остаётся открытой.
Данные, которые пользователь видит и редактирует, кладут в реквизит формы, иначе их не вывести в таблицу формы (таблица значений на форме). Данные, которые нужны только серверу между вызовами, держат в хранилище: в форме остаётся одна строка адреса, и контекстные вызовы не гоняют таблицу туда и обратно.
Если обработка падает на больших объёмах, форма тяжелеет с каждым нажатием или результат фонового задания теряется, отдайте задачу программисту: он разберёт клиент-серверную часть целиком. Опишите, что не работает и в какой конфигурации, и разместите задачу: оценка бесплатная, ответ — в течение рабочего дня. Профильное направление — отчёты и обработки 1С.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.