Как сохранить файл из поля типа ХранилищеЗначения 1С на диск?

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

При разработке в 1С часто возникает задача сохранения файлов, прикрепленных к различным объектам системы, на локальный диск пользователя или на сервер. Типичный сценарий — это хранение файлов в реквизите типа ХранилищеЗначения. Рассмотрим, как правильно извлечь данные из такого хранилища и записать их в файл, а также разберем распространенные ошибки и их решения.

Основной принцип сохранения файла из ХранилищаЗначения

Для того чтобы сохранить файл, хранящийся в реквизите типа ХранилищеЗначения, на диск, нам потребуется выполнить два основных шага:

  1. Получить данные из хранилища: Извлекаем само значение, которое находится внутри ХранилищаЗначения. Если мы сохраняли в него файл (например, двоичные данные PDF, JPEG и т.д.), то метод Получить() вернет объект типа ДвоичныеДанные.
  2. Записать двоичные данные на диск: Используем метод Записать() объекта ДвоичныеДанные, чтобы сохранить его в указанный файл на диске.

Рассмотрим базовый пример кода для серверного контекста:


// Предположим, что ФайлХранилище - это переменная типа ХранилищеЗначения,
// полученная из реквизита справочника или другого объекта.
// Например, из выборки запроса: Выборка.ФайлХранилище
ПеременнаяХранилище = ОбъектСправочника.Файл; // Или Выборка.ФайлХранилище

// 1. Получаем двоичные данные из хранилища
ДвоичныеДанные = ПеременнаяХранилище.Получить(); 

// 2. Указываем полный путь и имя файла для сохранения
ИмяФайлаНаДиске = "C:\Temp\МойДокумент.pdf"; 

// 3. Записываем двоичные данные в файл на диске
ДвоичныеДанные.Записать(ИмяФайлаНаДиске);

В этом примере ОбъектСправочника.Файл представляет собой реквизит справочника типа ХранилищеЗначения. Метод Получить() извлекает из него содержимое, которое затем, как объект ДвоичныеДанные, записывается на диск по указанному пути.

Разбираем ошибку "Метод объекта не обнаружен"

Одной из наиболее частых ошибок, с которой сталкиваются разработчики при работе с ХранилищемЗначения, является "Метод объекта не обнаружен" при попытке вызвать метод Получить(). Разберем основные причины этой ошибки и способы ее устранения.

Эта ошибка обычно возникает, когда:

  1. Переменная не является ХранилищемЗначения: Переменная, к которой вы пытаетесь применить метод Получить(), на самом деле не является объектом типа ХранилищеЗначения. Например, это может быть Неопределено, Null, или вообще другой тип данных.
  2. Переменная содержит Неопределено или Null: Если реквизит типа ХранилищеЗначения в базе данных пуст, то при его чтении вы можете получить значение Неопределено (для новых объектов или при чтении из запроса) или Null (при работе с объектами базы данных). Объект Неопределено или Null, естественно, не имеет метода Получить().

Для предотвращения этой ошибки крайне важно выполнять проверки перед вызовом метода Получить().

Проверка на тип и значение

Прежде чем вызывать метод Получить(), мы всегда должны убедиться, что переменная действительно является объектом ХранилищеЗначения и не содержит Неопределено. Рассмотрим более надежный вариант кода:


// Предположим, что ФайлХранилище - это переменная, которую мы получили.
// Например, из запроса:
// Запрос = Новый Запрос("ВЫБРАТЬ СправочникПрикрепленныеФайлы.Файл ИЗ Справочник.ПрикрепленныеФайлы КАК СправочникПрикрепленныеФайлы ГДЕ СправочникПрикрепленныеФайлы.Ссылка = &Ссылка");
// Запрос.УстановитьПараметр("Ссылка", СсылкаНаНужныйФайл);
// Выборка = Запрос.Выполнить().Выбрать();
// Если Выборка.Следующий() Тогда
//     ФайлХранилище = Выборка.Файл;
// КонецЕсли;

ДвоичныеДанные = Неопределено;

