Как прочитать реквизит объекта в 1С: точка, запрос и БСП

· 9 мин

Содержание

Чаще всего реквизит читают через точку: Ссылка.ИмяРеквизита. Если у вас в переменной Контрагент лежит ссылка на элемент справочника, то его ИНН вы получите выражением Контрагент.ИНН, а полное наименование — Контрагент.НаименованиеПолное. Это основной способ, и для одиночного чтения он правильный.

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

Точечная нотация: обращение к реквизиту через точку

Точка после ссылки или объекта возвращает значение одноимённого реквизита. Синтаксис одинаков для справочников, документов, регистров и перечислений:

Значение = Номенклатура.Артикул;
Валюта   = Договор.ВалютаДокумента;
Поставщик = Поступление.Контрагент;

Точки можно выстраивать в цепочку — платформа пройдёт по ссылкам до конечного реквизита:

ИНН = Заказ.Контрагент.ИНН;
Страна = Товар.Производитель.СтранаРегистрации.Наименование;

Каждое звено цепочки Заказ.Контрагент.ИНН — это обращение к базе: сначала читается Контрагент, потом из него ИНН. Одно выражение — несколько скрытых чтений. В отчёте на тысячу строк такая цепочка внутри цикла превращается в тысячи запросов.

Точка работает и для чтения свойств системных объектов: ТекущаяДата(), Строка.НомерСтроки, Выборка.Количество. Но у реквизитов объекта есть особенность — платформа подгружает их «лениво», по первому обращению, а не целиком при получении ссылки.

Через ссылку или через объект: в чём разница

У ссылки (СправочникСсылка, ДокументСсылка) и у объекта (СправочникОбъект, ДокументОбъект) набор реквизитов один, но ведут они себя по-разному.

Чтение через ссылку — для случаев, когда нужно только прочитать:

Код = Контрагент.Код; // Контрагент здесь — ссылка

Платформа сама обращается к базе за значением реквизита. Это дёшево для одиночного чтения и не блокирует объект.

Получение объекта нужно, когда вы собираетесь менять данные или читать сразу много реквизитов:

Объект = Контрагент.ПолучитьОбъект();
Объект.Комментарий = "Проверен";
Объект.Записать();

ПолучитьОбъект() целиком считывает элемент в память — все реквизиты уже загружены, повторных обращений к базе на точку не будет.

Правило простое: нужно только прочитать одно-два значения — работайте со ссылкой. Нужно читать десятки реквизитов одного элемента или записывать — берите объект через ПолучитьОбъект().

Штатные функции БСП для чтения реквизитов

В типовых конфигурациях на библиотеке стандартных подсистем (БП 3.0, ЗУП 3.1, УТ 11, ERP) есть готовые функции общего модуля ОбщегоНазначения. Они читают реквизиты одним запросом и обрабатывают пустые ссылки:

// один реквизит
Валюта = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Договор, "ВалютаДокумента");

// несколько реквизитов за одно обращение к базе
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(
    Контрагент, "ИНН, КПП, НаименованиеПолное");
ИНН = Реквизиты.ИНН;
КПП = Реквизиты.КПП;

ЗначенияРеквизитовОбъекта возвращает структуру, где ключи — имена реквизитов. Одно обращение к базе вместо трёх точек — это заметно быстрее в циклах и обработках. Если конфигурация на БСП, предпочитайте эти функции точечной цепочке в коде, который выполняется многократно.

Реквизиты табличных частей и строк документа

Табличная часть документа — это коллекция строк. У каждой строки свои реквизиты (колонки), читаются они той же точкой.

Перебор всех строк:

Для Каждого Строка Из Поступление.Товары Цикл
    Номенклатура = Строка.Номенклатура;
    Количество   = Строка.Количество;
    Сумма        = Строка.Сумма;
КонецЦикла;

Обращение к конкретной строке по индексу (нумерация с нуля):

ПерваяСтрока = Поступление.Товары[0];
Первый       = ПерваяСтрока.Номенклатура;

Полезные методы табличной части для выборки значений без ручного цикла:

МетодЧто возвращает
Товары.Количество()число строк
Товары.Итог("Сумма")сумму по колонке
Товары.Найти(Значение, "Номенклатура")первую подходящую строку или Неопределено
Товары.НайтиСтроки(Отбор)массив строк по структуре отбора
Товары.ВыгрузитьКолонку("Сумма")массив значений одной колонки
Товары.Выгрузить()всю табличную часть как таблицу значений

Пример выборки строк по условию:

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

Найти возвращает Неопределено, если ничего не нашлось. Перед обращением к реквизиту результата проверяйте: Если Строка <> Неопределено Тогда .... Иначе получите ошибку обращения к реквизиту неопределённого значения.

Чтение реквизитов запросом: когда это быстрее точки

