При разработке конфигураций на платформе 1С:Предприятие 8 программисты часто сталкиваются с задачей хранения файлов, картинок или других двоичных данных непосредственно внутри документов. Попытка реализовать это через реквизит табличной части (ТЧ) с типом ХранилищеЗначения на управляемой форме приводит к неожиданной проблеме: при передаче данных между клиентом и сервером содержимое хранилища бесследно исчезает.
В этой статье мы подробно разберем, почему управляемая форма очищает бинарные данные, рассмотрим быстрые способы обхода этого поведения с примерами кода, а также проанализируем, почему хранение файлов в табличных частях является грубой архитектурной ошибкой и как эту задачу решают профессионалы.
Давайте проанализируем стандартный сценарий, с которым сталкивается разработчик. В конфигурации создан документ, у которого есть табличная часть с реквизитом типа ХранилищеЗначения. На форме документа мы пытаемся программно заполнить эту ТЧ:
&НаСервере
Процедура ЗаполнитьТЧНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
// Загружаем таблицу значений с файлами в объект
ДокументОбъект.Файлы.Загрузить(ТаблицаСФайлами);
// Переносим данные обратно в форму
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
В момент, когда отрабатывает метод ЗначениеВРеквизитФормы, платформа принудительно очищает все поля типа ХранилищеЗначения в строках табличной части формы. При следующем обращении к серверу через метод РеквизитФормыВЗначение мы получаем объект, в котором вместо файлов содержатся пустые значения (Неопределено). Если такой документ записать, ранее сохраненные данные в базе будут затерты.
Почему это происходит? Это жесткое архитектурное ограничение управляемых форм. Платформа 1С оптимизирует трафик между клиентом и сервером. Файлы могут весить мегабайты, и если бы они постоянно циркулировали в составе структуры ДанныеФормыКоллекция, интерактивная работа с формой стала бы невозможной из-за колоссальных задержек. Поэтому платформа намеренно блокирует сериализацию типа ХранилищеЗначения в коллекциях формы.
Рассмотрим по шагам возможные варианты решения этой задачи.
Если вам необходимо быстро заставить работать существующую логику без изменения структуры базы данных, можно использовать обходной путь с сохранением данных ТЧ во временный реквизит формы.
Для этого добавим на форму реквизит с именем БуферФайлов и типом Произвольный. Этот реквизит будет доступен только на сервере. Реализуем чтение и запись данных следующим образом:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Извлекаем объект из реквизита формы
ДокументОбъект = РеквизитФормыВЗначение("Объект");
// Выгружаем ТЧ с файлами во временный реквизит формы в виде Хранилища Значений
БуферФайлов = Новый ХранилищеЗначения(ДокументОбъект.Файлы.Выгрузить());
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Перед записью восстанавливаем ТЧ из нашего буфера напрямую в записываемый объект
Если БуферФайлов <> Неопределено Тогда
ТаблицаФайлов = БуферФайлов.Получить();
Если ТипЗнч(ТаблицаФайлов) = Тип("ТаблицаЗначений") Тогда
ТекущийОбъект.Файлы.Загрузить(ТаблицаФайлов);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Плюсы метода: Простая реализация, минимум изменений в коде, отсутствие необходимости создавать внешние файлы на диске.
Минусы метода: Вся таблица значений целиком держится в оперативной памяти сервера на протяжении работы с формой. Метод не подходит для работы с большими файлами.
Более изящный способ, не нагружающий оперативную память сервера лишними структурами, — использование механизма временных хранилищ 1С. Вместо хранения бинарных объектов в ТЧ формы, мы будем хранить там строковые адреса.
Разберем алгоритм по шагам:
Файл (которая имеет тип ХранилищеЗначения) на строковую колонку АдресВоВременномХранилище.ХранилищеЗначения объекта базы данных.Посмотрим на программную реализацию этого механизма:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Заполняем строковые адреса на форме при чтении объекта из БД
Для Каждого СтрокаТЧ Из Объект.Файлы Цикл
// Получаем реальный объект ХранилищеЗначения из строки объекта БД
Индекс = Объект.Файлы.Индекс(СтрокаТЧ);
ТекущееХранилище = ТекущийОбъект.Файлы[Индекс].Файл;
Если ЗначениеЗаполнено(ТекущееХранилище) Тогда
// Помещаем двоичные данные во временное хранилище формы
СтрокаТЧ.АдресВоВременномХранилище = ПоместитьВоВременноеХранилище(
ТекущееХранилище.Получить(),
УникальныйИдентификатор
);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Перед записью переносим данные из временного хранилища в объект базы данных
Для Каждого СтрокаФормы Из Объект.Файлы Цикл
Индекс = Объект.Файлы.Индекс(СтрокаФормы);
СтрокаОбъекта = ТекущийОбъект.Файлы[Индекс];
// Проверяем, изменился ли или добавился ли файл
Если ЭтоАдресВременногоХранилища(СтрокаФормы.АдресВоВременномХранилище) Тогда
ДвоичныеДанные = ПолучитьИзВременногоХранилища(СтрокаФормы.АдресВоВременномХранилище);
СтрокаОбъекта.Файл = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот метод является стандартом для работы с файлами на управляемых формах, если файлы по какой-то причине всё же должны находиться внутри объекта.
Рассмотренные выше методы решают техническую проблему отображения и сохранения данных на форме. Однако хранение тяжелых бинарных данных (картинок, документов PDF, архивов) непосредственно в табличной части документа порождает серьезные проблемы при эксплуатации системы в реальном бизнесе:
ПолучитьОбъект() (например, при проведении документа, проверке подписок или работе регламентных заданий), СУБД считывает в оперативную память сервера 1С абсолютно все данные объекта, включая все строки табличных частей. Если у вас в документе прикреплено 15 фотографий по 5 Мб, то при банальном перепроведении документа сервер будет тратить более 75 Мб оперативной памяти на один сеанс. Групповое перепроведение документов в таком случае быстро приведет к падению рабочего процесса сервера с ошибкой Out of memory.varbinary(max) / blob существенно замедляет транзакции записи, увеличивает фрагментацию индексов и приводит к частым взаимным блокировкам пользователей при одновременной работе.Давайте выясним, как этот функционал реализован в Библиотеке стандартных подсистем (БСП). Разработчики фирмы "1С" отказались от хранения двоичных данных внутри документов. Вместо этого применяется паттерн "Присоединенные файлы":
Документ.ДоговорКонтрагента) создается подчиненный справочник Справочник.ДоговорыКонтрагентовПрисоединенныеФайлы.ХранилищеЗначения. При этом БСП позволяет настроить хранение не в базе данных, а во внешних томах (на сетевом диске или в облачном хранилище).При такой схеме документ остается легким. Его можно перепроводить, выбирать запросами и передавать по сети без лишней нагрузки. Сами файлы считываются из базы данных только в тот момент, когда пользователь непосредственно нажимает кнопку "Открыть файл". Для удобного добавления изображений в присоединенные файлы можно использовать решение для переноса картинок на форму методом Drag and Drop.
Если вам необходимо связать файлы именно со строками табличной части, но вы не хотите использовать тяжелые справочники БСП, отличным решением будет использование независимого регистра сведений (например, РегистрСведений.ФайлыДокументов).
Создадим регистр со следующей структурой:
ДокументСсылка (тип: Ссылка на ваш документ, свойство Ведущее установлено в Истина).КлючСтроки (тип: УникальныйИдентификатор или Строка). Это связующее звено со строкой ТЧ.ФайлХранилище (тип: ХранилищеЗначения).Для реализации этой схемы в табличную часть документа мы добавляем реквизит КлючСтроки с типом Строка (в нем будем генерировать уникальный идентификатор строки при ее создании).
Проанализируем преимущества такого подхода:
Редактирование файлов в такой схеме происходит через форму документа: при открытии данные считываются из регистра во временное хранилище по ключу строки, а при записи документа — записываются обратно в регистр сведений.
Мы детально разобрали особенности работы с двоичными данными на управляемых формах 1С. Подведем итог: