Как получить доступ к табличной части документа и обойти ее строки в 1С 8.3

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

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

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

Разница между парадигмами 1С 7.7 и 1С 8.x

В версии 1С 7.7 для работы с табличной частью документа использовались специализированные методы объекта типа ВыбратьСтроки() и ПолучитьСтроку(). Вся работа происходила в едином контексте, а обращение к реквизитам строки шло напрямую через объект документа.

В платформе 1С 8.x архитектура кардинально изменилась:

  1. Разделение на Клиент и Сервер. Интерактивные действия (нажатие кнопок, выбор файлов) происходят на клиенте, а непосредственная работа с базой данных (чтение документов, проведение, выборка из табличных частей) — исключительно на сервере.
  2. Объектная модель. Табличная часть документа теперь представляет собой полноценную коллекцию данных типа ДокументТабличнаяЧасть, состоящую из строк типа ЧислоСтрокиТабличнойЧасти.
  3. Ссылки и Объекты. Для чтения данных нам достаточно иметь ссылку на документ (ДокументСсылка), в то время как для изменения данных базы требуется получить сам объект (ДокументОбъект).

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

Шаг 1. Передача ссылки на документ с клиента на сервер

Представим, что у нас есть внешняя обработка. На ее форме расположен реквизит с именем ТО (тип данных — ссылка на документ, например, ДокументСсылка.ТребованиеОтделения). Также на форме присутствует кнопка, связанная с клиентской процедурой.

Нам не требуется извлекать уникальные идентификаторы или преобразовывать ссылку в строку. Объект ссылки в 1С 8.3 прекрасно передается между клиентом и сервером в качестве параметра процедуры. Напишем стартовую процедуру на клиенте:


&НаКлиенте
Процедура СфрЦены(Команда)
    
    // Передаем ссылку на выбранный документ напрямую в серверный метод
    РасчетЦенНаСервере(ТО);
    
КонецПроцедуры

Шаг 2. Обход табличной части на сервере и типичная ошибка новичков

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


// ПРИМЕР НЕПРАВИЛЬНОГО КОДА! ТАК ПИСАТЬ НЕЛЬЗЯ!
&НаСервере
Процедура РасчетЦенНаСервере(ВыбДок)
    
    Для Каждого ТекСтрокаТовары Из ВыбДок.Товары Цикл
        // Ошибка: Попытка обратиться к реквизиту всей коллекции, а не конкретной строки!
        Сообщить(ВыбДок.Товары.Номенклатура); 
    КонецЦикла;
    
КонецПроцедуры

При выполнении такого кода система выдаст ошибку: «Поле объекта не обнаружено (Номенклатура)». Выясним причину этого поведения.

Объект ВыбДок.Товары — это вся табличная часть целиком (таблица). У самой таблицы нет реквизита Номенклатура, этот реквизит есть только у конкретной строки. В цикле Для Каждого ... Из ... Цикл переменная ТекСтрокаТовары на каждой итерации принимает значение текущей строки. Следовательно, обращаться нужно именно к этой переменной.

Рассмотрим правильный вариант реализации серверного метода:


&НаСервере
Процедура РасчетЦенНаСервере(ВыбДок)
    
    // Проверяем, заполнена ли ссылка
    Если Не ЗначениеЗаполнено(ВыбДок) Тогда
        ВызватьИсключение "Документ не выбран!";
    КонецЕсли;

    // Обходим строки табличной части "Товары"
    Для Каждого ТекСтрокаТовары Из ВыбДок.Товары Цикл
        
        // Обращаемся к свойствам конкретной строки
        ИмяТовара = ТекСтрокаТовары.Номенклатура;
        Сообщить("Выбран товар: " + ИмяТовара);
        
    КонецЦикла;
    
КонецПроцедуры

Шаг 3. Оптимизация работы: получение цен из регистра сведений

Зачастую простого вывода наименований недостаточно. Автор исходной темы планирует осуществлять поиск цен по номенклатуре в регистре сведений ЦеныНоменклатуры для последующего расчета средних показателей.

Критически важный момент! Если вы будете искать цену для каждого товара внутри цикла по строкам (используя методы типа РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(...)), система будет делать отдельный запрос к базе данных на каждую строчку документа. Если в документе 100 строк — это 100 отдельных запросов к СУБД. Такое архитектурное решение называется "Запрос в цикле" и является грубейшей ошибкой, снижающей производительность системы в десятки раз.

Проанализируем, как сделать эту операцию оптимально с помощью запроса, который получит все цены за один раз.


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

Использование конструкции ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей СрезПоследних позволяет моментально сопоставить все товары из документа с их актуальными ценами в базе данных за одно-единственное обращение к СУБД.

Когда использовать ПолучитьОбъект(), а когда достаточно Ссылки

В процессе обсуждения на форумах часто советуют использовать метод ПолучитьОбъект(). Разберем детально разницу:


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

Таким образом, мы детально разобрали особенности работы с табличными частями в 1С 8.3. Понимание разницы между ссылочными типами данных и объектами, а также отказ от выполнения запросов внутри циклов обхода коллекций позволят вам писать быстрый, чистый и профессиональный код.

← На главную