Как правильно организовать загрузку, просмотр и сопоставление данных внешнего файла в тонком клиенте 1С

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия IT и автоматизация бизнеса
← На главную

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

Выясним причину медленной работы при переходе на тонкий клиент

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

  1. Построчные серверные вызовы. Если чтение файла или обход строк идет на клиенте, а поиск ссылки по коду или наименованию вызывает серверную процедуру для каждой строки, приложение совершает тысячи сетевых транзакций туда и обратно.
  2. Неявные вызовы при обходе коллекций формы. Таблица формы на клиенте представлена типом ДанныеФормыКоллекция. Платформа подгружает в нее строки порциями (по мере прокрутки). Если на клиенте запустить обход этой коллекции циклом Для Каждого или попытаться найти в ней данные, платформа будет незаметно для разработчика слать синхронные запросы к серверу за новыми пачками строк.
  3. Построчная модификация элементов формы. Добавление данных строка за строкой непосредственно в реквизит формы на клиенте приводит к постоянному пересчету внутреннего состояния интерфейса и перерисовке таблицы.

Как хранятся и передаются данные таблицы в управляемой форме

Рассмотрим подробнее механизм работы реквизитов формы. Когда мы создаем в форме реквизит с типом ТаблицаЗначений, важно понимать следующее:

  1. На клиенте и в серверном контексте формы этот объект существует в виде специальной оболочки ДанныеФормыКоллекция, а не как привычная серверная ТаблицаЗначений.
  2. При вызове процедур с директивой &НаСервере платформа не пересылает таблицу целиком туда и обратно каждый раз — передаются только зарегистрированные изменения (добавленные, измененные или удаленные пользователем строки).
  3. При неконтекстном вызове &НаСервереБезКонтекста контекст формы не передается вовсе, что максимально экономит сетевой трафик.
  4. Прямое преобразование между реальной таблицей значений и коллекцией формы на сервере выполняется методами ЗначениеВРеквизитФормы() и РеквизитФормыВЗначение() (или ДанныеФормыВЗначение()).

Пошаговый алгоритм оптимальной загрузки

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

  1. Помещаем файл на сервер через временное хранилище без сохранения на локальном диске сервера.
  2. Считываем файл целиком на сервере в локальную серверную таблицу значений.
  3. Выполняем пакетное сопоставление со справочниками базы данных одним SQL-запросом через временную таблицу.
  4. Заполняем реквизит формы пакетом за одну операцию и возвращаем управление пользователю для просмотра и фильтрации.
  5. По нажатию кнопки формирования создаем документы на сервере, используя отредактированные данные таблицы.

Шаг 1. Передача файла во временное хранилище на клиенте

Посмотрим на реализацию выбора и передачи файла. Так как сервер 1С не имеет прямого доступа к файловой системе клиентского компьютера (к путям вида C:\...), передачу файла необходимо выполнять через двоичные данные:


&НаКлиенте
Процедура ЗагрузитьФайл(Команда)

    Оповещение = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
    ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
    ПараметрыДиалога.Заголовок = "Выберите файл обмена (DBF)";
    ПараметрыДиалога.Фильтр = "Файлы DBF (*.dbf)|*.dbf";
    
    НачатьПомещениеФайлаНаСервер(Оповещение, , , ПараметрыДиалога, УникальныйИдентификатор);

КонецПроцедуры

&НаКлиенте
Процедура ПослеВыбораФайла(Результат, ДополнительныеПараметры) Экспорт

    Если Результат = Неопределено Тогда
        Возврат;
    КонецЕсли;

    ОбработатьФайлНаСервере(Результат.Адрес);

КонецПроцедуры

Шаг 2 и 3. Чтение файла и пакетное сопоставление данных на сервере

Рассмотрим серверную часть обработки. Вся тяжелая работа выполняется строго на сервере. Мы читаем файл через объект XBase во временную таблицу значений, а затем связываем её со справочниками базы данных одним запросом:


&НаСервере
Процедура ОбработатьФайлНаСервере(АдресВоВременномХранилище)

    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВоВременномХранилище);
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("dbf");
    ДвоичныеДанные.Записать(ИмяВременногоФайла);

    ТЗДанные = Новый ТаблицаЗначений;
    ТЗДанные.Колонки.Добавить("КодТовара", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20)));
    ТЗДанные.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
    ТЗДанные.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

    ФайлDBF = Новый XBase;
    ФайлDBF.ОткрытьФайл(ИмяВременногоФайла);

    Если ФайлDBF.Открыта() Тогда
        ФайлDBF.Первая();
        Пока Не ФайлDBF.ВКонце() Цикл
            НоваяСтрока = ТЗДанные.Добавить();
            НоваяСтрока.КодТовара = СокрЛП(ФайлDBF.CODE);
            НоваяСтрока.Количество = ФайлDBF.KOL;
            НоваяСтрока.Цена = ФайлDBF.PRICE;
            ФайлDBF.Следующая();
        КонецЦикла;
        ФайлDBF.ЗакрытьФайл();
    КонецЕсли;

    УдалитьФайлы(ИмяВременногоФайла);

    // Пакетное сопоставление через временную таблицу запроса
    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Данные.КодТовара КАК КодТовара,
    |    Данные.Количество КАК Количество,
    |    Данные.Цена КАК Цена
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |    &ТаблицаДанных КАК Данные;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ.КодТовара КАК КодТовара,
    |    ВТ.Количество КАК Количество,
    |    ВТ.Цена КАК Цена,
    |    Номенклатура.Ссылка КАК Номенклатура
    |ИЗ
    |    ВТ_ИсходныеДанные КАК ВТ
    |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
    |        ПО ВТ.КодТовара = Номенклатура.Код";

    Запрос.УстановитьПараметр("ТаблицаДанных", ТЗДанные);
    РезультатЗапроса = Запрос.Выполнить().Выгрузить();

    // Загрузка результата в реквизит формы целиком
    ЗначениеВРеквизитФормы(РезультатЗапроса, "ТаблицаЗагрузки");

КонецПроцедуры

Шаг 4. Просмотр и быстрая фильтрация на клиенте

Когда данные помещены в ТаблицаЗагрузки, пользователь видит их в табличном поле на форме. Чтобы фильтровать данные на стороне клиента без отправки запросов на сервер, используем штатное свойство элемента формы ОтборСтрок:


&НаКлиенте
Процедура УстановитьОтборПоОшибкам(Команда)

    // Быстрый отбор строк, где номенклатура не сопоставилась (пустая ссылка)
    Элементы.ТаблицаЗагрузки.ОтборСтрок = Новый ФиксированнаяСтруктура("Номенклатура", ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка"));

КонецПроцедуры

&НаКлиенте
Процедура СброситьОтбор(Команда)

    Элементы.ТаблицаЗагрузки.ОтборСтрок = Неопределено;

КонецПроцедуры

Важный момент: свойство ОтборСтрок работает полностью на клиенте и не нагружает канал связи, однако поддерживает фильтрацию только на строгое равенство значений.

Шаг 5. Формирование документов по отредактированным данным

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


&НаСервере
Процедура СоздатьДокументыНаСервере()

    ТЗ = РеквизитФормыВЗначение("ТаблицаЗагрузки");

    // Исключаем пустые или некорректные строки перед формированием
    Отбор = Новый Структура("Номенклатура", Справочники.Номенклатура.ПустаяСсылка());
    ОшибочныеСтроки = ТЗ.НайтиСтроки(Отбор);
    Для Каждого СтрокаОшибки Из ОшибочныеСтроки Цикл
        ТЗ.Строки.Удалить(СтрокаОшибки);
    КонецЦикла;

    // Группируем данные и формируем документы в транзакции
    // При необходимости можно свернуть данные методом ТЗ.Свернуть(...)
    
    // После успешного создания очищаем реквизит формы
    ТаблицаЗагрузки.Очистить();

КонецПроцедуры

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

← На главную