Чтение реквизитов и свойств в 1С 8.3: от точки до запроса

· 9 мин

Содержание

Значение свойства в 1С чаще всего получают через точку: Документ.Контрагент, Ссылка.Наименование. Если имя свойства известно только во время выполнения, пишут Объект["ИмяРеквизита"]. Для чтения одного-двух реквизитов по ссылке в типовых конфигурациях есть функции БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта() и ЗначенияРеквизитовОбъекта(), а для массовой выборки — запрос.

Проблемы начинаются, когда реквизит пустой, ссылка равна Неопределено, код выполняется на клиенте или одно и то же поле читается в цикле тысячи раз. Ниже — каждый способ с примерами, ограничениями и ошибками, которые он вызывает. Всё написано для платформы 8.3 и управляемых форм; встроенный язык общий для всех конфигураций.

Обращение через точку: самый короткий путь

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

// На сервере
Контрагент = ДокументОбъект.Контрагент;
ИНН        = ДокументОбъект.Контрагент.ИНН;
Дата       = ДокументСсылка.Дата;

Что происходит «под капотом» при обращении через ссылку:

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

На клиенте ссылку разыменовать нельзя. Код Объект.Контрагент.ИНН в клиентской процедуре формы вызовет ошибку: на клиенте ссылка — это просто идентификатор без данных. Читайте реквизиты в процедуре с директивой &НаСервере или через серверный общий модуль.

Квадратные скобки вместо точки

Тот же доступ можно записать через строку с именем:

ИмяРеквизита = "Контрагент";
Значение = ДокументОбъект[ИмяРеквизита];

Это удобно, когда имя реквизита приходит из настроек, параметра или списка. Скобки поддерживают объекты, ссылки, структуры, строки таблиц значений и табличных частей. Работает быстрее и безопаснее, чем Вычислить("ДокументОбъект." + ИмяРеквизита): строка не исполняется как код, значит, в неё нельзя подсунуть лишнее.

Проверка перед чтением: ЗначениеЗаполнено() и сравнение с Неопределено

Самая частая авария — цепочка точек через пустое значение. Если в документе не выбран договор, Документ.Договор.Организация вернёт пустую ссылку, а дальше всё зависит от типа:

Что лежит в реквизитеЧто будет при Реквизит.Поле
Пустая ссылка нужного типаПоле вернётся пустым, ошибки нет
Неопределено (составной тип, ничего не выбрано)Ошибка «Значение не является значением объектного типа»
Значение другого типа из составногоОшибка «Поле объекта не обнаружено»

Поэтому перед переходом по цепочке проверяйте значение:

Если ЗначениеЗаполнено(Документ.Договор) Тогда
    Организация = Документ.Договор.Организация;
Иначе
    Организация = Справочники.Организации.ПустаяСсылка();
КонецЕсли;

ЗначениеЗаполнено() возвращает Ложь для Неопределено, Null, пустой ссылки, пустой строки, нуля, пустой даты и Ложь для булева. Последний пункт ловит многих: для реквизита типа «Булево» функция не отличает «не заполнено» от «выключено».

Для составного типа одной проверки на заполненность мало — нужно ещё убедиться в типе:

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

Функции БСП для чтения реквизитов по ссылке

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

ФункцияЧто возвращаетКогда брать
ЗначениеРеквизитаОбъекта(Ссылка, "Имя")Значение одного реквизитаНужно одно поле
ЗначенияРеквизитовОбъекта(Ссылка, "Имя1, Имя2")Структуру с ключами-реквизитамиНужно несколько полей одного объекта
ЗначенияРеквизитовОбъектов(МассивСсылок, "Имя1, Имя2")Соответствие «ссылка → структура»Поля по многим объектам за один запрос

Пример:

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

Имена можно задавать через точку, например "Владелец.ИНН": функция сама построит соединение в запросе. Тогда ключом в структуре станет имя без точек, а при желании можно задать свой псевдоним — его описание есть в комментарии к функции в модуле.

💡 Не помните точную сигнатуру в своём релизе — откройте общий модуль ОбщегоНазначения в конфигураторе и найдите функцию поиском по модулю (Ctrl+F). Над каждой функцией БСП подробно расписаны параметры и возвращаемое значение.

