Как избежать перезаписи табличных документов при использовании временного хранилища в 1С?

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

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

Анализ исходной проблемы: почему происходит перезапись?

Изначально разработчик столкнулся с тем, что при формировании квитанций для множества лицевых счетов и попытке сохранения их в файлы, сохранялись только данные по последнему лицевому счету. Первая мысль, которая часто приходит на ум – использовать временное хранилище 1С (ПоместитьВоВременноеХранилище), чтобы сохранить каждый табличный документ по отдельному адресу, а затем уже извлекать и записывать их в файлы.

Давайте проанализируем фрагмент кода, который описывает эту проблему:


Для Каждого ЭлементСписка Из ПараметрыОтчета.ТаблицаОбъектов Цикл
    Если АдресПервойКвитанции = Адрес ИЛИ АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
        // АдресПервойКвитанции в самом начале всегда неопределено
        Если АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
            УникальныйИдентификатор = Новый УникальныйИдентификатор;
            СоответствиеАдресов = Новый Соответствие;
        КонецЕсли;
        СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат, ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
        АдресПервойКвитанции = Адрес;
        АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
        СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
    Иначе // Если у нас изменился адрес ЛС то создаем новый адрес хранилища.
        УникальныйИдентификатор = Новый УникальныйИдентификатор;
        ДокументРезультатВывод.Очистить; // <-- Проблемная строка
        СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат,ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
        АдресПервойКвитанции = Адрес;
        АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
        СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
    КонецЕсли;
КонецЦикла;

Для Каждого Элемент Из СоответствиеАдресов Цикл
    ТабДокЛС = ПолучитьИзВременногоХранилища(Элемент.Значение);
    ТабДокЛС.Записать(КаталогФайла + Элемент.Ключ + ".pdf", ТипФайлаТабличногоДокумента.PDF);
КонецЦикла;

На первый взгляд, все кажется логичным: мы создаем новый УникальныйИдентификатор для каждого адреса хранилища, что, по идее, должно гарантировать уникальное местоположение для каждого табличного документа. Однако, как выяснилось, в конечном итоге сохраняются только последние данные. В чем же причина?

Причина кроется в том, как 1С работает с объектами по ссылке. Объект ТабличныйДокумент (переменная ДокументРезультатВывод) является изменяемым (мутабельным) объектом. Когда мы вызываем метод ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор), во временное хранилище помещается не копия самого табличного документа, а фактически ссылка на этот объект. Это означает, что если мы изменим содержимое объекта ДокументРезультатВывод после того, как он был помещен в хранилище, эти изменения отразятся и на том "экземпляре", который находится во временном хранилище.

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

Пошаговое решение: создание нового объекта на каждой итерации

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

Решение, как было подсказано, заключается в замене строки ДокументРезультатВывод.Очистить; на ДокументРезультатВывод = Новый ТабличныйДокумент;. Это позволит нам получить желаемый результат.

Давайте посмотрим на исправленный фрагмент кода:


Для Каждого ЭлементСписка Из ПараметрыОтчета.ТаблицаОбъектов Цикл
    // В самом начале цикла или при смене адреса ЛС,
    // всегда создаем новый экземпляр табличного документа
    ДокументРезультатВывод = Новый ТабличныйДокумент; // <-- Исправленная строка

    Если АдресПервойКвитанции = Адрес ИЛИ АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
        Если АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
            УникальныйИдентификатор = Новый УникальныйИдентификатор;
            СоответствиеАдресов = Новый Соответствие;
        КонецЕсли;
        СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат,ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
        АдресПервойКвитанции = Адрес;
        АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
        СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
    Иначе // Если у нас изменился адрес ЛС то создаем новый адрес хранилища.
        УникальныйИдентификатор = Новый УникальныйИдентификатор;
        // ДокументРезультатВывод = Новый ТабличныйДокумент; // Эта строка уже есть в начале цикла, здесь не нужна
        СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат,ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
        АдресПервойКвитанции = Адрес;
        АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
        СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
    КонецЕсли;
КонецЦикла;

Для Каждого Элемент Из СоответствиеАдресов Цикл
    ТабДокЛС = ПолучитьИзВременногоХранилища(Элемент.Значение);
    ТабДокЛС.Записать(КаталогФайла + Элемент.Ключ + ".pdf", ТипФайлаТабличногоДокумента.PDF);
КонецЦикла;

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

Обратите внимание, что создание Новый УникальныйИдентификатор на каждой итерации является правильным подходом, так как он обеспечивает получение нового адреса во временном хранилище для каждого нового объекта ТабличныйДокумент. Объект СоответствиеАдресов затем корректно связывает имя файла с уникальным адресом в хранилище, позволяя нам извлечь и записать каждый документ по его специфическому имени и содержимому.

Важные аспекты работы с временным хранилищем в 1С

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

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

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

← На главную