Ссылка пришла в клиентский код: как узнать её тип и прочитать данные объекта
Содержание
На тонком и веб-клиенте 1С объект по ссылке получить нельзя: методы ПолучитьОбъект() и Метаданные() у ссылки там недоступны, глобального свойства Метаданные тоже нет. Рабочий путь — передать ссылку в серверную процедуру с директивой &НаСервереБезКонтекста и вернуть оттуда то, что нужно клиенту: имя объекта метаданных, полное имя, значения реквизитов.
Если нужно только понять тип ссылки, сервер не нужен. На клиенте работают ТипЗнч(), сравнение с Тип("ДокументСсылка.…") и XMLТипЗнч(), который возвращает имя типа вида DocumentRef.РеализацияТоваровУслуг. Ниже — какой способ брать в каком случае и готовый код.
Что умеет ссылка на клиенте, а что нет
Ссылка — это не сам объект, а указатель на запись в таблице базы: тип плюс уникальный идентификатор. Данных объекта в ней нет. Чтобы прочитать реквизит, платформа обращается к базе, а база доступна только на сервере.
Отсюда простое правило: на клиенте с ссылкой можно делать то, что не требует чтения из базы.
| Действие | Тонкий и веб-клиент | Сервер, толстый клиент (обычное приложение) |
|---|---|---|
ТипЗнч(Ссылка), сравнение с Тип("…") | ✅ | ✅ |
XMLТипЗнч(Ссылка) | ✅ | ✅ |
ЗначениеЗаполнено(Ссылка), Ссылка.Пустая() | ✅ | ✅ |
Ссылка.Метаданные() | ❌ | ✅ |
Ссылка.ПолучитьОбъект() | ❌ | ✅ |
Ссылка.Наименование, Ссылка.Дата (чтение через точку) | ❌ | ✅ |
Менеджеры Справочники, Документы | ❌ | ✅ |
На клиенте вместо Справочники.Валюты.ПустаяСсылка() пишут ПредопределенноеЗначение("Справочник.Валюты.ПустаяСсылка"). Это тот же принцип: менеджеры живут на сервере.
Если код в модуле формы ругается «Метод объекта не обнаружен (Метаданные)» или «Поле объекта не обнаружено», первым делом посмотрите, под какой директивой он выполняется. Почти всегда это &НаКлиенте.
В толстом клиенте обычного приложения ограничений нет: там Ссылка.Метаданные() и Ссылка.ПолучитьОбъект() работают прямо в клиентском коде. Но такой код не переносится в управляемое приложение, поэтому дальше речь о тонком клиенте.
Как узнать имя объекта метаданных по ссылке
У ссылки нет свойства «ИмяОбъекта». Имя берут у объекта метаданных, а его — через метод Метаданные() на сервере.
Способ 1. Серверная функция без контекста
Самый надёжный вариант. В модуле формы:
&НаКлиенте
Процедура ПоказатьИмяТипа(Команда)
Если Не ЗначениеЗаполнено(Объект.Основание) Тогда
Сообщить("Основание не указано");
Возврат;
КонецЕсли;
СведенияОТипе = СведенияОМетаданных(Объект.Основание);
Сообщить(СтрШаблон("Имя: %1; полное имя: %2; синоним: %3",
СведенияОТипе.Имя, СведенияОТипе.ПолноеИмя, СведенияОТипе.Синоним));
КонецПроцедуры
&НаСервереБезКонтекста
Функция СведенияОМетаданных(Ссылка)
МетаОбъект = Ссылка.Метаданные();
Результат = Новый Структура;
Результат.Вставить("Имя", МетаОбъект.Имя);
Результат.Вставить("ПолноеИмя", МетаОбъект.ПолноеИмя());
Результат.Вставить("Синоним", МетаОбъект.Синоним);
Возврат Результат;
КонецФункции
Что вернётся для документа реализации:
- 📌
Имя—РеализацияТоваровУслуг; - 📌
ПолноеИмя—Документ.РеализацияТоваровУслуг; - 📌
Синоним— строка, которую видит пользователь, например «Реализация (акты, накладные, УПД)».
Директива &НаСервереБезКонтекста важна. Обычная &НаСервере тащит на сервер всю форму со всеми данными, а здесь нужна одна ссылка. Разница заметна на тяжёлых формах и в веб-клиенте.
Способ 2. Метаданные.НайтиПоТипу
На сервере можно получить тот же объект метаданных иначе:
МетаОбъект = Метаданные.НайтиПоТипу(ТипЗнч(Ссылка));
Удобно, когда на руках не ссылка, а тип: например, из описания типов реквизита. Для ссылки результат тот же, что у Ссылка.Метаданные(). Если тип не принадлежит объекту конфигурации (строка, число), метод вернёт Неопределено.
Определение типа ссылки на клиенте без вызова сервера
Каждый серверный вызов — это обращение по сети. В цикле по строкам табличной части сотня вызовов превращается в заметную паузу. Если задача — понять, «что за ссылка», обходитесь клиентом.
Сравнение с конкретным типом
Когда вариантов два-три и они заранее известны:
&НаКлиенте
Процедура ОснованиеПриИзменении(Элемент)
ТипОснования = ТипЗнч(Объект.Основание);
Если ТипОснования = Тип("ДокументСсылка.ЗаказКлиента") Тогда
Элементы.ГруппаЗаказ.Видимость = Истина;
ИначеЕсли ТипОснования = Тип("ДокументСсылка.СчетНаОплатуПокупателю") Тогда
Элементы.ГруппаЗаказ.Видимость = Ложь;
КонецЕсли;
КонецПроцедуры
Имена документов здесь условные — подставьте свои из дерева конфигурации. Если имя типа написано с ошибкой, Тип() выдаст исключение «Тип не определен» в момент выполнения, а не при синтаксической проверке.
XMLТипЗнч: имя объекта строкой
Когда типов много или имя нужно строкой, помогает XMLТипЗнч. Он возвращает объект ТипДанныхXML, у которого свойство ИмяТипа содержит английский префикс вида объекта и русское имя из конфигурации:
&НаКлиенте
Функция ИмяОбъектаПоСсылке(Ссылка)
Если Ссылка = Неопределено Тогда
Возврат "";
КонецЕсли;
ТипXML = XMLТипЗнч(Ссылка);
Если ТипXML = Неопределено Тогда
Возврат "";
КонецЕсли;
// "DocumentRef.РеализацияТоваровУслуг" -> "РеализацияТоваровУслуг"
Части = СтрРазделить(ТипXML.ИмяТипа, ".");
Если Части.Количество() < 2 Тогда
Возврат "";
КонецЕсли;
Возврат Части[1];
КонецФункции
Если нужно полное имя в русской нотации, переведите префикс по таблице:
Префикс в ИмяТипа | Вид объекта |
|---|---|
CatalogRef | Справочник |
DocumentRef | Документ |
EnumRef | Перечисление |
ChartOfCharacteristicTypesRef | ПланВидовХарактеристик |
ChartOfAccountsRef | ПланСчетов |
ChartOfCalculationTypesRef | ПланВидовРасчета |
BusinessProcessRef | БизнесПроцесс |
TaskRef | Задача |
ExchangePlanRef | ПланОбмена |
Синонима этот способ не даст: он хранится в метаданных. Для подписи на экране всё равно нужен сервер или заранее подготовленный реквизит формы.
Навигационная ссылка
Ещё один клиентский приём — ПолучитьНавигационнуюСсылку(Ссылка). Результат выглядит как e1cib/data/Справочник.Номенклатура?ref=…. Между e1cib/data/ и знаком вопроса стоит полное имя объекта метаданных. Разбирать эту строку ради типа — обходной путь, но для логирования и передачи ссылки другому пользователю она подходит идеально.
Пустые, незаполненные и битые ссылки
Половина ошибок в таком коде случается не на нормальных ссылках, а на «пограничных». Их три вида.
- 🔹 Неопределено. Реквизит составного типа, в который ничего не выбрали.
ТипЗнчвернёт типНеопределено,XMLТипЗнчвернётНеопределено. Передача такого значения в серверную функцию сСсылка.Метаданные()закончится ошибкой. - 🔹 Пустая ссылка. Тип известен, идентификатор нулевой.
Метаданные()у неё работает, аПолучитьОбъект()вернётНеопределено. - 🔹 Битая ссылка. Идентификатор есть, а записи в базе нет: объект удалили напрямую или не догрузили при обмене. В форме такое поле показывает
<Объект не найден>и идентификатор.
Универсальная проверка на клиенте — ЗначениеЗаполнено(). Она отсекает и Неопределено, и пустую ссылку. Битую ссылку на клиенте не распознать: нужен сервер.
&НаСервереБезКонтекста
Функция СсылкаСуществует(Ссылка)
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат Ложь;
КонецЕсли;
ИмяТаблицы = Ссылка.Метаданные().ПолноеИмя();
Запрос = Новый Запрос;
Запрос.Текст = СтрШаблон(
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле1
|ИЗ
| %1 КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка", ИмяТаблицы);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Возврат Не Запрос.Выполнить().Пустой();
КонецФункции
Полное имя Документ.РеализацияТоваровУслуг годится как имя таблицы в запросе без переделок. Проверка запросом дешевле, чем ПолучитьОбъект(): она не читает табличные части.
Для значений перечисления проверка тоже работает, но обычно лишняя: значения перечисления задаются в конфигураторе и в рабочей базе не удаляются.
Как прочитать реквизиты объекта по ссылке из клиентского кода
Часто «получить объект» на самом деле значит «узнать пару реквизитов»: дату, организацию, сумму. Тянуть ради этого весь объект на сервере расточительно, а передать объект на клиент вообще нельзя — он не сериализуется в клиентский контекст.
Схема простая: клиент передаёт ссылку и список полей, сервер возвращает структуру.
&НаКлиенте
Процедура ПроверитьОснование(Команда)
Данные = РеквизитыПоСсылке(Объект.Основание, "Дата, Номер, Проведен");
Если Данные = Неопределено Тогда
Сообщить("Основание не заполнено или не найдено");
Возврат;
КонецЕсли;
Если Не Данные.Проведен Тогда
Сообщить(СтрШаблон("Документ № %1 от %2 не проведен",
Данные.Номер, Формат(Данные.Дата, "ДЛФ=D")));
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция РеквизитыПоСсылке(Ссылка, ИменаПолей)
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = СтрШаблон(
"ВЫБРАТЬ
| %1
|ИЗ
| %2 КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка", ИменаПолей, Ссылка.Метаданные().ПолноеИмя());
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат Неопределено;
КонецЕсли;
Результат = Новый Структура(ИменаПолей);
ЗаполнитьЗначенияСвойств(Результат, Выборка);
Возврат Результат;
КонецФункции
Функция возвращает Неопределено и для пустой, и для битой ссылки, поэтому клиенту хватает одной проверки.
Имена полей подставляются в текст запроса как есть. Не передавайте туда строку, которую ввёл пользователь: это прямой путь к ошибкам запроса и чтению лишних данных.
В типовых конфигурациях на Библиотеке стандартных подсистем писать такую функцию самому не нужно. В серверном общем модуле ОбщегоНазначения есть функции ЗначениеРеквизитаОбъекта и ЗначенияРеквизитовОбъекта, а также ИмяТаблицыПоСсылке. Вызывайте их из своей серверной функции без контекста.
Когда без объекта не обойтись
Если надо изменить и записать объект, клиентом этого не сделать. Два варианта:
- Передать ссылку на сервер, там вызвать
ПолучитьОбъект(), поменять реквизиты иЗаписать(). Ошибки записи перехватывайте на сервере и возвращайте текст на клиент. - Открыть форму объекта:
ПоказатьЗначение(, Ссылка)откроет основную форму нужного типа, даже если вы не знаете, справочник это или документ. Пользователь правит и записывает сам.
Какой способ выбрать: сравнение
| Способ | Где работает | Даёт | Цена |
|---|---|---|---|
ТипЗнч + Тип("…") | клиент | ответ «да/нет» по типу | бесплатно, но имена зашиты в код |
XMLТипЗнч().ИмяТипа | клиент | имя объекта строкой | бесплатно, нет синонима |
ПолучитьНавигационнуюСсылку | клиент | полное имя внутри строки | разбор строки |
Ссылка.Метаданные() | сервер | имя, полное имя, синоним, состав реквизитов | серверный вызов |
Метаданные.НайтиПоТипу | сервер | то же, но по типу | серверный вызов |
| Запрос по ссылке | сервер | значения реквизитов, проверка существования | вызов и запрос к базе |
ПолучитьОбъект() | сервер, толстый клиент | весь объект, можно записать | самое дорогое чтение |
Сокращаем число серверных вызовов
Если имя метаданных нужно часто для одних и тех же типов, кэшируйте его. Создайте общий модуль, например РаботаСТипамиВызовСервераПовтИсп, со свойствами:
- ✅ «Сервер» и «Вызов сервера» включены;
- ✅ «Повторное использование возвращаемых значений» — «На время сеанса».
Функция принимает не ссылку, а строку с именем типа — так кэш работает по типу, а не по каждому документу:
Функция СинонимТипа(ИмяТипаXML) Экспорт
Тип = ИзXMLТипа(ИмяТипаXML, "");
Если Тип = Неопределено Тогда
Возврат "";
КонецЕсли;
МетаОбъект = Метаданные.НайтиПоТипу(Тип);
Возврат ?(МетаОбъект = Неопределено, "", МетаОбъект.Синоним);
КонецФункции
ИзXMLТипа восстанавливает тип по строке вида DocumentRef.РеализацияТоваровУслуг. Для объектов конфигурации пространство имён передаётся пустой строкой. На клиенте вызов выглядит так:
Синоним = РаботаСТипамиВызовСервераПовтИсп.СинонимТипа(XMLТипЗнч(Ссылка).ИмяТипа);
Первый вызов уйдёт на сервер, следующие для того же типа платформа вернёт из кэша. В цикле по 300 строкам с тремя видами документов получится три серверных вызова вместо трёхсот.
💡 Возвращаемое значение кэшированной функции не изменяйте на клиенте: если это структура, она общая для всех вызовов. Строка или число безопасны.