// Сначала проверяем, что значение не Неопределено
Если ФайлХранилище <> Неопределено Тогда
    // Затем проверяем, что тип значения - ХранилищеЗначения
    Если ТипЗнч(ФайлХранилище) = Тип("ХранилищеЗначения") Тогда
        ДвоичныеДанные = ФайлХранилище.Получить();
    Иначе
        // Возможно, в переменной уже содержатся ДвоичныеДанные,
        // если ХранилищеЗначения уже было преобразовано (например, при отладке или специфическом коде).
        // Это менее вероятный сценарий для обычного реквизита, но для надежности можно учесть.
        Если ТипЗнч(ФайлХранилище) = Тип("ДвоичныеДанные") Тогда
            ДвоичныеДанные = ФайлХранилище;
        Иначе
            Сообщить("В переменной содержится неожиданный тип: " + ТипЗнч(ФайлХранилище));
        КонецЕсли;
    КонецЕсли;
Иначе
    Сообщить("Хранилище файла пустое или не определено.");
КонецЕсли;

Если ДвоичныеДанные <> Неопределено Тогда
    // Указываем путь, например, временный файл или путь, выбранный пользователем
    ИмяФайлаНаДиске = "C:\Temp\ПрикрепленныйДокумент.pdf"; // Убедитесь, что путь существует и доступен для записи
    
    Попытка
        ДвоичныеДанные.Записать(ИмяФайлаНаДиске);
        Сообщить("Файл успешно сохранен на диск: " + ИмяФайлаНаДиске);
    Исключение
        Сообщить("Ошибка при сохранении файла на диск: " + ОписаниеОшибки());
    КонецПопытки;
Иначе
    Сообщить("Не удалось получить двоичные данные для сохранения.");
КонецЕсли;

В этом расширенном примере мы сначала проверяем переменную на Неопределено, а затем проверяем ее тип. Это позволяет избежать ошибки "Метод объекта не обнаружен" и более точно диагностировать проблему, если данные отсутствуют или имеют неожиданный формат.

Контекст выполнения

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

Рекомендации по именованию переменных и реквизитов

Как было справедливо замечено в обсуждении, лучше избегать использования таких общих имен, как "Файл", для ваших реквизитов или переменных. Например, в 1С существует системный объект Файл, который предоставляет функциональность для работы с файловой системой. Использование такого же имени для вашего реквизита Файл типа ХранилищеЗначения может привести к путанице и потенциальным конфликтам в коде. Рекомендуется использовать более специфичные имена, например, ФайлХранилище, СодержимоеФайла или ДвоичныеДанныеФайла. Это улучшит читаемость кода и снизит риск пересечений с системными объектами.

Полный пример для клиент-серверного взаимодействия (Управляемые формы)

В управляемых формах, особенно когда мы работаем с файлами, которые нужно сохранить на клиентский диск, прямой доступ к файловой системе клиента из серверного кода невозможен. Для таких сценариев используется механизм временного хранилища. Мы рассмотрим полный пример, который демонстрирует сохранение файла на диск клиента с использованием временного хранилища. При необходимости можно воспользоваться руководством по работе с файлами в управляемых формах в клиент-серверном режиме.

  1. На клиентской стороне мы получаем данные или адрес временного хранилища.
  2. На серверной стороне мы помещаем или извлекаем двоичные данные из временного хранилища.
  3. Если сохранение происходит на клиентский диск, то на клиентской стороне вызывается специальная процедура для сохранения файла, которой передаются данные из временного хранилища.

Рассмотрим пример кода, адаптированный из сообщения форума, который демонстрирует этот подход:


// &НаКлиенте
// Процедура, вызываемая на клиенте, например, по нажатию кнопки
Процедура кнСохранитьФайлНаДиске(Команда)
    // Здесь мы должны получить данные, которые хотим сохранить.
    // В данном примере, предположим, что у нас есть некая переменная 'ДанныеФото',
    // которая содержит объект с методом 'ПолучитьДвоичныеДанные'.
    // В реальном приложении, 'ДанныеФото' может быть получено, например,
    // из реквизита формы, который уже содержит адрес временного хранилища,
    // или из объекта, который был создан ранее на сервере.
    
    // Для нашего примера, давайте предположим, что у нас есть некое ХранилищеЗначения
    // на сервере, которое мы хотим получить и сохранить.
    // Тогда сначала нам нужно вызвать серверную функцию для получения ДвоичныхДанных.
    
    // Пример: вызываем серверную функцию для получения ХранилищаЗначения
    // и помещаем его во временное хранилище на сервере, а на клиент получаем адрес.
    АдресФайлаВоВременномХранилище = ПолучитьФайлССервераИПоместитьВоВременноеХранилище();
    
    Если АдресФайлаВоВременномХранилище <> Неопределено Тогда
        // Вызываем серверную процедуру для сохранения файла на диск клиента.
        // Здесь используется стандартный диалог сохранения файла.
        // Передаем АдресФайлаВоВременномХранилище и имя, которое хотим предложить.
        СохранитьФайлНаКлиенте(АдресФайлаВоВременномХранилище, "МойДокумент.pdf");
    КонецЕсли;
