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