Разработка в среде управляемых форм 1С:Предприятия предъявляет жесткие требования к разграничению контекстов исполнения кода. Одной из частых проблем, с которой сталкиваются разработчики, является сохранение файлов (например, файлов выгрузки данных .xml) непосредственно в реквизит объекта с типом ХранилищеЗначения. Трудность заключается в том, что реквизиты этого типа недоступны напрямую на клиенте и не могут быть связаны с реквизитами формы в качестве данных.
В этой статье мы подробно разберем, как устроен жизненный цикл бинарных данных в управляемом приложении, рассмотрим по шагам процесс сохранения файлов, изучим современные потоковые методы чтения информации и обсудим архитектурные практики оптимизации базы данных.
Выясним причину, по которой стандартное присвоение пути к файлу реквизиту объекта вызывает ошибку. В управляемом приложении клиентский сеанс не имеет прямого доступа к СУБД, а серверный вызов не может обратиться к файловой системе локального компьютера пользователя. Реквизит типа ХранилищеЗначения физически находится в базе данных и обрабатывается исключительно на стороне сервера.
Более того, платформа не позволяет напрямую передать объект типа ХранилищеЗначения с клиента на сервер, так как данный тип не сериализуется для передачи по каналам связи между тонким клиентом и сервером. Для решения этой задачи используется промежуточное звено — Временное хранилище.
---
Рассмотрим классический и наиболее надежный алгоритм загрузки файла с локального компьютера пользователя и его сохранения в реквизит справочника.
Сначала нам необходимо получить двоичные данные файла на стороне клиента и временно загрузить их на сервер. В современных версиях платформы 1С для этого используется асинхронный метод НачатьПомещениеФайлаНаСервер(), который корректно работает во всех типах клиентов (включая веб-клиент) и не блокирует интерфейс пользователя.
Посмотрим на пример реализации интерактивного выбора файла на клиенте:
&НаКлиенте
Процедура ЗагрузитьФайлXML(Команда)
// Настраиваем параметры диалога выбора файла
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов("Выберите XML-файл поставщика", Ложь, "Файлы XML (*.xml)|*.xml");
// Создаем описание оповещения для обработки результата загрузки
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтаФорма);
// Асинхронно отправляем файл на сервер
НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , , ПараметрыДиалога, УникальныйИдентификатор);
КонецПроцедуры
После того как пользователь выберет файл, платформа загрузит его во временное хранилище и вызовет процедуру обратного вызова. Обратите внимание, что мы передаем УникальныйИдентификатор формы — это гарантирует, что временный файл будет автоматически удален из оперативной памяти сервера после закрытия нашей формы.
&НаКлиенте
Процедура ПослеПомещенияФайла(Результат, ДополнительныеПараметры) Экспорт
Если Результат <> Неопределено Тогда
// Получаем уникальный адрес во временном хранилище
АдресДвоичныхДанных = Результат.Адрес;
// Помечаем форму как измененную, чтобы платформа знала о наличии несохраненных данных
Модифицированность = Истина;
ПоказатьПредупреждение(, "Файл успешно загружен и готов к сохранению!");
КонецЕсли;
КонецПроцедуры
Для того чтобы адрес временного хранилища был доступен между различными процедурами формы, объявим переменную АдресДвоичныхДанных в качестве реквизита управляемой формы с типом Строка.
Теперь, когда бинарные данные нашего XML-файла находятся на сервере во временном хранилище, мы можем безопасно записать их в реквизит объекта. Наиболее подходящим местом для этого является обработчик события формы ПередЗаписьюНаСервере. В этот момент у нас есть прямой доступ к записываемому объекту базы данных через параметр ТекущийОбъект.
Разберем код серверной процедуры:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Проверяем, был ли загружен файл во временное хранилище
Если ЗначениеЗаполнено(АдресДвоичныхДанных) Тогда
// Извлекаем двоичные данные из временного хранилища
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресДвоичныхДанных);
Если ТипЗнч(ДвоичныеДанныеФайла) = Тип("ДвоичныеДанные") Тогда
// Помещаем двоичные данные в ХранилищеЗначения реквизита объекта
ТекущийОбъект.ФайлПоставщика = Новый ХранилищеЗначения(ДвоичныеДанныеФайла);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот подход изящно решает проблему отсутствия прямой связи реквизита базы данных с элементами формы. Данные передаются «транзитом» через оперативную память сервера непосредственно в момент записи элемента справочника или документа.
---
Проанализируем обратную задачу: пользователь открывает существующий элемент справочника, и нам необходимо прочитать ранее сохраненный XML-файл для визуализации его структуры или последующей обработки.
Исторически разработчики часто использовали запись временного файла на жесткий диск сервера через метод Записать() объекта ДвоичныеДанные, после чего читали этот файл с диска. Однако запись на физический диск сервера снижает общую производительность системы и требует ручной очистки временных файлов. Мы применим современный и быстрый способ — чтение потока данных напрямую из оперативной памяти.
Выполним чтение в событии формы ПриЧтенииНаСервере:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Проверяем наличие сохраненных данных в реквизите
Если ТекущийОбъект.ФайлПоставщика <> Неопределено Тогда
// Извлекаем двоичные данные из ХранилищаЗначения
ДвоичныеДанные = ТекущийОбъект.ФайлПоставщика.Получить();
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
// Открываем потоковое чтение данных в оперативной памяти
ПотокЧтения = ДвоичныеДанные.ОткрытьПотокДляЧтения();
ЧтениеXML = Новый ЧтениеXML;
Попытка
// Инициализируем чтение XML из потока
ЧтениеXML.ОткрытьПоток(ПотокЧтения);
// Вызываем процедуру парсинга данных
ПрочитатьФайлПоставщикаXML(ЧтениеXML);
Исключение
ЗаписьЖурналаРегистрации("Чтение XML", УровеньЖурналаРегистрации.Ошибка,,,
ОписаниеОшибки());
ВызватьИсключение "Не удалось прочитать структуру XML-файла!";
КонецПопытки;
// Закрываем потоки для освобождения памяти
ЧтениеXML.Закрыть();
ПотокЧтения.Закрыть();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Использование метода ОткрытьПотокДляЧтения() позволяет обрабатывать даже файлы большого объема практически мгновенно, поскольку операции ввода-вывода (I/O) производятся в RAM-буфере сервера приложений 1С, минуя дисковую подсистему.
---
По умолчанию конструктор Новый ХранилищеЗначения(Данные) применяет стандартный алгоритм сжатия платформы. Однако, поскольку XML-файлы представляют собой обычный текст, они обладают колоссальным потенциалом сжатия (вплоть до 90% от исходного объема).
Для экономии дискового пространства базы данных СУБД мы можем явно указать максимальный уровень сжатия данных при создании объекта ХранилищеЗначения. Для этого используется системный объект СжатиеДанных:
// Используем максимальную степень сжатия (значение 9)
СтепеньСжатия = Новый СжатиеДанных(9);
ТекущийОбъект.ФайлПоставщика = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, СтепеньСжатия);
Это незначительно увеличит нагрузку на процессор сервера при записи и чтении, но существенно уменьшит размер физических таблиц базы данных на диске.
---
Хранение тяжелых бинарных файлов (особенно если их размер превышает несколько мегабайт) непосредственно внутри основных реквизитов справочников имеет скрытые архитектурные угрозы:
Для оптимизации этой структуры рекомендуется использовать один из следующих подходов:
ПрисоединенныеФайлыXML), подчиненный основному справочнику. Реквизит ХранилищеЗначения должен располагаться исключительно в подчиненном справочнике. Обращайтесь к нему только тогда, когда файл действительно требуется открыть или обработать.---
Если обрабатываемый XML-файл имеет значительный объем (десятки мегабайт), его парсинг в процедуре ПриЧтенииНаСервере неизбежно приведет к временному зависанию клиентского приложения. Пользователь увидит белый экран или надпись «Программа не отвечает».
Чтобы избежать блокировки интерфейса, используйте следующую асинхронную модель работы:
ПриЧтенииНаСервере не выполняйте парсинг файла. Ограничьтесь лишь получением информации о том, что файл прикреплен к объекту (например, установите булевый флаг на форме ФайлЗагружен = Истина).ПриОткрытии на клиенте подключите однократный обработчик ожидания с задержкой в 0.1 секунды:
ПодключитьОбработчикОжидания("НачатьАсинхронноеЧтениеФайла", 0.1, Истина);
ДлительныеОперации.ЗапуститьВыполнениеВФоне()). Это позволит показать пользователю красивое окно ожидания с индикатором прогресса и кнопкой отмены, сохранив отзывчивость интерфейса системы.Применение описанных методик позволит создавать быстрые, отказоустойчивые и современные интерфейсы интеграции в вашей конфигурации 1С:Предприятие.