КонецПроцедуры

// &НаСервереБезКонтекста
// Функция для получения ДвоичныхДанных из ХранилищаЗначения на сервере
// и помещения их во временное хранилище. Возвращает адрес временного хранилища.
Функция ПолучитьФайлССервераИПоместитьВоВременноеХранилище()
    // Здесь получаем наше ХранилищеЗначения.
    // Например, из справочника ПрикрепленныеФайлы.
    СсылкаНаФайл = Справочники.ПрикрепленныеФайлы.НайтиПоКоду("000000001"); // Пример
    Если СсылкаНаФайл <> Неопределено Тогда
        ОбъектФайла = СсылкаНаФайл.ПолучитьОбъект();
        Если ОбъектФайла.Файл <> Неопределено И ТипЗнч(ОбъектФайла.Файл) = Тип("ХранилищеЗначения") Тогда
            ДвоичныеДанныеФайла = ОбъектФайла.Файл.Получить();
            Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, Новый УникальныйИдентификатор());
        Иначе
            Сообщить("Реквизит 'Файл' не содержит ХранилищеЗначения или пуст.");
        КонецЕсли;
    Иначе
        Сообщить("Не найдена ссылка на файл.");
    КонецЕсли;
    Возврат Неопределено;
КонецФункции

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

// Пример из сообщения 13, где сохранение происходит на серверный диск, но с использованием
// клиент-серверного взаимодействия для получения данных.
// &НаКлиенте
Процедура кнСделатьФотоПН(Команда)
    // Предположим, что ПолучитьДанныеФотоСнимка возвращает объект,
    // который содержит метод ПолучитьДвоичныеДанные.
    // Этот объект мог быть получен ранее с сервера или сформирован на клиенте.
    
    // В контексте нашей задачи, это может быть некий объект, 
    // который содержит ХранилищеЗначения.
    // Для простоты, допустим, что ДанныеФото - это уже ДвоичныеДанные.
    
    // Если ДанныеФото - это ХранилищеЗначения, то потребуется сначала получить ДвоичныеДанные.
    // Например, так:
    // ДанныеИзХранилища = ПолучитьХранилищеЗначенияСФормыСервер().Получить();
    // Если ДанныеИзХранилища <> Неопределено И ТипЗнч(ДанныеИзХранилища) = Тип("ДвоичныеДанные") Тогда
    //     ТекущееФото = ДанныеИзХранилища;
    // КонецЕсли;
    
    // В примере из сообщения 13, ДанныеФото.ПолучитьДвоичныеДанные()
    // подразумевает, что ДанныеФото - это объект, который предоставляет ДвоичныеДанные.
    // Давайте адаптируем под наш случай, когда у нас есть ХранилищеЗначения на сервере.
    
    // Сначала получаем адрес временного хранилища, куда помещены двоичные данные
    АдресФайлаВоВременномХранилище = ПолучитьДвоичныеДанныеИзХранилищаНаСервереИПоместитьВоВременное();
    
    Если АдресФайлаВоВременномХранилище <> Неопределено Тогда
        // Теперь мы хотим сохранить эти данные на серверный диск.
        // Для этого вызываем серверную процедуру и передаем ей адрес временного хранилища.
        СохранитьФотоНаДискеНаСервере(АдресФайлаВоВременномХранилище);
    КонецЕсли;
КонецПроцедуры

