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