Ссылка пришла в клиентский код: как узнать её тип и прочитать данные объекта

· 9 мин

Содержание

На тонком и веб-клиенте 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 КАК Т
		|ГДЕ
		|	Т.Ссылка = &Ссылка", ИменаПолей, Ссылка.Метаданные().ПолноеИмя());
	Запрос.УстановитьПараметр("Ссылка", Ссылка);
	
	Выборка = Запрос.Выполнить().Выбрать();
	Если Не Выборка.Следующий() Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Результат = Новый Структура(ИменаПолей);
	ЗаполнитьЗначенияСвойств(Результат, Выборка);
	Возврат Результат;
	
КонецФункции

Функция возвращает Неопределено и для пустой, и для битой ссылки, поэтому клиенту хватает одной проверки.

Имена полей подставляются в текст запроса как есть. Не передавайте туда строку, которую ввёл пользователь: это прямой путь к ошибкам запроса и чтению лишних данных.

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

Когда без объекта не обойтись

Если надо изменить и записать объект, клиентом этого не сделать. Два варианта:

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

Какой способ выбрать: сравнение

СпособГде работаетДаётЦена
ТипЗнч + Тип("…")клиентответ «да/нет» по типубесплатно, но имена зашиты в код
XMLТипЗнч().ИмяТипаклиентимя объекта строкойбесплатно, нет синонима
ПолучитьНавигационнуюСсылкуклиентполное имя внутри строкиразбор строки
Ссылка.Метаданные()серверимя, полное имя, синоним, состав реквизитовсерверный вызов
Метаданные.НайтиПоТипусерверто же, но по типусерверный вызов
Запрос по ссылкесерверзначения реквизитов, проверка существованиявызов и запрос к базе
ПолучитьОбъект()сервер, толстый клиентвесь объект, можно записатьсамое дорогое чтение
Что вам нужно сделать со ссылкой на клиенте?

Сокращаем число серверных вызовов

Если имя метаданных нужно часто для одних и тех же типов, кэшируйте его. Создайте общий модуль, например РаботаСТипамиВызовСервераПовтИсп, со свойствами:

  • ✅ «Сервер» и «Вызов сервера» включены;
  • ✅ «Повторное использование возвращаемых значений» — «На время сеанса».

Функция принимает не ссылку, а строку с именем типа — так кэш работает по типу, а не по каждому документу:

Функция СинонимТипа(ИмяТипаXML) Экспорт
	
	Тип = ИзXMLТипа(ИмяТипаXML, "");
	Если Тип = Неопределено Тогда
		Возврат "";
	КонецЕсли;
	
	МетаОбъект = Метаданные.НайтиПоТипу(Тип);
	Возврат ?(МетаОбъект = Неопределено, "", МетаОбъект.Синоним);
	
КонецФункции

ИзXMLТипа восстанавливает тип по строке вида DocumentRef.РеализацияТоваровУслуг. Для объектов конфигурации пространство имён передаётся пустой строкой. На клиенте вызов выглядит так:

Синоним = РаботаСТипамиВызовСервераПовтИсп.СинонимТипа(XMLТипЗнч(Ссылка).ИмяТипа);

Первый вызов уйдёт на сервер, следующие для того же типа платформа вернёт из кэша. В цикле по 300 строкам с тремя видами документов получится три серверных вызова вместо трёхсот.

💡 Возвращаемое значение кэшированной функции не изменяйте на клиенте: если это структура, она общая для всех вызовов. Строка или число безопасны.

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

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