При переходе с обычных форм на управляемые разработчики часто сталкиваются с проблемой: типы данных, которые раньше были доступны «всегда и везде», теперь жестко разделены между клиентом и сервером. Одним из таких «камней преткновения» является объект ХранилищеЗначения. В этой статье мы подробно разберем, почему попытка работать с ним напрямую в табличной части приводит к падению производительности, и как реализовать удобный интерактивный интерфейс для редактирования табличных документов, хранящихся в этом объекте.
Проанализируем ситуацию: у нас есть документ с табличной частью, где в одной из колонок хранится ХранилищеЗначения. В нем упакован ТабличныйДокумент. Наша задача — при активации строки таблицы показывать этот документ в поле на форме, позволять пользователю вносить изменения и сохранять их обратно.
Главная проблема в том, что ХранилищеЗначения не существует на клиенте. Его нельзя добавить в реквизиты формы напрямую, оно не передается в составе ДанныеФормыСтруктура. Если мы будем пытаться извлечь данные, каждый раз вызывая РеквизитФормыВЗначение("Объект"), вносить изменения и записывать объект в базу данных (как это часто делают новички), мы получим крайне медленный интерфейс и огромную нагрузку на сервер и СУБД.
Рассмотрим код, который часто пишут на начальном этапе. Программист пытается принудительно записать весь документ в базу данных только для того, чтобы зафиксировать изменения в одной строке табличной части. Это выглядит примерно так:
&НаСервере
Процедура СохранитьТабличныйДокументСервер()
// Извлекаем весь объект из реквизитов формы (тяжелая операция!)
ДокументОбъект = РеквизитФормыВЗначение("Объект");
// Создаем хранилище
Хранилище = Новый ХранилищеЗначения(ТабличныйДокумент);
// Записываем в ТЧ
ДокументОбъект.ТЧ[ИндексСтроки].Требования = Хранилище;
// Сохраняем в базу (очень тяжелая операция!)
ДокументОбъект.Записать();
КонецПроцедуры
Представим, что в документе 100 строк. Пользователь просто перемещается по ним стрелками. На каждое перемещение система выполняет полную десериализацию объекта, запись в таблицу БД и блокировку транзакции. Мы выясним, как избежать этого избыточного цикла, используя механизмы платформы 1С:Предприятие 8.3.
Это наиболее правильный архитектурный подход. Мы не будем трогать базу данных до момента финального сохранения документа пользователем. Вместо этого мы будем использовать сессионное ВременноеХранилище.
Рассмотрим алгоритм действий по шагам:
АдресДанных (тип Строка).ХранилищеЗначения и помещать их во временное хранилище.АдресДанных запишем полученный адрес.Посмотрим на пример реализации загрузки данных:
&НаСервере
Процедура ПолучитьДанныеДляСтроки(ИдентификаторСтроки)
СтрокаТЧ = Объект.ТЧ.НайтиПоИдентификатору(ИдентификаторСтроки);
// Проверяем, не загружали ли мы данные ранее
Если ПустаяСтрока(СтрокаТЧ.АдресДанных) Тогда
// Получаем ТабДок из реквизита объекта (который еще не в форме, а в данных)
ТабДок = СтрокаТЧ.Требования.Получить();
Если ТабДок = Неопределено Тогда
ТабДок = Новый ТабличныйДокумент;
КонецЕсли;
// Помещаем во временное хранилище с привязкой к форме
СтрокаТЧ.АдресДанных = ПоместитьВоВременноеХранилище(ТабДок, УникальныйИдентификатор);
КонецЕсли;
// Загружаем в поле на форме
ЗначениеИзХранилища = ПолучитьИзВременногоХранилища(СтрокаТЧ.АдресДанных);
// ПолеТабДока — это реквизит формы с типом ТабличныйДокумент
ПолеТабДока.Вывести(ЗначениеИзХранилища);
КонецПроцедуры
Если документов много и они «легкие», мы можем применить кэширование прямо в оперативной памяти клиента. Для этого проанализируем использование объекта Соответствие.
Мы можем создать переменную модуля формы КэшТабДоков. Ключом будет идентификатор строки, а значением — сам объект ТабличныйДокумент. Поскольку ТабличныйДокумент доступен и на сервере, и на клиенте, он прекрасно «летает» между контекстами без упаковки в ХранилищеЗначения.
Важный нюанс: Не делайте вызов сервера напрямую в событии ПриАктивизацииСтроки. Пользователь может быстро листать список, и форма будет «замирать». Используем обработчик ожидания.
&НаКлиенте
Процедура ТЧПриАктивизацииСтроки(Элемент)
// Отключаем предыдущий вызов, если он был
ОтключитьОбработчикОжидания("Подключаемый_ЗагрузитьДанныеСтроки");
// Запускаем небольшую паузу
ПодключитьОбработчикОжидания("Подключаемый_ЗагрузитьДанныеСтроки", 0.3, Истина);
КонецПроцедуры
&НаКлиенте
Процедура Подключаемый_ЗагрузитьДанныеСтроки()
ТекущиеДанные = Элементы.ТЧ.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Проверяем кэш
Если КэшТабДоков.Свойство(ТекущиеДанные.ПолучитьИдентификатор()) Тогда
ПолеТабДока = КэшТабДоков[ТекущиеДанные.ПолучитьИдентификатор()];
Иначе
// Идем на сервер только если в кэше нет
ЗагрузитьДанныеССервера(ТекущиеДанные.ПолучитьИдентификатор());
КонецЕсли;
КонецПроцедуры
Когда пользователь отредактировал ПолеТабДока, нам нужно вернуть его в объект, но опять же — без немедленной записи в БД. Мы должны обновить ВременноеХранилище или наш клиентский кэш.
Финальное сохранение произойдет в системном событии ПередЗаписьюНаСервере. Мы проанализируем ситуацию: только в этот момент мы переберем строки ТЧ, возьмем данные из временных адресов или кэша и упакуем их обратно в ХранилищеЗначения реквизита объекта.
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ПараметрыЗаписи)
Для Каждого СтрокаТЧ Из Объект.ТЧ Цикл
Если ЭтоАдресВременногоХранилища(СтрокаТЧ.АдресДанных) Тогда
Данные = ПолучитьИзВременногоХранилища(СтрокаТЧ.АдресДанных);
// Вот здесь происходит магия упаковки
СтрокаТЧ.Требования = Новый ХранилищеЗначения(Данные);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Выясним причину, по которой даже серверные вызовы могут тормозить. Если вы используете РеквизитФормыВЗначение("Объект"), платформа создает полную копию объекта со всеми его данными. Если в документе сотни строк и в каждой — ХранилищеЗначения, это потребует колоссального объема памяти.
Совет: Используйте точечные методы. Если вам нужно только прочитать данные одной строки, лучше воспользоваться запросом или методом Объект.ТЧ.НайтиПоИдентификатору() напрямую в контексте серверной процедуры формы, не превращая всю форму в ДокументОбъект.
Работа с ХранилищемЗначения в управляемом приложении требует четкого понимания жизненного цикла данных. Помните следующие правила:
ХранилищеЗначения только для физического хранения в БД. Внутри формы работайте с «живыми» объектами (ТабличныйДокумент, Картинка) или их адресами.ПодключитьОбработчикОжидания), чтобы перемещение по списку оставалось плавным даже при наличии серверных вызовов.Используя предложенные методики, мы создадим профессиональный и быстрый интерфейс, который будет корректно работать даже при медленном сетевом соединении и большом объеме данных в документе.