В практике разработки на платформе 1С:Предприятие часто возникает задача прикрепления файлов (сканов, договоров, изображений) к документам. Казалось бы, задача тривиальная: создать реквизит типа ХранилищеЗначения и записать туда файл. Однако, при реализации этого механизма, особенно в клиент-серверной архитектуре, разработчики сталкиваются с ошибками доступа к файловой системе или "зависанием" отладки. Кроме того, существует важный вопрос архитектуры базы данных: стоит ли вообще хранить файлы внутри самих документов?
В этой статье мы подробно разберем техническую реализацию передачи файла с клиента на сервер и рассмотрим, почему опытные архитекторы 1С настоятельно не рекомендуют хранить бинарные данные (BLOB) непосредственно в реквизитах транзакционных объектов.
Рассмотрим ситуацию, с которой столкнулся автор темы. В расширении конфигурации был добавлен реквизит Файл с типом ХранилищеЗначения. Задача — выбрать файл на компьютере пользователя и сохранить его в этот реквизит.
Изначальный код выглядел следующим образом:
&НаСервере
Процедура ПослеВыбораФайла(Результат, ДополнительныеПараметры)
// В Результат[0] содержится путь к файлу на диске клиента, например "C:\Users\User\Desktop\File.pdf"
ДД = Новый ДвоичныеДанные(Результат[0]);
Объект.Файл = Новый ХранилищеЗначения(ДД);
КонецПроцедуры
При выполнении этого кода возникают ошибки, такие как "Ошибка при вызове конструктора (ДвоичныеДанные)", или отладчик просто останавливается.
В чем причина ошибки?
&НаСервере.Результат[0] содержит локальный путь к файлу на компьютере пользователя (клиента).C:\ компьютера пользователя. Для сервера этот путь либо не существует, либо указывает на локальный диск самого сервера, где такого файла нет.Чтобы передать файл с клиента на сервер, необходимо использовать механизм Временного хранилища. Алгоритм действий должен быть следующим:
ДвоичныеДанные.ХранилищеЗначения.Разберем правильную реализацию кода по шагам.
Сначала обрабатываем выбор файла на клиенте. Создаем двоичные данные и помещаем их во временное хранилище. Обратите внимание: для привязки к форме (чтобы хранилище очистилось после закрытия формы) можно использовать Уникальный Идентификатор формы, но для разовой передачи часто создают новый идентификатор или передают без него.
&НаКлиенте
Процедура ПослеВыбораФайла(Результат, ДополнительныеПараметры) ЭКСПОРТ
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
ПолноеИмяФайла = Результат[0];
// 1. Считываем файл в двоичные данные прямо на клиенте
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПолноеИмяФайла);
// 2. Помещаем во временное хранилище
// Новый УникальныйИдентификатор() гарантирует уникальность адреса
АдресВХ = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, Новый УникальныйИдентификатор());
// 3. Вызываем серверную процедуру, передавая туда АДРЕС, а не путь к файлу
СохранитьФайлНаСервере(АдресВХ);
КонецПроцедуры
Теперь на стороне сервера мы можем "достать" переданный файл из оперативной памяти временного хранилища и записать его в реквизит объекта.
&НаСервере
Процедура СохранитьФайлНаСервере(АдресВХ)
// 4. Получаем данные из временного хранилища
ДД = ПолучитьИзВременногоХранилища(АдресВХ);
Если ДД <> Неопределено Тогда
// 5. Записываем в реквизит типа ХранилищеЗначения
// Рекомендуется использовать сжатие для экономии места
Объект.Файл = Новый ХранилищеЗначения(ДД, Новый СжатиеДанных(9));
КонецЕсли;
КонецПроцедуры
Такой подход гарантирует, что файл будет успешно передан с машины пользователя на сервер приложений, независимо от сетевой инфраструктуры.
Хотя технически мы решили задачу, опытные разработчики в обсуждении настоятельно рекомендовали не хранить файлы (BLOB-данные) непосредственно в реквизитах документа. Давайте проанализируем причины этой рекомендации.
В платформе 1С чтение объекта данных (справочника или документа) происходит целиком. Когда вы обращаетесь к документу через ДокументОбъект (например, при проведении, открытии формы или программной обработке), система считывает все его реквизиты в оперативную память.
Если в документе хранится файл размером 5-10 МБ, то при каждом обращении к объекту сервер будет вынужден считывать и эти мегабайты, даже если они вам в данный момент не нужны. Это создает колоссальную нагрузку на сеть и память сервера, замедляя работу всей системы. Как метко заметили в обсуждении: "Это как если бы в библиотеке к каждой книге был привязан бетонный блок, и чтобы почитать книгу, вам приходилось бы тащить его с собой".
Запись большого объема данных занимает время. Пока документ с большим файлом записывается в базу данных, таблица документов может быть заблокирована. Это мешает параллельной работе других пользователей, которые в этот момент могут просто изменять статус или проводить другие документы того же типа.
Хранение бинарных данных внутри .1CD файла (для файловой версии) или в таблицах SQL приводит к быстрому росту размера базы. Это усложняет создание резервных копий (backup) и выполнение регламентных операций.
Штатным и наиболее правильным решением является вынос хранения файлов в отдельную сущность.
Как это реализовать:
ФайлыДокументов или используйте подсистему БСП "Присоединенные файлы".ВладелецФайла (ссылка на ваш Документ) и реквизит ДанныеФайла (ХранилищеЗначения).Преимущества такого подхода:
Для записи файла, выбранного на клиенте, в базу данных 1С необходимо использовать Временное хранилище как промежуточный буфер обмена. Прямая передача пути к файлу в конструктор ДвоичныеДанные на сервере невозможна.
Однако, прежде чем реализовывать сохранение файла в реквизит самого документа, рассмотрите вариант использования отдельного справочника или регистра. Это убережет вашу систему от проблем с производительностью в будущем и является стандартом разработки в среде 1С:Предприятие.