Работа с табличными частями (ТЧ) — одна из самых частых задач, с которыми сталкивается разработчик в среде «1С:Предприятие 8». Несмотря на кажущуюся простоту операции очистки, на практике часто возникают ситуации, когда написанный код не выдает ошибок, но табличная часть физически остается заполненной. Или наоборот — система генерирует неочевидные ошибки времени выполнения.
В этой статье мы подробно разберем все аспекты очистки табличных частей. Мы выясним, почему изменения могут пропадать при передаче данных между клиентом и сервером, научимся правильно работать с управляемыми формами, рассмотрим особенности записи объектов базы данных, а также изучим продвинутые методы оптимизации и удаления строк по условию.
Начнем с классической ситуации, когда у нас есть ссылка на документ (например, полученная из реквизита, запроса или внешней обработки), и нам нужно очистить его табличную часть программно без участия пользователя.
Основная ошибка начинающих разработчиков заключается в попытке обратиться к табличной части непосредственно через ссылку. Проанализируем типичный ошибочный код:
// ОШИБОЧНЫЙ КОД
СсылкаНаДокумент = Справочники.Договоры.ПустаяСсылка(); // или Ссылка на Документ
СсылкаНаДокумент.Товары.Очистить(); // Вызовет ошибку выполнения!
Платформа 1С строго разделяет объекты базы данных на два типа: Ссылки (предназначены только для чтения и идентификации) и Объекты (предназначены для изменения данных). Метод Очистить() применим только к объекту.
Рассмотрим по шагам правильный алгоритм работы с объектом базы данных:
ПолучитьОбъект().Очистить() для нужной табличной части.Записать(). Если этого не сделать, изменения останутся только в оперативной памяти компьютера и не будут сохранены в базе данных СУБД.Посмотрим на пример правильной реализации:
// Получаем объект документа по ссылке
ДокументОбъект = СсылкаНаОбъект.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
// Очищаем табличную часть "Товары"
ДокументОбъект.Товары.Очистить();
// Записываем документ для фиксации изменений в базе данных
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Обратите внимание: при вызове метода Записать() мы передаем режим записи. Если документ не должен проводиться повторно (или вообще еще не был проведен), используем параметр РежимЗаписиДокумента.Запись.
Теперь разберем ситуацию, когда пользователь работает непосредственно с формой документа (например, в интерактивном режиме или при нажатии кнопки на форме), и нам требуется моментально очистить данные на экране.
В управляемом приложении данные формы представлены специальным типом ДанныеФормыКоллекция (для табличных частей) и ДанныеФормыСтруктура (для самого объекта формы). В этом контексте нам не требуется обращаться к базе данных и записывать документ. Мы работаем напрямую с экранными данными пользователя.
Выясним причину, почему для простой очистки на форме не нужно делать серверных вызовов. Вызов сервера — это всегда ресурсоемкая операция (сериализация данных, передача по сети, десериализация). Если перед нами стоит задача просто очистить ТЧ «здесь и сейчас», мы делаем это прямо на клиенте.
Рассмотрим пример клиентской процедуры:
&НаКлиенте
Процедура ОчиститьТабличнуюЧастьНаКлиенте(Команда)
// "Объект" - это стандартный реквизит управляемой формы типа ДанныеФормыСтруктура
// "Товары" - табличная часть (ДанныеФормыКоллекция)
Объект.Товары.Очистить();
// Данные на форме обновятся мгновенно и автоматически!
КонецПроцедуры
Этот код отработает мгновенно, интерфейс не «моргнет», а пользователь сразу увидит пустую таблицу, которую он сможет заполнить заново или просто закрыть форму без сохранения изменений, если передумает.
Часто в реальной практике очистка табличной части завязана на сложную серверную бизнес-логику. Например, перед очисткой нужно проверить остатки на складах, сделать сложный запрос к базе данных или перезаполнить ТЧ по какому-то алгоритму. В этом случае без сервера не обойтись.
Здесь разработчики часто совершают архитектурную ошибку: они передают объект формы на сервер в качестве параметра, очищают его там, но на клиенте таблица остается заполненной. Разберем, почему так происходит.
При передаче переменной типа ДанныеФормыСтруктура (наш Объект) в процедуру с директивой &НаСервереБезКонтекста или при передаче через параметры в обычную серверную процедуру происходит копирование данных. Изменения, внесенные на сервере в этот локальный параметр, не возвращаются на клиент автоматически.
Существует два правильных способа решения этой задачи.
Это самый надежный способ, когда вам требуется выполнить очистку ТЧ и сопутствующую обработку именно методами самого прикладного объекта (документа), задействовав логику его модуля. Мы используем встроенные функции платформы для конвертации данных формы в объект базы данных.
Рассмотрим по шагам этот код:
&НаКлиенте
Процедура ВыполнитьОчисткуИОбработку(Команда)
// Вызываем серверную контекстную процедуру
ОчиститьТЧНаСервере();
КонецПроцедуры
&НаСервере
Процедура ОчиститьТЧНаСервере()
// Шаг 1: Конвертируем данные формы в реальный прикладной объект "ДокументОбъект"
ДокОбъект = РеквизитФормыВЗначение("Объект");
// Шаг 2: Теперь у нас есть полноценный объект 1С. Очищаем его табличную часть
ДокОбъект.Товары.Очистить();
// Здесь также можно вызвать любые процедуры из модуля этого документа, например:
// ДокОбъект.ЗаполнитьЦены();
// Шаг 3: Возвращаем измененный объект обратно в реквизит управляемой формы
ЗначениеВРеквизитФормы(ДокОбъект, "Объект");
// Теперь клиентская форма сразу отобразит изменения пользователю
КонецПроцедуры
Если вы разрабатываете внешнюю обработку или логику, которая работает с формой извне (как в сообщении 7 и 14 из темы форума), и передаете структуру формы как параметр на сервер, вам необходимо явно вернуть измененные данные.
Для этого используется глобальный метод КопироватьДанныеФормы(Источник, Приемник). Посмотрим на пример кода:
&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
ПараметрыФормы = Новый Структура("Ключ", ОбъектыНазначенияМассив[0]);
Форма = ПолучитьФорму("Документ.СчетНаОплатуПокупателю.Форма.ФормаДокумента", ПараметрыФормы);
// Получаем структуру данных формы документа
ОбъектФормы = Форма.Объект;
// Создаем временную копию данных для передачи на сервер
ОчиститьТЧНаСервереВнешнейПроцедурой(ОбъектФормы);
// Копируем измененные на сервере данные обратно в контекст формы документа
КопироватьДанныеФормы(ОбъектФормы, Форма.Объект);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОчиститьТЧНаСервереВнешнейПроцедурой(ДокументДанныеФормы)
// На сервере очищаем ТЧ переданного параметра
ДокументДанныеФормы.Товары.Очистить();
КонецПроцедуры
Иногда под задачей «очистить табличную часть» понимается не полное удаление всех строк, а более специфические действия: обнуление конкретных колонок или выборочное удаление строк по условию. Рассмотрим, как решать эти задачи максимально эффективно и быстро.
Представим, что в табличной части «Товары» (содержащей сотни строк) вам нужно очистить только колонку «Количество», оставив саму номенклатуру нетронутой. Обычный перебор строк в цикле Для Каждого на сервере работает крайне медленно на больших объемах данных.
Для оптимизации этой операции мы можем применить продвинутый метод работы с коллекциями — ЗагрузитьКолонку(). Поскольку у ТЧ документов нет стандартного метода ЗаполнитьЗначения() (как у ТаблицыЗначений), мы используем трюк с инициализацией пустого массива:
&НаСервере
Процедура ОбнулитьКолонкуКоличество(ДокументОбъект)
КоличествоСтрок = ДокументОбъект.Товары.Количество();
// Создаем массив, размер которого в точности равен количеству строк в ТЧ.
// При создании он будет автоматически заполнен значениями Неопределено (или 0, если задать явно)
МассивПустыхЗначений = Новый Массив(КоличествоСтрок);
// Быстро записываем пустые значения во всю колонку ТЧ одной операцией
ДокументОбъект.Товары.ЗагрузитьКолонку(МассивПустыхЗначений, "Количество");
// Этот метод работает в десятки раз быстрее классического перебора строк в цикле!
КонецПроцедуры
Часто требуется удалить из табличной части только определенные строки (например, где количество равно нулю или не заполнена номенклатура). Начинающие разработчики часто пишут код, который приводит к пропуску строк при обходе.
Рассмотрим, почему обычный цикл Для Каждого работает неверно при удалении элементов:
// ОШИБОЧНЫЙ КОД (так делать нельзя!)
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Если СтрокаТЧ.Количество = 0 Тогда
Объект.Товары.Удалить(СтрокаТЧ); // Индексы коллекции сдвигаются, следующая строка проскакивает мимо цикла!
КонецЕсли;
КонецЦикла;
Чтобы избежать этой проблемы, используем один из двух правильных подходов.
Вариант 1. Обход коллекции с конца (по индексу). При удалении строк с конца сдвиг индексов никак не влияет на еще не обработанные элементы, находящиеся в начале таблицы.
Индекс = Объект.Товары.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТЧ = Объект.Товары[Индекс];
Если СтрокаТЧ.Количество = 0 Тогда
Объект.Товары.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Вариант 2. Сбор удаляемых строк в отдельный массив. Сначала мы находим все строки, подлежащие удалению, фиксируем их в массиве, а затем вторым циклом спокойно удаляем их из исходной таблицы.
СтрокиДляУдаления = Новый Массив;
// Шаг 1: Находим подходящие под условие строки
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Если СтрокаТЧ.Количество = 0 Тогда
СтрокиДляУдаления.Добавить(СтрокаТЧ);
КонецЕсли;
КонецЦикла;
// Шаг 2: Удаляем накопленные строки
Для Каждого Строка Из СтрокиДляУдаления Цикл
Объект.Товары.Удалить(Строка);
КонецЦикла;
Если в вашем документе содержатся тысячи строк (например, это крупная накладная поступления товаров или отчет о розничных продажах), вызов метода Очистить() непосредственно на клиенте для связанных данных формы может вызвать временное «зависание» интерфейса 1С.
Это происходит из-за того, что платформа тратит значительные ресурсы на перерисовку сложных элементов формы, пересчет итогов в подвалах таблиц и обновление связанных полей ввода.
В случае работы с экстремально большими объемами данных рекомендуется:
Мы подробно разобрали все варианты работы с очисткой табличных частей в 1С. Выбирайте наиболее подходящий сценарий в зависимости от того, работаете ли вы с базой данных напрямую или управляете интерфейсом пользователя на клиенте!