// &НаСервереБезКонтекста
// Вспомогательная функция для получения ДвоичныхДанных из ХранилищаЗначения на сервере
// и их помещения во временное хранилище.
Функция ПолучитьДвоичныеДанныеИзХранилищаНаСервереИПоместитьВоВременное()
    // Здесь мы должны получить наше ХранилищеЗначения.
    // Для примера возьмем из справочника.
    СсылкаНаФайл = Справочники.ПрикрепленныеФайлы.НайтиПоКоду("000000001"); 
    Если СсылкаНаФайл <> Неопределено Тогда
        ОбъектФайла = СсылкаНаФайл.ПолучитьОбъект();
        Если ОбъектФайла.Файл <> Неопределено И ТипЗнч(ОбъектФайла.Файл) = Тип("ХранилищеЗначения") Тогда
            ДвоичныеДанные = ОбъектФайла.Файл.Получить();
            // Помещаем двоичные данные во временное хранилище на сервере
            Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, Новый УникальныйИдентификатор());
        Иначе
            Сообщить("Реквизит 'Файл' не содержит ХранилищеЗначения или пуст.");
        КонецЕсли;
    Иначе
        Сообщить("Не найдена ссылка на файл.");
    КонецЕсли;
    Возврат Неопределено;
КонецФункции

// &НаСервере
// Процедура для сохранения файла на серверный диск.
Процедура СохранитьФотоНаДискеНаСервере(ФотоХранилище)
    // Формируем имя файла на сервере.
    // В данном случае, ИмяФайла должно быть полным путем к файлу на серверном диске.
    // Например: "C:\Temp\" + ВыбПриходнаяНакладная.Номер + ".jpg";
    // Для примера:
    ИмяФайла = КаталогВременныхФайлов() + "файл_с_сервера.pdf"; 

    // Получаем двоичные данные из временного хранилища.
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(ФотоХранилище);

    Если ДвоичныеДанные <> Неопределено Тогда
        Попытка
            ДвоичныеДанные.Записать(ИмяФайла);
            СообщитьНаКлиент("Файл успешно сохранен на сервере: " + ИмяФайла);
        Исключение
            СообщитьНаКлиент("Ошибка при сохранении файла на сервере: " + ОписаниеОшибки());
        КонецПопытки;
    Иначе
        СообщитьНаКлиент("Не удалось получить двоичные данные из временного хранилища.");
    КонецЕсли;
КонецПроцедуры

// &НаКлиенте
Процедура СообщитьНаКлиент(ТекстСообщения)
    Сообщить(ТекстСообщения);
КонецПроцедуры

В этом развернутом примере мы видим, как происходит взаимодействие: на клиенте мы инициируем процесс, на сервере получаем данные из ХранилищаЗначения, помещаем их во временное хранилище, а затем, в зависимости от задачи (сохранение на клиентский или серверный диск), либо вызываем клиентскую процедуру НачатьСохранениеФайла, либо серверную процедуру для записи файла на сервер.

Дополнительные важные моменты

  1. Сжатие данных: При создании объекта ХранилищеЗначения мы можем указать степень сжатия данных. Это может быть полезно для больших файлов, так как позволяет экономить место в базе данных. Однако сжатие и распаковка требуют некоторых ресурсов процессора.
  2. Хранение файлов в томах: Для конфигураций, работающих с большим объемом присоединенных файлов, рекомендуется использовать функциональность внешних томов хранения. Это позволяет хранить сами файлы на диске файлового сервера, а в базе данных хранить только ссылки на эти файлы. Такой подход предотвращает "разбухание" информационной базы, особенно в файловом режиме, и улучшает производительность.
  3. Работа с Картинкой или ТабличнымДокументом: Важно помнить, что ХранилищеЗначения может содержать не только ДвоичныеДанные, но и любые другие сериализуемые типы, например, объекты Картинка или ТабличныйДокумент. Если вы сохраняли в хранилище именно такие объекты, то метод Получить() вернет объект соответствующего типа, а не ДвоичныеДанные. В этом случае для сохранения их на диск потребуются специфические методы этих объектов (например, Картинка.Записать() или ТабличныйДокумент.Записать()).
  4. Облачная 1С и удаленный рабочий стол: При работе в облачной 1С или через удаленный рабочий стол (RDP) сохранение файлов "на диск клиента" будет означать сохранение на диск той машины, где запущен удаленный рабочий стол или облачное приложение. Для доступа к локальным дискам пользователя может потребоваться дополнительная настройка клиента удаленного доступа или специальные возможности платформы.

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

← На главную