Чтение реквизитов, табличных частей и регистров во встроенном языке 1С 8.3: что выбрать в каждой ситуации

· 9 мин

Содержание

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

Частое заблуждение: «обращение через точку бесплатное, ведь объект уже под рукой». На деле Ссылка.Реквизит читает из базы весь объект целиком, со всеми табличными частями. В цикле по тысяче документов это тысяча тяжёлых обращений к СУБД вместо одного запроса.

Ссылка и объект: базовые способы для справочников и документов

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

Обращение через точку от ссылки

Самый короткий способ — прочитать реквизит прямо от ссылки:

&НаСервере
Процедура ПоказатьИНН(Контрагент)
	ИНН = Контрагент.ИНН;
	Сообщить("ИНН: " + ИНН);
КонецПроцедуры

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

Метод ПолучитьОбъект()

Если реквизиты нужно не только прочитать, но и изменить, получите объект:

ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
	// объект удалён из базы, ссылка битая
	Возврат;
КонецЕсли;

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

ПолучитьОбъект() возвращает Неопределено, если записи с такой ссылкой в базе нет. Эту проверку пропускают чаще всего — и получают ошибку «Значение не является значением объектного типа » на следующей строке.

Чтение по коду, наименованию или номеру

Когда ссылки нет, её находят через менеджер:

Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Бумага А4", Истина);
Валюта = Справочники.Валюты.НайтиПоКоду("643");
Документ = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("0000-000123", ТекущаяДата());

Если ничего не найдено, метод вернёт пустую ссылку. Проверяйте её через .Пустая() или сравнением с Справочники.Номенклатура.ПустаяСсылка(). Второй параметр Истина в НайтиПоНаименованию включает точное совпадение — без него найдётся первый элемент, наименование которого начинается с переданной строки.

Поиск по наименованию ненадёжен: в справочнике легко оказываются два элемента с одинаковым именем. Для интеграций ищите по коду, ИНН или уникальному идентификатору — ПолучитьСсылку(Новый УникальныйИдентификатор(Строка)).

Сравнение базовых способов

СпособКогда применятьЧто читается из базыМожно изменить
Ссылка.Реквизитразово, на серверевесь объектнет
ПолучитьОбъект()нужно изменить и записатьвесь объектда
ЗначенияРеквизитовОбъекта()несколько полей одного объектатолько указанные полянет
Запросмного объектов, отборы, связитолько выбранные полянет

Чтение отдельных реквизитов без загрузки объекта

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

Одно значение

ИНН = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Контрагент, "ИНН");

Несколько значений структурой

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

Функция возвращает Структура, ключи совпадают с именами реквизитов. Через точку можно взять реквизит связанного объекта, задав псевдоним: "Организация.ИНН КАК ИННОрганизации" — в этом случае ключ структуры будет ИННОрганизации.

Один реквизит у списка ссылок

МассивСсылок = Новый Массив;
МассивСсылок.Добавить(Контрагент1);
МассивСсылок.Добавить(Контрагент2);

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

Результат — Соответствие: ключ — ссылка, значение — реквизит. Для нескольких полей у списка есть парная функция ЗначенияРеквизитовОбъектов().

По умолчанию (параметр ВыбратьРазрешенные = Ложь) функции читают данные в привилегированном режиме, без учёта прав пользователя. Если значение потом показывается в интерфейсе, передайте Истина — тогда функция вернёт только разрешённые данные.

Табличная часть объекта

Строки табличной части от ссылки получают так:

Товары = СсылкаНаДокумент.Товары.Выгрузить();
Для Каждого СтрокаТЧ Из Товары Цикл
	Сообщить(Строка(СтрокаТЧ.Номенклатура) + " — " + Формат(СтрокаТЧ.Количество, "ЧГ=0"));
КонецЦикла;

Выгрузить() превращает табличную часть в ТаблицаЗначений. Это снова чтение всего объекта. Если документов много, берите строки запросом из таблицы Документ.РеализацияТоваровУслуг.Товары.

Запросы: как вытащить значения сразу у многих объектов

Как только объектов больше пяти-десяти, переходите на запрос. Один запрос заменяет сотни обращений через точку.

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

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

Что здесь стоит запомнить:

  • 🔹 выбирайте только нужные поля, а не ВЫБРАТЬ *;
  • 🔹 разыменование через точку в запросе (ГоловнойКонтрагент.Наименование) порождает соединение — для составных типов, ссылающихся на много таблиц, оно тяжёлое, используйте ВЫРАЗИТЬ;
  • 🔹 для одного значения хватит Выбрать() и одного Следующий(), для обработки в памяти — Выгрузить() в таблицу значений;
  • 🔹 параметр-массив в условии В (&Список) удобнее, чем склейка ссылок в текст.

Одно значение из запроса

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

Выборка = Запрос.Выполнить().Выбрать();
Цена = ?(Выборка.Следующий(), Выборка.Цена, 0);

Конструкция с ?() защищает от пустого результата: без неё обращение к Выборка.Цена до Следующий() вызовет ошибку. Имена регистра и его измерений берите из своей конфигурации — в разных решениях они называются по-разному.

Значения из регистров сведений и накопления

Регистры — не объекты в привычном смысле. Значение здесь получают на дату и по отбору измерений.

