Чтобы в 1С с БСП получить реквизит объекта по ссылке, не нужно ни писать запрос, ни обращаться к полю через точку. В общем модуле ОбщегоНазначения есть четыре функции, которые читают из базы только указанные поля: ЗначениеРеквизитаОбъекта возвращает одно значение, ЗначенияРеквизитовОбъекта — структуру, а ЗначениеРеквизитаОбъектов и ЗначенияРеквизитовОбъектов работают с массивом ссылок. Ниже — готовый код, табличные части, вызов с клиента и поведение без прав.
Код сверен с БСП 3.1.5 (выгрузка УТ 11.4) и 3.1.10 (КА 2.5). Функции лежат в конфигураторе: «Общие» → «Общие модули» → ОбщегоНазначения, область ДанныеВБазе. В другой версии библиотеки сверьтесь с комментарием над функцией.
Типичная ситуация — получить значение объекта по ссылке, не загружая сам объект:
Если ссылки ещё нет, найдите её по коду или наименованию — см. статью о поиске ссылки.
Обращение Реализация.Контрагент.ИНН выглядит дешёвым, но на каждой точке платформа читает из базы объект целиком, а не одно поле: сначала документ, потом контрагента. Функция БСП читает запросом только нужные поля, а путь через точку превращает в соединения внутри запроса.
// Было: каждая точка от ссылки — чтение объекта из базы целиком
ИННПокупателя = Реализация.Контрагент.ИНН;
ОкончаниеДоговора = Реализация.Договор.ДатаОкончанияДействия;
// Стало: один запрос, в нём только два поля
Данные = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Реализация,
"Контрагент.ИНН, Договор.ДатаОкончанияДействия");
ИННПокупателя = Данные.КонтрагентИНН;
ОкончаниеДоговора = Данные.ДоговорДатаОкончанияДействия;
Ещё два довода. В тонком и веб-клиенте реквизиты по ссылке не читаются вовсе — разбор этой ошибки в статье «Поле объекта не обнаружено». А при ограничении доступа на уровне записей функции БСП умеют вернуть Неопределено вместо исключения.
Кэша у функций нет: каждый вызов — новый запрос. Значение, которое нужно несколько раз, сохраните в переменную.
| Функция | Что передать | Что вернётся |
|---|---|---|
ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита, ВыбратьРазрешенные = Ложь, КодЯзыка = Неопределено) | ссылку или полное имя предопределённого элемента строкой; имя реквизита, можно через точку | значение реквизита |
ЗначенияРеквизитовОбъекта(Ссылка, Реквизиты, ВыбратьРазрешенные = Ложь, КодЯзыка = Неопределено) | то же; реквизиты — строка через запятую, массив имён или структура с псевдонимами | Структура |
ЗначениеРеквизитаОбъектов(МассивСсылок, ИмяРеквизита, ВыбратьРазрешенные = Ложь, КодЯзыка = Неопределено) | массив ссылок, допускаются разные типы; имя без точки | Соответствие: ссылка → значение |
ЗначенияРеквизитовОбъектов(Ссылки, Реквизиты, ВыбратьРазрешенные = Ложь, КодЯзыка = Неопределено) | массив ссылок; реквизиты строкой или массивом, без точки | Соответствие: ссылка → структура |
ВыбратьРазрешенные = Истина добавляет в запрос слово РАЗРЕШЕННЫЕ, разбор — ниже. КодЯзыка нужен только для мультиязычных реквизитов. Модуль ОбщегоНазначения серверный и без флага «Вызов сервера»: на клиенте управляемого приложения его нет.
Функции кладут в общий модуль с флагом «Сервер» или в модуль менеджера. Имена реквизитов взяты из УТ 11.4 — в другой конфигурации подставьте свои.
// Общий модуль «ПродажиСервер»: флаг «Сервер», без «Вызова сервера».
// Имена реквизитов — из документа РеализацияТоваровУслуг УТ 11.4.
// Один реквизит: читается запросом, объект целиком не загружается.
Функция СкладРеализации(Реализация) Экспорт
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Реализация, "Склад");
КонецФункции
// Путь через точку: реквизит договора прямо из документа, без чтения договора.
// Если договор в документе не указан, придёт NULL.
Функция ОкончаниеДоговораРеализации(Реализация) Экспорт
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Реализация, "Договор.ДатаОкончанияДействия");
КонецФункции
// Несколько полей с псевдонимами: ключ — имя в результате, значение — поле таблицы.
// Значение не задано — поле берётся из ключа.
Функция ДанныеШапкиРеализации(Реализация) Экспорт
Поля = Новый Структура;
Поля.Вставить("Номер");
Поля.Вставить("Дата");
Поля.Вставить("Сумма", "СуммаДокумента");
Поля.Вставить("Покупатель", "Контрагент");
Поля.Вставить("ИННПокупателя", "Контрагент.ИНН");
Поля.Вставить("КПППокупателя", "Контрагент.КПП");
Поля.Вставить("ИННПродавца", "Организация.ИНН");
Поля.Вставить("Продавец", "Организация.НаименованиеПолное");
// Структура с ключами Номер, Дата, Сумма, Покупатель, ИННПокупателя и т. д.
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Реализация, Поля);
КонецФункции
// Строка без псевдонимов: у поля через точку ключ — имя без точек.
Функция ОписаниеРеализации(Реализация) Экспорт
Данные = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Реализация, "Номер, Дата, Контрагент.ИНН");
// Ключи: Номер, Дата и КонтрагентИНН. Ключа Контрагент в структуре нет.
Возврат СтрШаблон(НСтр("ru = '№ %1 от %2, ИНН покупателя %3'"),
Данные.Номер, Формат(Данные.Дата, "ДЛФ=D"), Данные.КонтрагентИНН);
КонецФункции
// Предопределённый элемент передаём строкой с полным именем — ссылка не нужна.
// Элемент ещё не создан в базе — вернётся Неопределено.
Функция НаименованиеУправленческойОрганизации() Экспорт
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
"Справочник.Организации.УправленческаяОрганизация", "Наименование");
КонецФункции
"Договор.ДатаОкончанияДействия" — путь через точку. В запросе это неявное соединение с таблицей договоров. Если договор в документе не указан, соединение ничего не найдёт и придёт NULL."Номер, Дата, Контрагент.ИНН". Без псевдонима поле через точку получает ключ без точек — КонтрагентИНН, ключа Контрагент нет. ЗначениеРеквизитаОбъекта убирает точки сама и сразу возвращает значение.Неопределено, опечатка в имени — исключение «Неверный первый параметр Ссылка…»."Номер, Дата, Контрагент.ИНН" стоит двух обращений к базе. В цикле это заметно.ЗначениеРеквизитаОбъекта в цикле по тысяче строк — тысяча запросов. Функции для массивов делают один запрос: для каждого типа ссылок своя часть, части склеены через ОБЪЕДИНИТЬ ВСЕ. Так одним вызовом можно получить реквизиты объекта для всей таблицы товаров.
// Тот же общий модуль. Чтение по массиву ссылок: один запрос на все строки.
// Добавляет в таблицу значений колонки Артикул и ЕдиницаИзмерения по колонке Номенклатура.
Процедура ДобавитьСвойстваНоменклатуры(ТаблицаТоваров) Экспорт
ТаблицаТоваров.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка"));
ТаблицаТоваров.Колонки.Добавить("ЕдиницаИзмерения");
// Только заполненные ссылки: Неопределено в массиве вызовет исключение.
// Повторы не мешают — в запросе условие «Ссылка В (&Ссылки)».
Ссылки = Новый Массив;
Для Каждого СтрокаТоваров Из ТаблицаТоваров Цикл
Если ЗначениеЗаполнено(СтрокаТоваров.Номенклатура) Тогда
Ссылки.Добавить(СтрокаТоваров.Номенклатура);
КонецЕсли;
КонецЦикла;
// Соответствие: ключ — ссылка, значение — структура с ключами Артикул и ЕдиницаИзмерения.
Свойства = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(Ссылки, "Артикул, ЕдиницаИзмерения");
Для Каждого СтрокаТоваров Из ТаблицаТоваров Цикл
СвойстваТовара = Свойства.Получить(СтрокаТоваров.Номенклатура);
Если СвойстваТовара <> Неопределено Тогда // пустых и битых ссылок в соответствии нет
ЗаполнитьЗначенияСвойств(СтрокаТоваров, СвойстваТовара);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Организации документов разных видов (реализации и заказы вперемешку) одним вызовом.
// Результат: Организации.Получить(ДокументСсылка) — Неопределено, если ссылка битая
// или документ скрыт ограничением на уровне записей.
Функция ОрганизацииДокументов(МассивДокументов) Экспорт
Ссылки = Новый Массив;
Для Каждого ДокументСсылка Из МассивДокументов Цикл
Если ЗначениеЗаполнено(ДокументСсылка) И ОбщегоНазначения.ЭтоСсылка(ТипЗнч(ДокументСсылка)) Тогда
Ссылки.Добавить(ДокументСсылка);
КонецЕсли;
КонецЦикла;
// Третий параметр Истина: недоступные документы выпадут из результата без исключения.
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъектов(Ссылки, "Организация", Истина);
КонецФункции
Ссылка В (&Ссылки) вернёт каждый объект один раз.ВыбратьРазрешенные = Истина) скрытых правами ссылок в соответствии нет — Получить() вернёт Неопределено.Неопределено, строка или число в массиве дают исключение, поэтому массив фильтруем заранее.Как собрать и свернуть сам массив — в статье про массивы в БСП.
Имя табличной части передают как обычный реквизит — так делает и сама БСП, например в модуле менеджера справочника «Группы доступа». В ответ приходит РезультатЗапроса со всеми колонками: у табличной части «Товары» реализации в УТ 11.4 их три десятка, даже если нужны две. Когда нужны отдельные колонки, отбор или итог, удобнее запрос к таблице Документ.РеализацияТоваровУслуг.Товары.
// Табличная часть по имени: приходит РезультатЗапроса со всеми колонками ТЧ.
Функция ТоварыРеализации(Реализация) Экспорт
РезультатТЧ = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Реализация, "Товары");
Если РезультатТЧ = Неопределено Тогда
Возврат Неопределено; // пустая или битая ссылка
КонецЕсли;
Возврат РезультатТЧ.Выгрузить(); // ТаблицаЗначений
КонецФункции
// Шапка и табличная часть одним вызовом: поле Товары в структуре — РезультатЗапроса.
Функция ТоварыБезЦены(Реализация) Экспорт
НоменклатураБезЦены = Новый Массив;
Данные = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Реализация, "Проведен, Товары");
Если Данные.Проведен <> Истина Тогда
Возврат НоменклатураБезЦены; // не проведён, ссылка пустая или битая
КонецЕсли;
Выборка = Данные.Товары.Выбрать();
Пока Выборка.Следующий() Цикл
Если Не ЗначениеЗаполнено(Выборка.Цена) Тогда
НоменклатураБезЦены.Добавить(Выборка.Номенклатура);
КонецЕсли;
КонецЦикла;
Возврат НоменклатураБезЦены;
КонецФункции
// Нужны две колонки, итог и реквизит номенклатуры — свой запрос к таблице ТЧ.
Функция КоличествоПоНоменклатуре(Реализация) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| Товары.Номенклатура.Артикул КАК Артикул,
| СУММА(Товары.Количество) КАК Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| Товары.Номенклатура,
| Товары.Номенклатура.Артикул";
Запрос.УстановитьПараметр("Ссылка", Реализация);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Проверка на Неопределено перед Выгрузить() обязательна: для пустой или битой ссылки результата запроса не будет, и вызов метода упадёт с ошибкой «Значение не является значением объектного типа». Запрос к табличной части удобно сначала отладить в консоли запросов.
В клиентской процедуре формы модуль ОбщегоНазначения недоступен. Клиентский обработчик вызывает серверную функцию, та вызывает БСП и возвращает структуру. Для разового чтения подходит &НаСервереБезКонтекста: на сервер уходит только ссылка, без данных формы. Если функция нужна в нескольких формах, вынесите её в общий модуль с флагами «Сервер» и «Вызов сервера». Подробнее о директивах — в статье про &НаКлиенте и &НаСервере.
// Модуль формы своего документа или обработки с реквизитами
// Объект.Договор, Объект.Организация, Объект.Контрагент, Объект.ВалютаВзаиморасчетов.
&НаКлиенте
Процедура ДоговорПриИзменении(Элемент)
Если Не ЗначениеЗаполнено(Объект.Договор) Тогда
Возврат; // пустую ссылку на сервер не отправляем
КонецЕсли;
// Один серверный вызов: уходит только ссылка, данные формы не передаются.
ДанныеДоговора = РеквизитыДоговора(Объект.Договор);
Если ДанныеДоговора.Контрагент = Неопределено Тогда
ПоказатьПредупреждение(, НСтр("ru = 'Договор не найден или недоступен.'"));
Возврат;
КонецЕсли;
Объект.Организация = ДанныеДоговора.Организация;
Объект.Контрагент = ДанныеДоговора.Контрагент;
Объект.ВалютаВзаиморасчетов = ДанныеДоговора.ВалютаВзаиморасчетов;
КонецПроцедуры
&НаСервереБезКонтекста
Функция РеквизитыДоговора(Договор)
// Истина: запись, скрытая ограничением на уровне записей, даст Неопределено, а не исключение.
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Договор,
"Организация, Контрагент, ВалютаВзаиморасчетов", Истина);
КонецФункции
// Строки табличной части для клиента: РезультатЗапроса и ТаблицаЗначений
// туда не передаются, поэтому возвращаем массив структур.
// Вызов на клиенте: Строки = СтрокиТоваров(Объект.Ссылка);
&НаСервереБезКонтекста
Функция СтрокиТоваров(ДокументСсылка)
Строки = Новый Массив;
ТоварыДокумента = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ДокументСсылка, "Товары");
Если ТоварыДокумента = Неопределено Тогда
Возврат Строки;
КонецЕсли;
Выборка = ТоварыДокумента.Выбрать();
Пока Выборка.Следующий() Цикл
Строки.Добавить(Новый Структура("Номенклатура, Количество, Сумма",
Выборка.Номенклатура, Выборка.Количество, Выборка.Сумма));
КонецЦикла;
Возврат Строки;
КонецФункции
ТаблицаЗначений и РезультатЗапроса на клиент не передаются, поэтому строки табличной части перекладываем в массив структур.Готовой клиентской обёртки в БСП 3.1.5 и 3.1.10 нет: в ОбщегоНазначенияВызовСервера функций чтения реквизитов нет. В УТ 11.4 есть ОбщегоНазначенияУТВызовСервера.ЗначениеРеквизитаОбъекта, но она в служебной области модуля, а в КА 2.5 помечена «Устарел». Своя функция надёжнее.
Поведение по описаниям функций и коду БСП 3.1.5 и 3.1.10:
| Ситуация | Один объект | Массив ссылок |
|---|---|---|
| Пустая ссылка | Неопределено; в структуре все поля Неопределено | ссылки нет в соответствии |
| Битая ссылка (объект удалён) | то же | то же |
Запись скрыта ограничением на уровне записей, ВыбратьРазрешенные = Ложь | исключение | исключение |
То же, ВыбратьРазрешенные = Истина | Неопределено | объекта нет в соответствии |
| Нет права «Чтение» на таблицу | исключение при любом значении параметра | исключение |
| Пустое звено в пути через точку | NULL | путь через точку не поддерживается |
Если реквизит нужен независимо от прав, БСП советует перед вызовом включить привилегированный режим — так написано в комментарии к каждой из четырёх функций. Чтобы различить причины пустого ответа, добавьте в список полей Ссылка: так сама БСП проверяет, прочиталась ли запись. Настройка ограничений — в статье про RLS.
// Общий модуль (Сервер). Читает реквизиты с учётом прав и объясняет, почему данных нет.
// Реквизиты — строка через запятую, например "ИНН, КПП".
// Причина: "" — прочитано; ПустаяСсылка, НетПраваНаТаблицу, СкрытаПравами, БитаяСсылка.
Функция ПрочитатьРеквизитыСПричиной(Ссылка, Реквизиты) Экспорт
Результат = Новый Структура("Данные, Причина", Неопределено, "");
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Результат.Причина = "ПустаяСсылка";
Возврат Результат;
КонецЕсли;
// Без права «Чтение» на таблицу исключение будет и при ВыбратьРазрешенные = Истина.
Если Не ПравоДоступа("Чтение", Ссылка.Метаданные()) Тогда
Результат.Причина = "НетПраваНаТаблицу";
Возврат Результат;
КонецЕсли;
// Поле Ссылка — признак: Неопределено значит, что строка объекта не прочитана.
Данные = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "Ссылка, " + Реквизиты, Истина);
Если Данные.Ссылка <> Неопределено Тогда
Результат.Данные = Данные;
ИначеЕсли ОбщегоНазначения.СсылкаСуществует(Ссылка) Тогда
Результат.Причина = "СкрытаПравами"; // объект есть, но ограничение на уровне записей его скрыло
Иначе
Результат.Причина = "БитаяСсылка";
КонецЕсли;
Возврат Результат;
КонецФункции
// Служебное чтение без учёта прав: только на сервере и только нужное поле.
Функция МенеджерДокументаБезУчетаПрав(ДокументСсылка) Экспорт
УстановитьПривилегированныйРежим(Истина);
Менеджер = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ДокументСсылка, "Менеджер");
УстановитьПривилегированныйРежим(Ложь);
Возврат Менеджер;
КонецФункции
СсылкаСуществует внутри включает привилегированный режим и поэтому видит скрытые записи. Сам привилегированный режим снимает проверку прав со всего, что выполняется до его отключения: включайте его на одно чтение и не отдавайте прочитанное на клиент без нужды. Подробнее — в статье о привилегированном режиме.
В обработчике ПередЗаписью новая версия объекта ещё в памяти, а в базе лежит прежняя. Функция БСП читает базу и возвращает старое значение — так видно, что именно поменял пользователь. Пример — обработчик подписки на событие документа:
// Общий модуль «КонтрольИзмененийСервер» (Сервер). Обработчик подписки на событие
// ПередЗаписью документа реализации: в базе ещё прежняя версия, в Источник — новая.
Процедура ЗапомнитьСменуСклада(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
Если Источник.ОбменДанными.Загрузка Или Источник.ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Читается запись из базы, а не объект в памяти, — это и есть старые значения.
Прежние = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Источник.Ссылка, "Склад, Проведен");
СкладИзменен = (Прежние.Проведен = Истина) И (Прежние.Склад <> Источник.Склад);
Источник.ДополнительныеСвойства.Вставить("СкладИзмененПослеПроведения", СкладИзменен);
КонецПроцедуры
У нового документа в базе ничего нет, поэтому ЭтоНовый() проверяется первым, а при загрузке из обмена обработчик пропускается.
&НаКлиенте. Нужна серверная функция, как в разделе про клиент.ЗначениеРеквизитаОбъектов или ЗначенияРеквизитовОбъектов.Неопределено или примитивное значение."Контрагент.ИНН", а читаете Данные.Контрагент.ИНН. Правильно — Данные.КонтрагентИНН.Неопределено, для пустого звена пути — NULL. Проверяйте через ЗначениеЗаполнено().ВыбратьРазрешенные там, где включено ограничение на уровне записей.В управляемом приложении нет: модуль серверный. Оберните вызов в функцию &НаСервереБезКонтекста или в общий модуль с флагом «Вызов сервера» и возвращайте структуру из ссылок, строк, чисел и дат.
Внутри функции тот же запрос к таблице объекта, только собранный автоматически. Свой запрос нужен, когда есть отбор, группировка, соединение с регистрами или несколько колонок табличной части.
Вариантов три: запись скрыта правами при ВыбратьРазрешенные = Истина, ссылка битая (объект удалён или ссылка пришла из другой базы) или реквизит составного типа и в нём действительно Неопределено. Какой случай ваш, покажет ПрочитатьРеквизитыСПричиной из листинга выше.
Функции из статьи только читают поля. Если объект нужно изменить и записать, вызовите ПолучитьОбъект() у ссылки на сервере. Для чтения двух-трёх полей это лишняя нагрузка.
Если документы проводятся медленно из-за сотен обращений через точку, а пользователи с ограниченными правами ловят исключения, переписать чтение и проверить его под разными ролями проще опытному программисту — среди услуг по доработке 1С есть направление «1С тормозит». Опишите задачу на странице разместить задачу: оценка бесплатная, ответ в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.