Как правильно добавить файл в регистр сведений программно через форму списка

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

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

Разберем по шагам, как устроена эта механика, выясним причины типичных ошибок и рассмотрим правильные варианты программной записи данных.

Помещение файла в ХранилищеЗначения

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

Посмотрим на корректный пример инициализации хранилища со сжатием данных:


// Считываем файл в двоичные данные и упаковываем в ХранилищеЗначения со сжатием
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(_Файл);
ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
НоваяЗапись.Файл = ХранилищеФайла;

Отмена стандартного интерактивного добавления строки

Если код выполняется в обработчике события списка ПередНачаломДобавления, платформа по умолчанию пытается добавить пустую строку в табличное поле списка после выполнения процедуры. Если при этом в коде уже выполняется программная запись в базу, в интерфейсе возникнет задвоение или попытка открыть форму новой записи.

Чтобы отменить стандартное добавление платформы, установим параметр события в значение Истина:

Отказ = Истина;

Почему затираются старые файлы и как работает НаборЗаписей

Проанализируем распространенную ошибку, из-за которой при добавлении нового файла удаляются ранее прикрепленные файлы по данному объекту. Метод НаборЗаписей.Записать() по умолчанию вызывается с параметром Замещать = Истина. Если перед этим набор не был прочитан из базы данных, платформа замещает все записи, попадающие под установленный отбор, содержимым текущего набора (то есть одной новой строкой).

Устранить эту проблему при работе с объектом НаборЗаписей можно двумя способами:

  1. Вызов метода Прочитать() перед добавлением строки: после установки отбора мы считываем существующие записи в коллекцию набора, добавляем новую запись и сохраняем обновленный набор целиком.
  2. Использование параметра замещения: метод НаборЗаписей.Записать(Ложь) дописывает новые записи к существующим в информационной базе.

Ошибки при формировании отбора и вычислении номера

Разберем еще две скрытые проблемы, которые часто встречаются в исходном коде:

  1. Ошибка несоответствия отбору: если на набор записей наложен отбор НаборЗаписей.Отбор.ИмяОбъекта.Установить(...), создаваемая запись обязана содержать в измерении ИмяОбъекта точно такое же значение, иначе при вызове Записать() платформа выдаст исключение.
  2. Глобальный расчет номера файла: запрос к таблице регистра для определения следующего номера файла обязательно должен содержать условие ГДЕ ФайлыОбъектов.ИмяОбъекта = &ИмяОбъекта. Без фильтрации по текущему объекту нумерация станет сквозной по всей базе данных, что нарушит независимую нумерацию файлов внутри каждого отдельного документа.
  3. Некорректное получение расширения: строковая функция ПРАВ(_Файл, 3) обрезает четырехбуквенные расширения (docx, xlsx, jpeg). Для безопасного извлечения параметров файла рекомендуется использовать свойства объекта Файл.

Вариант 1. Запись через МенеджерЗаписи (Рекомендуемый)

Для добавления одиночной строки в независимый регистр сведений удобнее и надежнее использовать объект МенеджерЗаписи. Он работает напрямую с одной записью по ключевым измерениям и не требует манипуляций с чтением всего набора.


