Почему Ссылка.Реквизит не работает в форме и чем его заменить

· 9 мин

Содержание

Получить реквизиты ссылки на клиенте напрямую нельзя: в управляемой форме обращение Ссылка.ИНН в процедуре с директивой &НаКлиенте не работает . Нужна серверная функция с директивой &НаСервереБезКонтекста, которая прочитает нужные поля и вернёт их структурой. В типовых конфигурациях на БСП это одна строка: ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "ИНН, КПП").

Типичная ситуация: в документе выбрали контрагента, и в обработчике ПриИзменении нужно сразу показать его ИНН или проверить договор. Код, который годами работал в обычных формах, в тонком клиенте падает. Причина не в ошибке разработчика, а в том, как устроен клиент управляемого приложения.

Почему на клиенте у ссылки нет реквизитов

В управляемом приложении код делится на клиентский и серверный. Тонкий клиент и веб-клиент не имеют доступа к базе данных. Всё, что лежит в таблицах, читает только сервер.

Ссылка на клиенте — это идентификатор объекта плюс его тип. Реквизитов внутри неё нет. Чтобы узнать ИНН контрагента, надо сходить в таблицу справочника, а это может только сервер.

Отсюда правила, которые стоит держать в голове:

  • 🚫 Ссылка.Реквизит в процедуре &НаКлиенте — не работает, при выполнении будет ошибка вида Поле объекта не обнаружено.
  • 🚫 Ссылка.ПолучитьОбъект() на клиенте недоступен.
  • 🚫 Объект Запрос на клиенте не существует — запросы пишутся только в серверном контексте.
  • ✅ Сравнить ссылки, проверить тип, проверить заполненность, получить представление — можно на клиенте.

В обычном приложении (толстый клиент, обычные формы) разыменование ссылки на клиенте работало: толстый клиент сам ходил в базу. Поэтому при переводе старой конфигурации на управляемые формы такие места всплывают первыми.

Каждый вызов сервера — это сетевой обмен. Внутри одной локальной сети он почти незаметен, а через интернет или веб-клиент каждый лишний вызов добавляет заметную задержку. Отсюда главное правило: собирайте всё, что нужно, за один вызов.

Что доступно на клиенте без обращения к серверу

Не всякая задача требует серверного вызова. Часто разработчик тянет реквизиты, хотя ему хватило бы того, что уже есть на клиенте.

ЗадачаКак решить на клиенте
Проверить, выбрано ли значениеЗначениеЗаполнено(Объект.Контрагент)
Узнать тип ссылкиТипЗнч(Объект.Получатель) = Тип("СправочникСсылка.Контрагенты")
Сравнить две ссылкиОбъект.Контрагент = Объект.Грузополучатель
Получить текст для сообщенияСтрока(Объект.Контрагент)
Взять поле из строки спискаЭлементы.Список.ТекущиеДанные.ИмяПоля

Представление ссылки

Представление — это тот текст, который пользователь видит в поле формы. Платформа передаёт его на клиент вместе со ссылкой, поэтому Строка(Ссылка) работает в клиентском коде. Этого достаточно для сообщения или заголовка: «Контрагент ООО Ромашка не заполнен».

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

Поля динамического списка

В форме списка реквизиты текущей строки уже есть на клиенте, если они выведены в колонки. Элементы.Список.ТекущиеДанные вернёт данные строки, и можно прочитать, например, ТекущиеДанные.ИНН.

Если поле нужно в коде, но не нужно на экране, у этого поля в настройках динамического списка включают признак «Использовать всегда». Без него невыведенное поле в ТекущиеДанные не попадёт. Для реквизитов связанных объектов (например, ИНН контрагента в списке документов) поле добавляют в запрос динамического списка.

Основной способ: серверная функция без контекста

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

Через БСП: ЗначенияРеквизитовОбъекта

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

  • ЗначениеРеквизитаОбъекта(Ссылка, "ИмяРеквизита") — одно значение;
  • ЗначенияРеквизитовОбъекта(Ссылка, "Имя1, Имя2") — структура с ключами по именам реквизитов;
  • ЗначенияРеквизитовОбъектов(МассивСсылок, "Имя1, Имя2") — соответствие «ссылка → структура» для нескольких ссылок одного типа.

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

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)

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

    Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
    ИННКонтрагента = Реквизиты.ИНН;
    КППКонтрагента = Реквизиты.КПП;

КонецПроцедуры

&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)

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

КонецФункции

Здесь ИННКонтрагента и КППКонтрагента — реквизиты формы строкового типа. Функция БСП читает только перечисленные поля одним запросом, а не весь объект.

Без БСП: свой запрос

В нетиповой конфигурации без библиотеки пишем запрос сами. Результат всегда возвращаем структурой с заранее заданными ключами, чтобы клиентский код не упал, если ссылка вдруг не найдена в базе:

&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)

    Результат = Новый Структура("ИНН, КПП", "", "");

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

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

    Возврат Результат;

КонецФункции

Почему без контекста, а не &НаСервере

Директива &НаСервере передаёт на сервер всю форму: реквизиты, табличные части, данные элементов. После выполнения форма едет обратно. Для документа с сотней строк товаров это лишние килобайты в каждом вызове.

&НаСервереБезКонтекста передаёт только параметры функции. Для чтения реквизитов по ссылке контекст формы не нужен, поэтому выбор очевиден.

ДирективаЧто уходит на серверКогда нужна
&НаСервереВся форма с даннымиНадо изменить реквизиты формы или объект на сервере
&НаСервереБезКонтекстаТолько параметрыПрочитать данные по ссылке и вернуть результат
Общий модуль с флагом «Вызов сервера»Только параметрыОдна и та же функция нужна во многих формах

Почему не ПолучитьОбъект

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

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

Запрос на сервере: реквизиты многих ссылок за один вызов

Частая ошибка — вызов серверной функции в цикле по строкам табличной части. Двадцать строк — двадцать обращений к серверу и двадцать запросов к базе.

Правильно: собрать ссылки на клиенте в массив, один раз отправить на сервер и получить соответствие «ссылка → значение».

&НаКлиенте
Процедура ЗаполнитьИНН(Команда)

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

    Если Ссылки.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;

    ИННПоСсылкам = ИННКонтрагентов(Ссылки);

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

КонецПроцедуры

&НаСервереБезКонтекста
Функция ИННКонтрагентов(Знач Ссылки)

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

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

    Возврат Результат;

КонецФункции

Поставщики здесь — условная табличная часть с колонками Контрагент и ИНН. Повторяющиеся ссылки в массиве не мешают: условие В (&Ссылки) вернёт каждую запись один раз.

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

Ссылки составного типа

Реквизит Получатель может хранить ссылку на контрагента, на физлицо или на организацию. На клиенте тип легко проверить, а вот какие у него реквизиты, решает сервер.

Проверка типа на клиенте

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

&НаКлиенте
Процедура ПолучательПриИзменении(Элемент)

    Если ТипЗнч(Объект.Получатель) = Тип("СправочникСсылка.Контрагенты") Тогда
        ИННПолучателя = ОбщийМодульСервер.ИННПоСсылке(Объект.Получатель);
    Иначе
        ИННПолучателя = "";
    КонецЕсли;

КонецПроцедуры

ОбщийМодульСервер — пример имени вашего общего модуля с флагами «Сервер» и «Вызов сервера».

ВЫРАЗИТЬ в запросе

Когда составное поле разыменовывают в запросе через точку (Документ.Получатель.ИНН), платформа соединяет таблицы всех возможных типов. Чем больше типов в составе, тем тяжелее запрос.

Если нужен реквизит конкретного типа, приведите поле явно:

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

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

Ссылки разных типов в одном списке

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

&НаСервереБезКонтекста
Функция РеквизитыРазныхСсылок(Знач Ссылки, Знач ИменаРеквизитов)

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

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

    Возврат Результат;

КонецФункции

Запрашиваемые реквизиты должны существовать у всех типов из списка. Проверить наличие на сервере можно так: Ссылка.Метаданные().Реквизиты.Найти("ИНН") <> Неопределено.

Что нужно получить по ссылке на клиенте?

Как не тормозить форму и не создавать блокировок

Чтение реквизитов само по себе дешёвое. Проблемы появляются от количества вызовов и от тяжёлых способов чтения.

Читайте заранее, если данные нужны сразу

Если реквизиты нужны при открытии формы, прочитайте их в ПриСозданииНаСервере и положите в реквизиты формы. Этот обработчик и так выполняется на сервере, лишнего вызова не будет. Клиентский код потом берёт готовые значения.

Кэшируйте редко меняющиеся данные

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

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

Блокировки при чтении

Сам клиентский код транзакций не открывает. Серверная функция, которая только читает данные запросом, в режиме управляемых блокировок не ставит блокировок и не мешает другим пользователям записывать документы.

Проблемы начинаются, когда в «функцию чтения» попадает лишнее:

  • 🔒 ПолучитьОбъект() с последующей записью «чтобы обновить» — запись ставит блокировку и запускает все подписки и проверки;
  • 🔒 явная БлокировкаДанных в функции, которая ничего не меняет;
  • 🐢 вызов сервера в цикле по строкам — не блокировка, но пользователь видит подвисание формы.
ПриёмВызовов сервераНагрузка на базу
Ссылка.Реквизит в цикле в серверной процедуре1Чтение каждого объекта целиком
Серверная функция в цикле на клиентеПо числу строкПо одному запросу на строку
Массив ссылок + один запрос1Один запрос по нужным полям
ПриСозданииНаСервере + реквизиты формы0 дополнительныхОдин раз при открытии

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

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