При разработке в среде «1С:Предприятие 8.3» (особенно на управляемых формах) часто возникает классическая задача: при изменении данных в строке табличной части необходимо автоматически рассчитать или получить из базы данных зависимый показатель. Например, при выборе сотрудника (юриста) и услуги в строке документа нужно мгновенно подтянуть актуальную ставку (тариф) из регистра сведений.
В этой статье мы подробно разберем, как правильно реализовать передачу нескольких реквизитов строки табличной части в параметры запроса на сервере, заполнить результирующий реквизит и сделать это максимально быстро, безопасно и в соответствии со стандартами разработки фирмы «1С».
Мы проанализируем типичные ошибки начинающих разработчиков, оптимизируем обращение к серверу и напишем производительный код.
---
Прежде чем перейти к правильному коду, давайте разберем, какие ошибки часто допускают разработчики при решении этой задачи:
РеквизитФормыВЗначение и ЗначениеВРеквизитФормы для получения значений из текущей строки табличной части. Эти методы предназначены для конвертации сложных объектов формы (например, всей таблицы или всего документа) в прикладные объекты базы данных на сервере. Применять их для чтения простой строки — грубейшая ошибка, которая приводит к падению производительности.&НаСервере): Использование контекстных серверных процедур заставляет платформу гонять «тяжелый» контекст всей формы (со всеми строками табличной части, реквизитами шапки и служебными данными) туда и обратно между клиентом и сервером. При работе по сети или при больших размерах документов это вызывает заметные «зависания» интерфейса.ГДЕ вместо передачи параметров непосредственно в параметры виртуальной таблицы (например, в СрезПоследних). Это заставляет СУБД сначала выбрать весь срез последних, а уже затем отсекать лишнее, что крайне неэффективно.Выгрузить() для получения одного значения: Выгрузка результата запроса в таблицу значений на сервере расходует оперативную память СУБД и сервера приложений 1С. Для получения единственного числа (тарифа) это абсолютно избыточно.---
Решим задачу поэтапно. Наша цель — получить актуальную ставку юриста по конкретной услуге из регистра сведений СтавкаЮристов на дату документа и для конкретной организации.
Для начала настроим логику на клиенте. Нам нужно реагировать на изменение колонок «Юрист» и «Услуга» в табличной части. Для этого мы привяжем их к обработчикам событий ПриИзменении.
Перед тем как вызвать сервер, обязательно проверим, заполнены ли оба аналитических разреза. Если хотя бы одно поле пустое, мы просто обнуляем ставку и прерываем выполнение, сохраняя ресурсы сервера.
&НаКлиенте
Процедура УслугиСебестоимостьЮристПриИзменении(Элемент)
РассчитатьСтавкуСтрокиТЧ();
КонецПроцедуры
&НаКлиенте
Процедура УслугиСебестоимостьУслугаПриИзменении(Элемент)
РассчитатьСтавкуСтрокиТЧ();
КонецПроцедуры
Теперь напишем вспомогательную процедуру расчета ставки строки, которая будет выполняться на клиенте и управлять передачей параметров на сервер:
&НаКлиенте
Процедура РассчитатьСтавкуСтрокиТЧ()
// Получаем текущие данные строки табличной части
ТекДанные = Элементы.УслугиСебестоимость.ТекущиеДанные;
Если ТекДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Защита от лишних вызовов сервера: проверяем заполнение ключевых реквизитов
Если НЕ ЗначениеЗаполнено(ТекДанные.Юрист) Или НЕ ЗначениеЗаполнено(ТекДанные.Услуга) Тогда
ТекДанные.Ставка = 0;
Возврат;
КонецЕсли;
// Вызываем быстрый безконтекстный серверный метод, передавая только нужные параметры
ТекДанные.Ставка = ПолучитьТарифЮристаНаСервере(
Объект.Дата,
Объект.Организация,
ТекДанные.Юрист,
ТекДанные.Услуга
);
КонецПроцедуры
---
Для получения тарифа мы создаем функцию с директивой &НаСервереБезКонтекста. Это гарантирует, что на сервер передаются только четыре простых параметра (дата, организация, юрист, услуга), а не вся форма документа. Такой вызов отрабатывает мгновенно.
Внутри функции мы пишем запрос к виртуальной таблице СрезПоследних. Обратите внимание, что все отборы мы накладываем строго внутри параметров виртуальной таблицы. Мы также полностью отказываемся от избыточного предложения СГРУППИРОВАТЬ ПО, которое замедляло работу СУБД.
Вместо метода Выгрузить() мы используем легковесную выборку с помощью метода Выбрать().
&НаСервереБезКонтекста
Функция ПолучитьТарифЮристаНаСервере(Знач ДатаДокумента, Знач Организация, Знач Юрист, Знач Услуга)
Запрос = Новый Запрос;
// Накладываем жесткие отборы непосредственно в параметрах виртуальной таблицы
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(СтавкаЮристовСрезПоследних.Стоимость, 0) КАК Тариф
|ИЗ
| РегистрСведений.СтавкаЮристов.СрезПоследних(
| &Период,
| Организация = &Организация
| И Юрист = &Юрист
| И Услуга = &Услуга) КАК СтавкаЮристовСрезПоследних";
// Если дата документа не заполнена (например, новый документ), используем текущую дату
Запрос.УстановитьПараметр("Период", ?(ЗначениеЗаполнено(ДатаДокумента), ДатаДокумента, ТекущаяДата()));
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Юрист", Юрист);
Запрос.УстановитьПараметр("Услуга", Услуга);
// Выполняем запрос и обходим результат с помощью выборки
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Тариф;
КонецЕсли;
Возврат 0;
КонецФункции
---
Представим ситуацию: пользователь заполнил табличную часть из 50 строк. После этого он решает изменить дату документа или организацию в шапке. Тарифы в строках автоматически становятся неактуальными, так как срезы последних в регистре сведений зависят от даты и организации.
Если мы будем пересчитывать строки в цикле на клиенте, вызывая серверную безконтекстную функцию для каждой строки по отдельности, то система сделает 50 сетевых запросов к серверу. Это приведет к сильному подвисанию программы. В таких случаях необходимо использовать контекстный серверный вызов, выполняющий пакетный пересчет всех строк за один раз.
Реализуем процедуру массового пересчета табличной части на сервере:
&НаСервере
Процедура ПересчитатьВсеСтавкиНаСервере()
// Если пересчитывать нечего, прерываем выполнение
Если Объект.УслугиСебестоимость.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Используем запрос для пакетного получения всех актуальных ставок для строк ТЧ
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_ТЧ.НомерСтроки КАК НомерСтроки,
| ЕСТЬNULL(СтавкаЮристовСрезПоследних.Стоимость, 0) КАК Тариф
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТабличнаяЧасть КАК ВТ_ТЧ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ИсходныеДанные.НомерСтроки КАК НомерСтроки,
| ЕСТЬNULL(СтавкаЮристовСрезПоследних.Стоимость, 0) КАК Тариф
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтавкаЮристов.СрезПоследних(
| &Период,
| Организация = &Организация
| И Юрист В (ВЫБРАТЬ РАЗЛИЧНЫЕ Юрист ИЗ ВТ_ИсходныеДанные)
| И Услуга В (ВЫБРАТЬ РАЗЛИЧНЫЕ Услуга ИЗ ВТ_ИсходныеДанные)) КАК СтавкаЮристовСрезПоследних
| ПО ВТ_ИсходныеДанные.Юрист = СтавкаЮристовСрезПоследних.Юрист
| И ВТ_ИсходныеДанные.Услуга = СтавкаЮристовСрезПоследних.Услуга";
Запрос.УстановитьПараметр("Период", ?(ЗначениеЗаполнено(Объект.Дата), Объект.Дата, ТекущаяДата()));
Запрос.УстановитьПараметр("Организация", Объект.Организация);
// Передаем табличную часть формы в качестве параметра запроса напрямую
Запрос.УстановитьПараметр("ТабличнаяЧасть", Объект.УслугиСебестоимость.Выгрузить());
РезультатТаблицей = Запрос.Выполнить().Выгрузить();
// Обходим строки исходной ТЧ и сопоставляем результаты по индексу (номеру строки)
Для Каждого СтрокаТЧ Из Объект.УслугиСебестоимость Цикл
НайденнаяСтрока = РезультатТаблицей.Найти(СтрокаТЧ.НомерСтроки, "НомерСтроки");
Если НайденнаяСтрока <> Неопределено Тогда
СтрокаТЧ.Ставка = НайденнаяСтрока.Тариф;
Иначе
СтрокаТЧ.Ставка = 0;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ОрганизацияПриИзменении(Элемент)
ПересчитатьВсеСтавкиНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура ДатаПриИзменении(Элемент)
ПересчитатьВсеСтавкиНаСервере();
КонецПроцедуры
Такой подход демонстрирует высокий класс оптимизации работы с СУБД. Программа выполняет всего один запрос к базе данных, пакетно сопоставляет информацию для всех строк и за один такт обновляет табличную часть на клиенте.
---
Для корректной и быстрой работы интерфейса 1С всегда разделяйте задачи на одиночные интерактивные операции (при изменении одной строки пользователем) и групповые операции (при изменении шапки документа).
Используйте &НаСервереБезКонтекста для быстрых точечных запросов, всегда накладывайте жесткие параметры на виртуальные таблицы и избегайте необоснованного применения методов выгрузки данных в таблицы значений на сервере.