Как реализовать множественный подбор с переносом нескольких колонок в 1С 8.3

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

Разработка интерфейса подбора номенклатуры или других элементов в табличную часть документа — одна из самых частых задач в практике программиста 1С. При реализации множественного выбора разработчики регулярно сталкиваются с двумя классическими проблемами: ошибкой доступа к реквизитам объекта на клиенте и проблемой низкой производительности из-за избыточных серверных вызовов в цикле (так называемая ошибка N+1 запроса).

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

Выясняем причину: почему возникает ошибка «Поле объекта не обнаружено»?

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


&НаКлиенте
Процедура НоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    Для Каждого ВыбранныйЭлемент Из ВыбранноеЗначение Цикл
        НоваяСтрока = Объект.Номенклатура.Добавить;
        НоваяСтрока.Наименование = ВыбранныйЭлемент;
        НоваяСтрока.МестоХранения = ВыбранныйЭлемент.МестоХранения; // Ошибка здесь!
    КонецЦикла;
КонецПроцедуры

При выполнении этого кода система выдает ошибку: Поле объекта не обнаружено (МестоХранения). Давайте разберем, почему это происходит.

Платформа «1С:Предприятие 8» строго разделяет контекст выполнения кода на клиентский и серверный. Переменная ВыбранныйЭлемент на клиенте является обычной ссылкой (например, типа СправочникСсылка.Номенклатура). На клиенте ссылка — это лишь уникальный идентификатор (GUID) объекта в базе данных. Она не содержит внутри себя реальных данных объекта.

Попытка обратиться через точку к реквизиту ссылки на клиенте (например, ВыбранныйЭлемент.МестоХранения) неявным образом заставляет платформу обратиться к серверу и базе данных для извлечения этого свойства. В управляемом приложении такое обращение заблокировано на уровне платформы для обеспечения предсказуемой производительности интерфейса. Именно поэтому система выдает ошибку отсутствия поля.

Анализ второй ошибки: серверный вызов в цикле

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


&НаКлиенте
Процедура НоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    Для Каждого ВыбранныйЭлемент Из ВыбранноеЗначение Цикл
        НоваяСтрока = Объект.Номенклатура.Добавить();
        НоваяСтрока.Наименование = ВыбранныйЭлемент;
        НоваяСтрока.МестоХранения = ПолучитьМестоХранения(ВыбранноеЗначение); // Ошибка передачи параметра!
    КонецЦикла;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьМестоХранения(ВыбранноеЗначение)
    Возврат ВыбранноеЗначение.МестоХранения;
КонецФункции

Здесь допущены сразу две критические ошибки:

  1. Передача массива вместо элемента: Переменная ВыбранноеЗначение представляет собой массив (так как включен множественный выбор). В цикле нужно было передавать итератор — ВыбранныйЭлемент. Передавая весь массив на сервер и пытаясь получить свойство ВыбранноеЗначение.МестоХранения, мы гарантированно получаем ту же ошибку, так как у объекта типа Массив нет свойства МестоХранения.
  2. Катастрофическая потеря производительности: Даже если бы автор передавал ВыбранныйЭлемент, вызов серверной функции внутри цикла Для Каждого порождает сетевой запрос на каждую выбранную строку. Если пользователь выберет 50 товаров, система сделает 100 запросов к серверу (по 2 на каждую итерацию). В реальных условиях работы (особенно через веб-клиент или тонкий клиент по VPN) это приведет к зависанию программы на несколько секунд.

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

Решение 1. Оптимизированный пакетный запрос на сервере (за один вызов)

Самый надежный способ быстро получить реквизиты для множества выбранных элементов — передать весь массив на сервер за один раз, выполнить один запрос к базе данных, вернуть результат на клиент в виде массива структур и заполнить табличную часть.

Давайте пошагово разберем реализацию этого метода. В модуле формы вашего документа напишем следующий код:


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