Процедура РегистрСведенийСписокПередНачаломДобавления(Элемент, Отказ, Копирование)
    
    Отказ = Истина; // Отменяем стандартное интерактивное добавление строки
    
    // 1. Диалог выбора файла
    Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    Диалог.Заголовок = НСтр("ru = 'Выберите файл'");
    Диалог.ПолноеИмяФайла = "";
    Диалог.МножественныйВыбор = Ложь;
    Диалог.ПроверятьСуществованиеФайла = Истина;
    
    Если Не Диалог.Выбрать() Тогда
        Возврат; // Пользователь отменил выбор
    КонецЕсли;
    
    ПутьКФайлу = Диалог.ПолноеИмяФайла;
    ОбъектВладелец = Элемент.Значение.Отбор.ИмяОбъекта.Значение;
    
    // 2. Получаем максимальный номер файла конкретно для текущего объекта
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЕСТЬNULL(МАКСИМУМ(ФайлыОбъектов.НомерФайла), 0) КАК МаксНомер
        |ИЗ
        |   РегистрСведений.ФайлыОбъектов КАК ФайлыОбъектов
        |ГДЕ
        |   ФайлыОбъектов.ИмяОбъекта = &ИмяОбъекта";
    Запрос.УстановитьПараметр("ИмяОбъекта", ОбъектВладелец);
    
    Выборка = Запрос.Выполнить().Выбрать();
    СледующийНомер = 1;
    Если Выборка.Следующий() Тогда
        СледующийНомер = Выборка.МаксНомер + 1;
    КонецЕсли;
    
    // 3. Получаем свойства файла через объект Файл
    СведенияОФайле = Новый Файл(ПутьКФайлу);
    
    // 4. Записываем запись через МенеджерЗаписи
    МенеджерЗаписи = РегистрыСведений.ФайлыОбъектов.СоздатьМенеджерЗаписи();
    
    // Измерения
    МенеджерЗаписи.ИмяОбъекта = ОбъектВладелец;
    МенеджерЗаписи.НомерФайла  = СледующийНомер;
    
    // Ресурсы и реквизиты
    МенеджерЗаписи.Файл = Новый ХранилищеЗначения(Новый ДвоичныеДанные(ПутьКФайлу), Новый СжатиеДанных(9));
    МенеджерЗаписи.ИмяФайла = СведенияОФайле.ИмяБезРасширения;
    МенеджерЗаписи.РасширениеФайла = СведенияОФайле.Расширение;
    МенеджерЗаписи.ПредставлениеФайла = СведенияОФайле.Имя;
    
    МенеджерЗаписи.Записать(Истина);
    
    // Оповещаем интерфейс об изменении данных регистра для обновления списка
    ОповеститьОбИзменении(Тип("РегистрСведенийЗапись.ФайлыОбъектов"));
    
КонецПроцедуры

Вариант 2. Запись через НаборЗаписей с предварительным чтением

Если по архитектурным соображениям требуется использовать именно НаборЗаписей, обязательно выполняем вызов Прочитать() после установки отбора:


Процедура ЗаписатьФайлЧерезНабор(ОбъектВладелец, ПутьКФайлу)
    
    СведенияОФайле = Новый Файл(ПутьКФайлу);
    
    НаборЗаписей = РегистрыСведений.ФайлыОбъектов.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.ИмяОбъекта.Установить(ОбъектВладелец);
    
    // Считываем текущие записи объекта, чтобы не затереть их при вызове Записать()
    НаборЗаписей.Прочитать();
    
    // Вычисляем следующий номер на основе существующих записей в наборе
    МаксНомер = 0;
    Для Каждого СтрЗапись Из НаборЗаписей Цикл
        Если СтрЗапись.НомерФайла > МаксНомер Тогда
            МаксНомер = СтрЗапись.НомерФайла;
        КонецЕсли;
    КонецЦикла;
    
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.ИмяОбъекта = ОбъектВладелец;
    НоваяЗапись.НомерФайла  = МаксНомер + 1;
    
    НоваяЗапись.Файл = Новый ХранилищеЗначения(Новый ДвоичныеДанные(ПутьКФайлу), Новый СжатиеДанных(9));
    НоваяЗапись.ИмяФайла = СведенияОФайле.ИмяБезРасширения;
    НоваяЗапись.РасширениеФайла = СведенияОФайле.Расширение;
    НоваяЗапись.ПредставлениеФайла = СведенияОФайле.Имя;
    
    НаборЗаписей.Записать(Истина);
    
КонецПроцедуры

Рассмотренные подходы позволяют гарантировать целостность данных в регистре, корректно формируют уникальные номера файлов в разрезе объектов-владельцев и исключают ошибки записи в пользовательском интерфейсе.

← На главную