Модуль ОбщегоНазначения серверный. С клиента эти функции напрямую не вызвать: сделайте серверную процедуру формы или свою функцию в серверном модуле с флажком «Вызов сервера».

Дополнительные реквизиты и сведения

В типовых конфигурациях «свойствами» часто называют дополнительные реквизиты, которые пользователь добавил сам. Через точку их не прочитать — в метаданных объекта их нет. Хранятся они в двух местах:

  • 📁 дополнительные реквизиты — в табличной части ДополнительныеРеквизиты самого объекта (колонки Свойство и Значение);
  • 📁 дополнительные сведения — в регистре сведений ДополнительныеСведения (измерения Объект и Свойство, ресурс Значение).

Для их чтения в БСП есть модуль УправлениеСвойствами, функция ЗначенияСвойств() возвращает таблицу значений по списку объектов. Либо читайте напрямую запросом:

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

Свойство здесь — элемент плана видов характеристик ДополнительныеРеквизитыИСведения. Найти его можно по полю Имя или по наименованию.

Значение свойства через запрос

Запрос выгоднее всего, когда полей много или объектов много. Разыменование в запросе тоже пишется через точку, но превращается в соединение таблиц на стороне СУБД.

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

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

Нюансы, о которых стоит помнить:

  • 🔸 если реквизит пустой, Контрагент.ИНН в запросе вернёт NULL, а не ошибку — оберните в ЕСТЬNULL(..., ""), если результат пойдёт в расчёты или строки;
  • 🔸 для составного типа длинные цепочки точек порождают соединения со всеми таблицами из состава типа — уточняйте тип через ВЫРАЗИТЬ(Поле КАК Справочник.Контрагенты);
  • 🔸 ПРЕДСТАВЛЕНИЕ() берите только для вывода: сравнивать по нему нельзя.

Свойства коллекций: Структура, Соответствие, Массив, таблица значений

У каждой коллекции свой способ безопасного чтения. Путать их — источник половины «загадочных» ошибок в обработках.

КоллекцияЧтениеБезопасная проверкаЕсли ключа или индекса нет
СтруктураСтр.Ключ, Стр["Ключ"]Стр.Свойство("Ключ", Значение)Ошибка «Поле объекта не обнаружено»
ФиксированнаяСтруктуратак жетак жетак же
СоответствиеСоотв[Ключ], Соотв.Получить(Ключ)сравнить результат с НеопределеноВернёт Неопределено
МассивМассив[Индекс], индекс с нуляИндекс < Массив.Количество()Ошибка «Индекс находится за границами диапазона»
ТаблицаЗначенийСтрока.Колонка, Строка["Колонка"]Таблица.Колонки.Найти("Колонка") <> НеопределеноОшибка «Поле объекта не обнаружено»

Метод Свойство() у структуры одновременно проверяет наличие ключа и кладёт значение во второй параметр:

Параметры = Новый Структура("Склад, Дата", Склад, ТекущаяДата());

Значение = Неопределено;
Если Параметры.Свойство("Организация", Значение) И ЗначениеЗаполнено(Значение) Тогда
    Организация = Значение;
КонецЕсли;

У соответствия ключом может быть ссылка, число или строка. Если в соответствии реально хранится Неопределено как значение, отличить его от отсутствующего ключа через Получить() нельзя — в таком случае храните значения в структуре-обёртке или проверяйте перебором.

Последний элемент массива берут по индексу Массив.Количество() - 1. Для пустого массива это -1, поэтому проверку на количество делайте до обращения.

Имя свойства неизвестно заранее: метаданные и ЗаполнитьЗначенияСвойств

В 1С нет рефлексии в привычном для Java или C# смысле, но её роль играют три инструмента.

1. Перебор реквизитов через метаданные

МетаОбъект = Ссылка.Метаданные();
Для Каждого Реквизит Из МетаОбъект.Реквизиты Цикл
    Сообщить(Реквизит.Имя + " = " + Строка(Ссылка[Реквизит.Имя]));
КонецЦикла;

Реквизиты содержит только реквизиты, добавленные в конфигурации. Код, наименование, дата, номер, пометка удаления лежат в коллекции СтандартныеРеквизиты, табличные части — в ТабличныеЧасти.