Когда нужно получить реквизиты у множества объектов, запрос почти всегда выигрывает у цикла с точками: он забирает все данные за одно обращение к базе.

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ
    |    Контрагенты.Ссылка КАК Ссылка,
    |    Контрагенты.ИНН КАК ИНН,
    |    Контрагенты.НаименованиеПолное КАК Наименование
    |ИЗ
    |    Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |    Контрагенты.ЮридическоеФизическоеЛицо = &ВидЛица";
Запрос.УстановитьПараметр("ВидЛица",
    Перечисления.ЮридическоеФизическоеЛицо.ЮридическоеЛицо);

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.ИНН + " — " + Выборка.Наименование);
КонецЦикла;

В запросе точка тоже работает — ей удобно подниматься по ссылкам, но уже на стороне СУБД, без скрытых обращений из кода:

ВЫБРАТЬ
    Заказы.Ссылка,
    Заказы.Контрагент.ИНН КАК ИНН,
    Заказы.Контрагент.ОсновнойМенеджер.Наименование КАК Менеджер
ИЗ
    Документ.ЗаказПокупателя КАК Заказы

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

ТаблицаКонтрагентов = Запрос.Выполнить().Выгрузить();

Когда из запроса нужно забрать единственное значение, выгружать таблицу необязательно:

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    ИНН = Выборка.ИНН;
КонецЕсли;

Ориентир по выбору способа: одно значение у одного объекта — точка через ссылку; несколько реквизитов у одного объекта — ЗначенияРеквизитовОбъекта или объект; реквизиты у многих объектов — запрос.

Реквизиты предопределённых и найденных элементов

Нередко реквизит нужно взять не из переменной, а у конкретного элемента справочника. Ссылку на него получают через менеджер:

// по коду или наименованию
Валюта = Справочники.Валюты.НайтиПоКоду("840");
Если ЗначениеЗаполнено(Валюта) Тогда
    Курс = РегистрыСведений.КурсыВалют... // дальше по данным
КонецЕсли;

// предопределённый элемент — обращение по имени из конфигуратора
СтавкаНДС = Перечисления.СтавкиНДС.НДС20;

НайтиПоКоду и НайтиПоНаименованию возвращают пустую ссылку, если ничего не нашли, поэтому результат тоже проверяют ЗначениеЗаполнено(). У предопределённых элементов имена фиксированы в конфигурации — их видно в дереве объектов, на вкладке Прочее → Предопределённые.

Данные из таблицы значений и временного хранилища

Таблица значений — универсальная коллекция в памяти. Её строки читаются как строки табличной части:

Для Каждого Строка Из ТаблицаЗначений Цикл
    Значение = Строка.ИмяКолонки;
КонецЦикла;

// по индексу
ПервоеЗначение = ТаблицаЗначений[0].ИмяКолонки;

// поиск, итоги, выгрузка колонки
НайденнаяСтрока = ТаблицаЗначений.Найти(Искомое, "Код");
Итог = ТаблицаЗначений.Итог("Сумма");
МассивКодов = ТаблицаЗначений.ВыгрузитьКолонку("Код");

Перебрать имена колонок, когда структура таблицы заранее неизвестна:

Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл
    Сообщить(Колонка.Имя);
КонецЦикла;

Временное хранилище — не источник реквизитов, а способ передать данные между клиентом и сервером (а также между серверными вызовами). В хранилище кладут готовое значение — таблицу, структуру, двоичные данные — и получают его по адресу:

// сервер: положили
Адрес = ПоместитьВоВременноеХранилище(ТаблицаЗначений, УникальныйИдентификатор);

// позже: достали
Данные = ПолучитьИзВременногоХранилища(Адрес);
Значение = Данные[0].ИмяКолонки;

Прочитать реквизит прямо «из хранилища» нельзя: сначала получаете объект из него, потом работаете с его реквизитами обычной точкой. Если передаёте идентификатор формы, значение живёт до закрытия формы; с Новый УникальныйИдентификатор — до конца серверного вызова.

Чтение реквизита элемента на управляемой форме

На форме «реквизит объекта» и «реквизит формы» — разные вещи. Главный объект формы доступен через реквизит Объект, его реквизиты читаются точкой:

&НаКлиенте
Процедура ПриИзмененииКонтрагента(Элемент)
    ТекущийКонтрагент = Объект.Контрагент;
КонецПроцедуры

Значение, которое пользователь видит в поле ввода, — это именно реквизит формы, а не элемент управления. Элемент (Элементы.ИмяПоля) отвечает за отображение: доступность, видимость, заголовок. Данные лежат в связанном с ним реквизите.

Строку табличной части, на которой стоит курсор, берут через текущие данные таблицы:

