Чтение реквизитов, табличных частей и регистров во встроенном языке 1С 8.3: что выбрать в каждой ситуации
Содержание
Получить значение объекта в 1С 8.3 можно пятью путями: через точку от ссылки (Ссылка.Наименование), через ПолучитьОбъект(), функциями БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта() и ЗначенияРеквизитовОбъекта(), запросом и методами менеджера регистра. Для одного-двух реквизитов на сервере в типовой конфигурации лучший выбор — функция БСП. Для списка объектов — только запрос.
Частое заблуждение: «обращение через точку бесплатное, ведь объект уже под рукой». На деле Ссылка.Реквизит читает из базы весь объект целиком, со всеми табличными частями. В цикле по тысяче документов это тысяча тяжёлых обращений к СУБД вместо одного запроса.
Ссылка и объект: базовые способы для справочников и документов
Сначала разберитесь, что у вас в руках. В переменной может лежать ссылка (СправочникСсылка.Контрагенты) или объект (СправочникОбъект.Контрагенты). Ссылка — это указатель на запись. Объект — загруженные в память данные, которые можно менять и записывать.
Обращение через точку от ссылки
Самый короткий способ — прочитать реквизит прямо от ссылки:
&НаСервере
Процедура ПоказатьИНН(Контрагент)
ИНН = Контрагент.ИНН;
Сообщить("ИНН: " + ИНН);
КонецПроцедуры
Работает только на сервере (и в толстом клиенте обычного приложения). Платформа при первом обращении считывает объект в кэш объектов и отдаёт значение. Удобно для разового чтения, плохо для циклов и для объектов с большими табличными частями.
Метод ПолучитьОбъект()
Если реквизиты нужно не только прочитать, но и изменить, получите объект:
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
// объект удалён из базы, ссылка битая
Возврат;
КонецЕсли;
ДокументОбъект.Комментарий = "Проверено";
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
ПолучитьОбъект() возвращает Неопределено, если записи с такой ссылкой в базе нет. Эту проверку пропускают чаще всего — и получают ошибку «Значение не является значением объектного типа
» на следующей строке.
Чтение по коду, наименованию или номеру
Когда ссылки нет, её находят через менеджер:
Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Бумага А4", Истина);
Валюта = Справочники.Валюты.НайтиПоКоду("643");
Документ = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("0000-000123", ТекущаяДата());
Если ничего не найдено, метод вернёт пустую ссылку. Проверяйте её через .Пустая() или сравнением с Справочники.Номенклатура.ПустаяСсылка(). Второй параметр Истина в НайтиПоНаименованию включает точное совпадение — без него найдётся первый элемент, наименование которого начинается с переданной строки.
Поиск по наименованию ненадёжен: в справочнике легко оказываются два элемента с одинаковым именем. Для интеграций ищите по коду, ИНН или уникальному идентификатору — ПолучитьСсылку(Новый УникальныйИдентификатор(Строка)).
Сравнение базовых способов
| Способ | Когда применять | Что читается из базы | Можно изменить |
|---|---|---|---|
Ссылка.Реквизит | разово, на сервере | весь объект | нет |
ПолучитьОбъект() | нужно изменить и записать | весь объект | да |
ЗначенияРеквизитовОбъекта() | несколько полей одного объекта | только указанные поля | нет |
| Запрос | много объектов, отборы, связи | только выбранные поля | нет |
Чтение отдельных реквизитов без загрузки объекта
В конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11, ERP и большинство самописных на её базе) есть общий модуль ОбщегоНазначения с функциями точечного чтения. Внутри они строят запрос только по нужным полям.
Одно значение
ИНН = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Контрагент, "ИНН");
Несколько значений структурой
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Контрагент, "Наименование, ИНН, КПП");
Сообщить(Реквизиты.Наименование + ", ИНН " + Реквизиты.ИНН);
Функция возвращает Структура, ключи совпадают с именами реквизитов. Через точку можно взять реквизит связанного объекта, задав псевдоним: "Организация.ИНН КАК ИННОрганизации" — в этом случае ключ структуры будет ИННОрганизации.
Один реквизит у списка ссылок
МассивСсылок = Новый Массив;
МассивСсылок.Добавить(Контрагент1);
МассивСсылок.Добавить(Контрагент2);
СоответствиеИНН = ОбщегоНазначения.ЗначениеРеквизитаОбъектов(МассивСсылок, "ИНН");
Для Каждого Элемент Из СоответствиеИНН Цикл
Сообщить(Строка(Элемент.Ключ) + ": " + Элемент.Значение);
КонецЦикла;
Результат — Соответствие: ключ — ссылка, значение — реквизит. Для нескольких полей у списка есть парная функция ЗначенияРеквизитовОбъектов().
По умолчанию (параметр ВыбратьРазрешенные = Ложь) функции читают данные в привилегированном режиме, без учёта прав пользователя. Если значение потом показывается в интерфейсе, передайте Истина — тогда функция вернёт только разрешённые данные.
Табличная часть объекта
Строки табличной части от ссылки получают так:
Товары = СсылкаНаДокумент.Товары.Выгрузить();
Для Каждого СтрокаТЧ Из Товары Цикл
Сообщить(Строка(СтрокаТЧ.Номенклатура) + " — " + Формат(СтрокаТЧ.Количество, "ЧГ=0"));
КонецЦикла;
Выгрузить() превращает табличную часть в ТаблицаЗначений. Это снова чтение всего объекта. Если документов много, берите строки запросом из таблицы Документ.РеализацияТоваровУслуг.Товары.
Запросы: как вытащить значения сразу у многих объектов
Как только объектов больше пяти-десяти, переходите на запрос. Один запрос заменяет сотни обращений через точку.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.ИНН КАК ИНН,
| Контрагенты.ГоловнойКонтрагент.Наименование КАК Головной
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка В (&Список)
| И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("Список", МассивСсылок);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Строка(Выборка.Ссылка) + " / " + Выборка.ИНН);
КонецЦикла;
Что здесь стоит запомнить:
- 🔹 выбирайте только нужные поля, а не
ВЫБРАТЬ *; - 🔹 разыменование через точку в запросе (
ГоловнойКонтрагент.Наименование) порождает соединение — для составных типов, ссылающихся на много таблиц, оно тяжёлое, используйтеВЫРАЗИТЬ; - 🔹 для одного значения хватит
Выбрать()и одногоСледующий(), для обработки в памяти —Выгрузить()в таблицу значений; - 🔹 параметр-массив в условии
В (&Список)удобнее, чем склейка ссылок в текст.
Одно значение из запроса
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Товар) КАК Цены";
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("Товар", Товар);
Выборка = Запрос.Выполнить().Выбрать();
Цена = ?(Выборка.Следующий(), Выборка.Цена, 0);
Конструкция с ?() защищает от пустого результата: без неё обращение к Выборка.Цена до Следующий() вызовет ошибку. Имена регистра и его измерений берите из своей конфигурации — в разных решениях они называются по-разному.
Значения из регистров сведений и накопления
Регистры — не объекты в привычном смысле. Значение здесь получают на дату и по отбору измерений.
Регистр сведений: последнее значение на дату
Для периодического регистра у менеджера есть метод ПолучитьПоследнее():
Отбор = Новый Структура("Валюта", ВалютаUSD);
Курс = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(ТекущаяДатаСеанса(), Отбор);
Сообщить(Курс.Курс);
Метод возвращает Структура с ресурсами. Если записей нет, ресурсы будут пустыми (0 для числа), без ошибки. Для первого значения после даты есть парный ПолучитьПервое(). Для непериодического регистра — Получить(Отбор).
Регистр накопления: остатки и обороты
У регистра накопления значения получают через виртуальные таблицы Остатки, Обороты, ОстаткиИОбороты — запросом:
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Момент, Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Момент", Новый Граница(МоментВремени, ВидГраницы.Исключая));
Ключевой приём: условие ставьте в параметры виртуальной таблицы, а не в ГДЕ. Отбор в параметрах платформа применяет до расчёта остатков, а ГДЕ — после. На регистре с миллионом записей разница — секунды против минут.
Движения конкретного документа
Записи регистра, которые сделал документ, читают через набор записей:
Набор = РегистрыНакопления.ТоварыНаСкладах.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(СсылкаНаДокумент);
Набор.Прочитать();
Для Каждого Запись Из Набор Цикл
Сообщить(Строка(Запись.Номенклатура) + ": " + Формат(Запись.Количество, "ЧГ=0"));
КонецЦикла;
Значения в управляемой форме: клиент и сервер
В управляемом приложении на клиенте нет доступа к базе. Строка Контрагент.ИНН в процедуре с директивой &НаКлиенте выдаст ошибку. Варианты решения:
- Серверная функция без контекста. Самый лёгкий вызов: на сервер не передаются данные формы.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
Объект.ИННКонтрагента = ПолучитьИНН(Объект.Контрагент);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьИНН(Контрагент)
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Контрагент, "ИНН");
КонецФункции
Данные формы. Реквизиты редактируемого объекта уже лежат в
Объект:Объект.Дата,Объект.Товары[0].Количествочитаются на клиенте без вызова сервера.Объект прикладного типа на сервере. Если нужны методы модуля объекта, конвертируйте данные формы:
ДокументОбъект = РеквизитФормыВЗначение("Объект"), а после изменений —ЗначениеВРеквизитФормы(ДокументОбъект, "Объект").
Группируйте чтение: один серверный вызов, который возвращает структуру из пяти полей, быстрее пяти отдельных вызовов. Каждый вызов сервера — это сетевой обмен, на медленном канале он заметен глазом.
Кэширование и асинхронное получение значений
Модули с повторным использованием
Если одно и то же значение нужно десятки раз за сеанс (основная организация, настройки учёта, константы), положите функцию в общий модуль и в его свойствах задайте Повторное использование возвращаемых значений: «На время вызова» или «На время сеанса».
// Общий модуль ЗначенияПовтИсп, повторное использование — на время сеанса
Функция ОсновнаяВалюта() Экспорт
Возврат Константы.ВалютаРегламентированногоУчета.Получить();
КонецФункции
Правила, о которых забывают:
- 🔸 модуль кэширует результат по значениям параметров — параметры должны быть простых типов или ссылками;
- 🔸 нельзя возвращать изменяемые коллекции (
Массив,Структура) и править их у себя — вы испортите кэш; оборачивайте вФиксированнаяСтруктураилиФиксированныйМассив; - 🔸 кэш «на время сеанса» не узнает, что пользователь сменил константу; для часто меняющихся данных берите «на время вызова».
Имя константы в примере взято для иллюстрации — проверьте его в дереве своей конфигурации.
Асинхронность и длительные операции
На клиенте значения, которые требуют ответа пользователя или долгой работы, получают асинхронно. С платформы 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...). Отличить такую ссылку можно поПолучитьОбъект() = Неопределено. - 🔻 Значение «на сейчас» вместо «на дату документа». Курс, цену, учётную политику берите на дату документа (
Объект.ДатаилиМоментВремени()), а не наТекущаяДатаСеанса(). Иначе при перепроведении задним числом получите другие суммы.
Частые вопросы
Чем ЗначениеРеквизитаОбъекта отличается от обращения через точку?
Как получить значение реквизита по имени, заданному строкой?
Объект["ИНН"]. На сервере то же даёт ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита).Как узнать, есть ли у объекта такой реквизит?
Ссылка.Метаданные().Реквизиты.Найти("ИНН") <> Неопределено. Стандартные реквизиты (Код, Наименование, Дата) проверяют в коллекции СтандартныеРеквизиты.Можно ли получить значение в фоновом задании или регламенте?
Как получить значение константы?
Константы.ИмяКонстанты.Получить() на сервере. Если она читается часто, оберните вызов в модуль с повторным использованием.