&НаСервереБезКонтекста
Функция ПолучитьДанныеДляПодбораИзРегистра(МассивНоменклатуры)
    
    // Используем запрос для пакетного извлечения данных из регистра сведений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	РегистрСведений.Номенклатура КАК Номенклатура,
        |	РегистрСведений.МестоХранения КАК МестоХранения,
        |	РегистрСведений.СтатьяФинансирования КАК СтатьяФинансирования
        |ИЗ
        |	РегистрСведений.МестаХраненияИФинансирование КАК РегистрСведений
        |ГДЕ
        |	РегистрСведений.Номенклатура В(&МассивНоменклатуры)";
        
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    
    ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
    
    // Таблицу значений нельзя напрямую передать на клиент, преобразуем её в массив структур
    МассивРезультатов = Новый Массив;
    Для Каждого СтрокаТЗ Из ТаблицаРезультата Цикл
        
        СтруктураСтроки = Новый Структура("Номенклатура, МестоХранения, СтатьяФинансирования");
        ЗаполнитьЗначенияСвойств(СтруктураСтроки, СтрокаТЗ);
        МассивРезультатов.Добавить(СтруктураСтроки);
        
    КонецЦикла;
    
    Возврат МассивРезультатов;
    
КонецФункции

Преимущества этого способа: мы делаем ровно один запрос к серверу независимо от того, выбрал пользователь 5 позиций или 500. База данных оптимизирует поиск по индексу операции В(&МассивНоменклатуры), что обеспечивает моментальный отклик интерфейса.

Решение 2. Передача данных напрямую из формы подбора (без запросов к БД)

Если в форме выбора справочника Номенклатура («ФормаВыбораДляЗаявки») уже выведен динамический список, который содержит колонки «МестоХранения» и «СтатьяФинансирования» (например, полученные левым соединением в запросе динамического списка), то обращаться к серверу вообще нет необходимости! Данные уже прочитаны платформой и отображаются на экране пользователя.

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

Рассмотрим по шагам реализацию этого элегантного и самого быстрого метода.

Шаг 1. Настройка формы подбора («ФормаВыбораДляЗаявки»)

В свойствах таблицы динамического списка на форме выбора переопределим стандартное событие выбора (двойной клик или нажатие Enter), назначив обработчик СписокВыбор:


&НаКлиенте
Процедура СписокВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
    
    // Отменяем стандартное поведение платформы (которое вернуло бы только ссылку на Номенклатуру)
    СтандартнаяОбработка = Ложь;
    
    МассивВыбора = Новый Массив;
    
    // Обходим все выделенные пользователем строки в динамическом списке
    Для Каждого ИдентификаторСтроки Из Элементы.Список.ВыделенныеСтроки Цикл
        
        // Метод ДанныеСтроки() считывает ВСЕ отображаемые данные строки на клиенте без обращения к серверу
        ДанныеСтроки = Элементы.Список.ДанныеСтроки(ИдентификаторСтроки);
        
        Если ДанныеСтроки <> Неопределено Тогда
            
            СтруктураДанных = Новый Структура;
            СтруктураДанных.Вставить("Номенклатура", ДанныеСтроки.Ссылка);
            СтруктураДанных.Вставить("МестоХранения", ДанныеСтроки.МестоХранения);
            СтруктураДанных.Вставить("СтатьяФинансирования", ДанныеСтроки.СтатьяФинансирования);
            
            МассивВыбора.Добавить(СтруктураДанных);
            
        КонецЕсли;
        
    КонецЦикла;
    
    // Оповещаем форму-владельца о сделанном выборе, передавая подготовленный массив структур
    ОповеститьОВыборе(МассивВыбора);
    
КонецПроцедуры

Шаг 2. Прием данных в форме документа

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


&НаКлиенте
Процедура НоменклатураОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    
    // Проверяем тип пришедших данных
    Если ТипЗнч(ВыбранноеЗначение) <> Тип("Массив") Тогда
        Возврат;
    КонецЕсли;
    
    Для Каждого ЭлементВыбора Из ВыбранноеЗначение Цикл
        
        // Проверяем, что получили структуру с подготовленными реквизитами
        Если ТипЗнч(ЭлементВыбора) = Тип("Структура") Тогда
            
            НоваяСтрока = Объект.Номенклатура.Добавить();
            НоваяСтрока.Наименование = ЭлементВыбора.Номенклатура;
            НоваяСтрока.МестоХранения = ЭлементВыбора.МестоХранения;
            НоваяСтрока.СтатьяФинансирования = ЭлементВыбора.СтатьяФинансирования;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

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

Резюме и рекомендации

Выбор оптимального решения зависит от архитектуры вашей конфигурации:

Избегайте обращений к серверу внутри циклов и никогда не пытайтесь получить реквизиты ссылочных типов на клиенте напрямую через точку. Соблюдение этих простых правил сделает ваши конфигурации быстрыми, надежными и приятными в использовании!

← На главную