Регистр сведений: последнее значение на дату

Для периодического регистра у менеджера есть метод ПолучитьПоследнее():

Отбор = Новый Структура("Валюта", ВалютаUSD);
Курс = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(ТекущаяДатаСеанса(), Отбор);
Сообщить(Курс.Курс);

Метод возвращает Структура с ресурсами. Если записей нет, ресурсы будут пустыми (0 для числа), без ошибки. Для первого значения после даты есть парный ПолучитьПервое(). Для непериодического регистра — Получить(Отбор).

Регистр накопления: остатки и обороты

У регистра накопления значения получают через виртуальные таблицы Остатки, Обороты, ОстаткиИОбороты — запросом:

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

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

Движения конкретного документа

Записи регистра, которые сделал документ, читают через набор записей:

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

Значения в управляемой форме: клиент и сервер

В управляемом приложении на клиенте нет доступа к базе. Строка Контрагент.ИНН в процедуре с директивой &НаКлиенте выдаст ошибку. Варианты решения:

  1. Серверная функция без контекста. Самый лёгкий вызов: на сервер не передаются данные формы.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
	Объект.ИННКонтрагента = ПолучитьИНН(Объект.Контрагент);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьИНН(Контрагент)
	Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Контрагент, "ИНН");
КонецФункции
  1. Данные формы. Реквизиты редактируемого объекта уже лежат в Объект: Объект.Дата, Объект.Товары[0].Количество читаются на клиенте без вызова сервера.

  2. Объект прикладного типа на сервере. Если нужны методы модуля объекта, конвертируйте данные формы: ДокументОбъект = РеквизитФормыВЗначение("Объект"), а после изменений — ЗначениеВРеквизитФормы(ДокументОбъект, "Объект").

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

Кэширование и асинхронное получение значений

Модули с повторным использованием

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

// Общий модуль ЗначенияПовтИсп, повторное использование — на время сеанса
Функция ОсновнаяВалюта() Экспорт
	Возврат Константы.ВалютаРегламентированногоУчета.Получить();
КонецФункции

Правила, о которых забывают:

  • 🔸 модуль кэширует результат по значениям параметров — параметры должны быть простых типов или ссылками;
  • 🔸 нельзя возвращать изменяемые коллекции (Массив, Структура) и править их у себя — вы испортите кэш; оборачивайте в ФиксированнаяСтруктура или ФиксированныйМассив;
  • 🔸 кэш «на время сеанса» не узнает, что пользователь сменил константу; для часто меняющихся данных берите «на время вызова».

Имя константы в примере взято для иллюстрации — проверьте его в дереве своей конфигурации.

Асинхронность и длительные операции

На клиенте значения, которые требуют ответа пользователя или долгой работы, получают асинхронно. С платформы 8.3.18 есть ключевые слова Асинх и Ждать:

&НаКлиенте
Асинх Процедура ВыбратьСумму(Команда)
	Сумма = Ждать ВвестиЧислоАсинх(0, "Введите сумму", 15, 2);
	Если Сумма <> Неопределено Тогда
		Объект.СуммаДокумента = Сумма;
	КонецЕсли;
КонецПроцедуры

На платформах ниже 8.3.18 то же делают через ОписаниеОповещения и процедуру-обработчик.

Для тяжёлого серверного расчёта (отчёт, массовая выборка остатков) в БСП есть общий модуль ДлительныеОперации: расчёт уходит в фоновое задание, форма не зависает, результат забирается из временного хранилища по адресу. Платформенная основа — ФоновыеЗадания.Выполнить() и ПоместитьВоВременноеХранилище() / ПолучитьИзВременногоХранилища().

Чем отличается получение значений в 8.3 и 8.2

Что8.2 (обычное приложение, толстый клиент)8.3 (управляемое приложение, «Такси»)
Ссылка.Реквизит на клиентеработаетошибка, нужен вызов сервера
Асинхронные диалогимодальные Вопрос(), ВвестиЧисло()ОписаниеОповещения, с 8.3.18 — Асинх/Ждать
Расширения конфигурациинетесть с 8.3.6
Функции БСП ЗначенияРеквизитовОбъектав старых версиях библиотеки, с другим набором параметровв актуальной БСП 3.x

Главная ловушка при переносе кода с 8.2 — привычка читать реквизиты от ссылок прямо в обработчиках формы. В обычном приложении это работало и даже было нормой. В управляемом каждый такой фрагмент придётся вынести в серверную функцию.

Второе отличие — режим модальности. В конфигурациях 8.3 со свойством «Режим использования модальности» = «Не использовать» модальные функции ввода значений вызывают ошибку, их заменяют асинхронными аналогами.

Ошибки при чтении значений и как их не допустить

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

Ещё две ловушки, которые не дают явной ошибки:

  • 🔻 Битые ссылки. Если объект удалили без контроля ссылочной целостности, Ссылка.Наименование вернёт строку вида <Объект не найден> (12:a1b2...). Отличить такую ссылку можно по ПолучитьОбъект() = Неопределено.
  • 🔻 Значение «на сейчас» вместо «на дату документа». Курс, цену, учётную политику берите на дату документа (Объект.Дата или МоментВремени()), а не на ТекущаяДатаСеанса(). Иначе при перепроведении задним числом получите другие суммы.

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

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