Перевод старых обработок с обычных форм на управляемые формы в тонком клиенте нередко приводит к резкому падению производительности: типовая загрузка объемом всего в 1 000 строк может выполняться десятки минут. Проблема кроется в некорректном распределении логики между клиентом и сервером, а также в скрытых серверных вызовах. Разберем по шагам, как устроено взаимодействие тонкого клиента с сервером при обработке табличных данных и как реализовать быструю загрузку файла с предварительным просмотром и интерактивным отбором.
Проанализируем типовую ситуацию: в толстом клиенте чтение файла и поиск по справочникам выполнялись в одном цикле быстро, так как весь контекст и база данных находились рядом. В тонком клиенте такой подход порождает критические задержки по следующим причинам:
ДанныеФормыКоллекция. Платформа подгружает в нее строки порциями (по мере прокрутки). Если на клиенте запустить обход этой коллекции циклом Для Каждого или попытаться найти в ней данные, платформа будет незаметно для разработчика слать синхронные запросы к серверу за новыми пачками строк.Рассмотрим подробнее механизм работы реквизитов формы. Когда мы создаем в форме реквизит с типом ТаблицаЗначений, важно понимать следующее:
ДанныеФормыКоллекция, а не как привычная серверная ТаблицаЗначений.&НаСервере платформа не пересылает таблицу целиком туда и обратно каждый раз — передаются только зарегистрированные изменения (добавленные, измененные или удаленные пользователем строки).&НаСервереБезКонтекста контекст формы не передается вовсе, что максимально экономит сетевой трафик.ЗначениеВРеквизитФормы() и РеквизитФормыВЗначение() (или ДанныеФормыВЗначение()).Разберем архитектуру, которая позволяет загрузить и сопоставить тысячи строк за считанные секунды без блокировки интерфейса:
Посмотрим на реализацию выбора и передачи файла. Так как сервер 1С не имеет прямого доступа к файловой системе клиентского компьютера (к путям вида C:\...), передачу файла необходимо выполнять через двоичные данные:
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Заголовок = "Выберите файл обмена (DBF)";
ПараметрыДиалога.Фильтр = "Файлы DBF (*.dbf)|*.dbf";
НачатьПомещениеФайлаНаСервер(Оповещение, , , ПараметрыДиалога, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораФайла(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
ОбработатьФайлНаСервере(Результат.Адрес);
КонецПроцедуры
Рассмотрим серверную часть обработки. Вся тяжелая работа выполняется строго на сервере. Мы читаем файл через объект XBase во временную таблицу значений, а затем связываем её со справочниками базы данных одним запросом:
&НаСервере
Процедура ОбработатьФайлНаСервере(АдресВоВременномХранилище)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВоВременномХранилище);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("dbf");
ДвоичныеДанные.Записать(ИмяВременногоФайла);
ТЗДанные = Новый ТаблицаЗначений;
ТЗДанные.Колонки.Добавить("КодТовара", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20)));
ТЗДанные.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗДанные.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ФайлDBF = Новый XBase;
ФайлDBF.ОткрытьФайл(ИмяВременногоФайла);
Если ФайлDBF.Открыта() Тогда
ФайлDBF.Первая();
Пока Не ФайлDBF.ВКонце() Цикл
НоваяСтрока = ТЗДанные.Добавить();
НоваяСтрока.КодТовара = СокрЛП(ФайлDBF.CODE);
НоваяСтрока.Количество = ФайлDBF.KOL;
НоваяСтрока.Цена = ФайлDBF.PRICE;
ФайлDBF.Следующая();
КонецЦикла;
ФайлDBF.ЗакрытьФайл();
КонецЕсли;
УдалитьФайлы(ИмяВременногоФайла);
// Пакетное сопоставление через временную таблицу запроса
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| Данные.КодТовара КАК КодТовара,
| Данные.Количество КАК Количество,
| Данные.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТаблицаДанных КАК Данные;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.КодТовара КАК КодТовара,
| ВТ.Количество КАК Количество,
| ВТ.Цена КАК Цена,
| Номенклатура.Ссылка КАК Номенклатура
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ВТ.КодТовара = Номенклатура.Код";
Запрос.УстановитьПараметр("ТаблицаДанных", ТЗДанные);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// Загрузка результата в реквизит формы целиком
ЗначениеВРеквизитФормы(РезультатЗапроса, "ТаблицаЗагрузки");
КонецПроцедуры
Когда данные помещены в ТаблицаЗагрузки, пользователь видит их в табличном поле на форме. Чтобы фильтровать данные на стороне клиента без отправки запросов на сервер, используем штатное свойство элемента формы ОтборСтрок:
&НаКлиенте
Процедура УстановитьОтборПоОшибкам(Команда)
// Быстрый отбор строк, где номенклатура не сопоставилась (пустая ссылка)
Элементы.ТаблицаЗагрузки.ОтборСтрок = Новый ФиксированнаяСтруктура("Номенклатура", ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка"));
КонецПроцедуры
&НаКлиенте
Процедура СброситьОтбор(Команда)
Элементы.ТаблицаЗагрузки.ОтборСтрок = Неопределено;
КонецПроцедуры
Важный момент: свойство ОтборСтрок работает полностью на клиенте и не нагружает канал связи, однако поддерживает фильтрацию только на строгое равенство значений.
После того как пользователь проверил данные, заполнил незаполненные позиции и нажал кнопку формирования документов, управление передается в серверный контекст. Преобразуем реквизит формы обратно в таблицу значений и группируем данные стандартными методами:
&НаСервере
Процедура СоздатьДокументыНаСервере()
ТЗ = РеквизитФормыВЗначение("ТаблицаЗагрузки");
// Исключаем пустые или некорректные строки перед формированием
Отбор = Новый Структура("Номенклатура", Справочники.Номенклатура.ПустаяСсылка());
ОшибочныеСтроки = ТЗ.НайтиСтроки(Отбор);
Для Каждого СтрокаОшибки Из ОшибочныеСтроки Цикл
ТЗ.Строки.Удалить(СтрокаОшибки);
КонецЦикла;
// Группируем данные и формируем документы в транзакции
// При необходимости можно свернуть данные методом ТЗ.Свернуть(...)
// После успешного создания очищаем реквизит формы
ТаблицаЗагрузки.Очистить();
КонецПроцедуры
Такой подход исключает паразитный трафик, избавляет от подвисаний клиентского интерфейса и позволяет комфортно обрабатывать массивы данных в тонком клиенте без изменения конфигурации базы данных.