2. Проверка, есть ли такой реквизит у объекта

Через метаданные:

ЕстьРеквизит = (Ссылка.Метаданные().Реквизиты.Найти("Склад") <> Неопределено);

Приём практиков для любых объектов, включая строки табличных частей и данные формы, — проверка через структуру с маркером:

Функция ЕстьСвойство(Источник, ИмяСвойства) Экспорт
    Маркер = Новый УникальныйИдентификатор;
    Проверка = Новый Структура(ИмяСвойства, Маркер);
    ЗаполнитьЗначенияСвойств(Проверка, Источник);
    Возврат Проверка[ИмяСвойства] <> Маркер;
КонецФункции

ЗаполнитьЗначенияСвойств() копирует только совпадающие по имени свойства и молча пропускает отсутствующие. Если маркер остался на месте — свойства у источника нет.

3. Копирование группы свойств одним вызовом

ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник, "Номенклатура, Количество, Цена");

Третий параметр — список свойств через запятую, четвёртый — исключения. Это быстрее и короче, чем десяток строк присваивания, и не падает, если в приёмнике нет какого-то поля.

Вычислить() и Выполнить() ради чтения свойства не нужны. Скобки Объект[Имя] решают ту же задачу, а выполнение строки как кода в безопасном режиме и в облачных базах запрещено или ограничено.

Ошибки при чтении свойств и как их снять

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

Как читать свойства быстро: практика для обработок и отчётов

Медленный код почти всегда выглядит невинно — точка внутри цикла:

// Плохо: на каждой итерации читается объект контрагента
Для Каждого Строка Из ТаблицаДокументов Цикл
    Строка.ИНН = Строка.Документ.Контрагент.ИНН;
КонецЦикла;

На тысяче документов это тысячи чтений объектов из базы. Правильнее одним вызовом:

Документы = ТаблицаДокументов.ВыгрузитьКолонку("Документ");
Данные = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(Документы, "Контрагент.ИНН");
Для Каждого Строка Из ТаблицаДокументов Цикл
    Строка.ИНН = Данные[Строка.Документ].КонтрагентИНН;
КонецЦикла;

Ключ в структуре здесь — имя без точки. Если сомневаетесь в формате ключа в вашем релизе БСП, задайте псевдоним явно или выберите данные обычным запросом с В (&Документы) — результат будет тот же.

Короткий свод правил:

  1. Одно-два поля по ссылке — ЗначениеРеквизитаОбъекта() или ЗначенияРеквизитовОбъекта().
  2. Поля по многим объектам — один запрос или ЗначенияРеквизитовОбъектов(), но не точка в цикле.
  3. Объект уже получен через ПолучитьОбъект() и всё равно будет записываться — читайте прямо из него, лишний запрос не нужен.
  4. На клиенте — только реквизиты формы и данные, переданные с сервера. Ссылки не разыменовываются.
  5. Имя поля из переменной — Объект[Имя], а не Вычислить().
  6. Реквизиты, которые нужны форме постоянно, кладите в реквизиты формы при создании на сервере, чтобы не гонять серверные вызовы.

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

Чем Ссылка.Реквизит отличается от ЗначениеРеквизитаОбъекта()?
Точка через ссылку читает объект целиком, функция БСП делает запрос только по нужному полю. Для одного поля большого документа функция заметно легче.
Можно ли прочитать реквизит, который запрещён пользователю правами?
Обычный запрос и разыменование учитывают права. Функции БСП по умолчанию тоже, но у них есть параметр выборки без учёта прав — пользуйтесь им только в служебном коде, где это оправдано.
Почему ЗначениеЗаполнено() возвращает Ложь для заполненного флажка?
Для типа Булево функция считает Ложь пустым значением. Проверяйте флажок напрямую: Если Объект.Флаг Тогда.
Как получить свойство элемента формы, а не данных?
Через коллекцию Элементы: Элементы.Склад.Видимость, Элементы.Склад.ТолькоПросмотр. Значение поля лежит в реквизите формы, к которому привязан элемент, например Объект.Склад.
Как узнать тип реквизита по имени?
Через метаданные: Ссылка.Метаданные().Реквизиты.Найти(Имя).Тип вернёт описание типов, у него есть метод СодержитТип().