&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    Если ТекущаяСтрока <> Неопределено Тогда
        Номенклатура = ТекущаяСтрока.Номенклатура;
    КонецЕсли;
КонецПроцедуры

Чтобы на сервере поработать с реквизитом формы как с полноценным объектом (например, выгрузить таблицу формы в таблицу значений), применяют преобразование данных формы:

&НаСервере
Процедура ЗаполнитьНаСервере()
    ТаблицаЗначений = ДанныеФормыВЗначение(Товары, Тип("ТаблицаЗначений"));
    Итог = ТаблицаЗначений.Итог("Сумма");
КонецПроцедуры

ТекущиеДанные возвращают Неопределено, когда в таблице нет выделенной строки (список пуст или курсор снят). Проверяйте результат перед обращением к его реквизитам.

Проверка типа и приведение значения реквизита

Реквизит может быть составного типа (например, «Контрагент или Физлицо») или оказаться пустым. Прежде чем идти по нему дальше, тип стоит проверить.

Определение типа значения:

Если ТипЗнч(Значение) = Тип("СправочникСсылка.Контрагенты") Тогда
    ИНН = Значение.ИНН;
КонецЕсли;

Проверка на заполненность и на пустую ссылку:

Если ЗначениеЗаполнено(Контрагент) Тогда
    // ссылка не пустая и не Неопределено
КонецЕсли;

Если НЕ Контрагент.Пустая() Тогда
    Код = Контрагент.Код;
КонецЕсли;

ЗначениеЗаполнено() удобна тем, что одинаково отрабатывает и пустую ссылку, и Неопределено, и пустую строку, и нулевую дату.

Приведение к нужному типу делают явными функциями, а не в надежде на автоматику:

ЧислоСуммы = Число(СтрокаСуммы);
СтрокаКода = Строка(Контрагент.Код);
ДатаДок    = Дата(ЗначениеДаты);

Автоматического приведения числа к строке при конкатенации в 1С нет — выражение "Итог: " + 1500 вызовет ошибку типов. Приводите явно: "Итог: " + Формат(1500, "ЧГ=0") или через СтрШаблон("Итог: %1", 1500).

Частые ошибки при чтении реквизитов и отладка

Разберём, что именно ломается и как это увидеть.

Диагностика: выберите симптом

Инструменты отладки, которыми это ловят:

  • Сообщить(Значение) или Сообщить(ТипЗнч(Значение)) — быстро посмотреть, что в переменной и какого она типа.
  • Точка останова (F9) и пошаговое выполнение (F11) в конфигураторе — остановить код перед проблемной строкой.
  • Табло и окно «Вычислить выражение» (Shift+F9) в режиме отладки — раскрыть ссылку и увидеть все её реквизиты и значения прямо во время остановки.
  • ПредставлениеОшибки(ИнформацияОбОшибке()) в блоке Попытка … Исключение — получить понятный текст сбоя вместо аварийного завершения.
Попытка
    Значение = Объект.СомнительныйРеквизит;
Исключение
    Сообщить(ПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Если не уверены, как точно называется реквизит, не угадывайте в коде — откройте объект в дереве конфигурации (Справочники → ваш справочник → ветка Реквизиты) и возьмите имя оттуда. Регистр в именах реквизитов не важен, но опечатка в букве — это ошибка «Поле объекта не обнаружено».

Частые вопросы

Чем отличается чтение реквизита через ссылку от чтения через объект?
Ссылка читает значение из базы по требованию и не загружает элемент целиком — это дёшево для одного-двух реквизитов. ПолучитьОбъект() считывает элемент в память полностью, поэтому выгоден, когда нужно много реквизитов сразу или запись.
Почему обращение к реквизиту в цикле тормозит отчёт?
Каждая точка через ссылку — отдельное обращение к базе. На тысячах строк это тысячи мелких запросов. Соберите нужные реквизиты одним запросом или функцией ЗначенияРеквизитовОбъекта, а в цикле уже работайте с готовой выборкой.
Что вернёт точка, если ссылка пустая?
Не ошибку, а пустое значение соответствующего типа (пустую строку, ноль, пустую дату). Поэтому пустую ссылку безопаснее отлавливать заранее через ЗначениеЗаполнено() или Пустая(), а не по факту пустого результата.
Как прочитать реквизит, если он составного типа?
Сначала определите фактический тип через ТипЗнч(Значение) = Тип("..."), затем обращайтесь к реквизитам этого типа. У разных типов составного реквизита наборы реквизитов отличаются, поэтому без проверки можно получить «Поле объекта не обнаружено».
Можно ли получить реквизит напрямую из временного хранилища?
Нет. Временное хранилище хранит значение целиком. Сначала извлеките его функцией ПолучитьИзВременногоХранилища, а затем читайте реквизиты полученного объекта или строки таблицы обычной точкой.