Владелец подчинённого справочника в 1С 8.3: получаем ссылку из кода, запроса и отладчика
Содержание
К владельцу элемента справочника обращаются через стандартный реквизит Владелец: Договор.Владелец вернёт ссылку на контрагента, которому подчинён договор. Реквизит есть и у ссылки, и у объекта, и у поля в запросе. Сработает он только в серверном контексте и только у справочника, для которого в конфигураторе задан список владельцев.
Дальше — пять рабочих способов добраться до владельца справочника в 1С: от одной строчки кода до запроса с соединением и проверки в отладчике. Примеры на типовом сочетании «Контрагенты → Договоры контрагентов», которое есть в Бухгалтерии 3.0, УТ 11 и ERP. Всё написано под платформу 8.3 и управляемые формы.
Как устроено подчинение: настройка владельцев в конфигураторе
Подчинённость задаётся в метаданных, а не в коде. Пока у справочника нет владельцев, реквизита Владелец у него просто нет, и любое обращение к нему закончится ошибкой.
Где посмотреть настройку:
- Откройте конфигуратор и дерево конфигурации.
- Раскройте ветку
Справочникии дважды щёлкните подчинённый справочник, напримерДоговорыКонтрагентов. - Перейдите на вкладку
Владельцы. - В списке «Владельцы справочника» перечислены справочники, которым подчинены элементы.
На той же вкладке есть свойство «Использование подчинения». Оно определяет, кто подчиняется владельцу:
- 📄 Элементам — владелец есть только у элементов;
- 📁 Группам — только у групп иерархического справочника;
- 🗂 Группам и элементам — у тех и других.
Если владельцев в списке несколько, реквизит Владелец получает составной тип. Так устроены, например, банковские счета в типовых конфигурациях: счёт может принадлежать и контрагенту, и своей организации. Для кода это значит одно — перед обращением к полям владельца нужно проверить его тип.
Список владельцев можно прочитать и программно: Метаданные.Справочники.ДоговорыКонтрагентов.Владельцы. Это коллекция объектов метаданных, у неё есть метод Содержит().
МетаДоговоров = Метаданные.Справочники.ДоговорыКонтрагентов;
Если МетаДоговоров.Владельцы.Содержит(Метаданные.Справочники.Контрагенты) Тогда
Сообщить("Договоры подчинены контрагентам");
КонецЕсли;
Такая проверка пригодится в универсальных обработках, которые работают с любым справочником и заранее не знают, есть ли у него владелец.
Свойство «Владелец» у ссылки на элемент справочника
Самый короткий способ — точка после ссылки. Платформа сама прочитает элемент из базы и вернёт значение реквизита.
&НаСервере
Функция КонтрагентПоДоговору(Договор)
Если НЕ ЗначениеЗаполнено(Договор) Тогда
Возврат Справочники.Контрагенты.ПустаяСсылка();
КонецЕсли;
Возврат Договор.Владелец;
КонецФункции
Результат — ссылка СправочникСсылка.Контрагенты. Через неё можно идти дальше по цепочке: Договор.Владелец.ИНН, Договор.Владелец.Наименование.
Что происходит «под капотом»:
- 🔎 при первом обращении к реквизиту через ссылку платформа читает элемент целиком, а не одно поле;
- ⏱ прочитанные данные кешируются на короткое время, повторное обращение в том же месте кода дешевле;
- 🔁 каждая точка в цепочке
Договор.Владелец.ОсновнойБанковскийСчет.Банк— ещё одно чтение из базы.
Для одного-двух элементов это нормально. В цикле по тысяче договоров такой код станет заметно медленным — там нужен запрос, о нём ниже.
Если в конфигурации есть БСП
В типовых конфигурациях на Библиотеке стандартных подсистем удобнее функция общего модуля:
Владелец = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Договор, "Владелец");
Она читает из базы только указанное поле, а не весь элемент. Для нескольких реквизитов сразу есть ЗначенияРеквизитовОбъекта(Ссылка, "Владелец, Наименование") — вернёт структуру. Обе функции серверные.
Через ПолучитьОбъект(): когда нужен объект, а не ссылка
Метод ПолучитьОбъект() у ссылки возвращает СправочникОбъект. У объекта тоже есть свойство Владелец, и его можно не только прочитать, но и изменить.
&НаСервере
Процедура ПеренестиДоговор(Договор, НовыйКонтрагент)
ДоговорОбъект = Договор.ПолучитьОбъект();
Если ДоговорОбъект = Неопределено Тогда
Возврат; // элемент не найден в базе
КонецЕсли;
СтарыйВладелец = ДоговорОбъект.Владелец;
ДоговорОбъект.Владелец = НовыйКонтрагент;
ДоговорОбъект.Записать();
Сообщить("Договор перенесён от " + СтарыйВладелец + " к " + НовыйКонтрагент);
КонецПроцедуры
Когда выбирать этот путь:
- ✏️ нужно сменить владельца или поменять другие реквизиты вместе с ним;
- 🧩 код уже работает с объектом: в модуле объекта, в обработчике
ПередЗаписью, в форме элемента; - 🧪 нужно проверить заполнение владельца перед записью.
Внутри модуля объекта справочника к реквизиту обращаются без префикса: просто Владелец. В модуле формы элемента — через основной реквизит формы: Объект.Владелец. Это значение доступно и на клиенте, потому что лежит в данных формы.
Смена владельца у договора, по которому уже есть документы, ломает связку «контрагент документа — владелец договора». В типовых БП и УТ при проведении такие документы начнут ругаться на несоответствие договора контрагенту. Перед массовым переносом элементов сделайте резервную копию базы.
Новый подчинённый элемент: владельца указывают до записи
При создании элемента через менеджер владелец заполняется так же, как любой реквизит. Делать это нужно до вызова Записать():
&НаСервере
Функция СоздатьДоговор(Контрагент, Наименование)
НовыйДоговор = Справочники.ДоговорыКонтрагентов.СоздатьЭлемент();
НовыйДоговор.Владелец = Контрагент;
НовыйДоговор.Наименование = Наименование;
НовыйДоговор.Записать();
Возврат НовыйДоговор.Ссылка;
КонецФункции
Если владельца забыть, элемент может записаться с пустым владельцем и «потеряется»: в списке договоров конкретного контрагента его не будет. В типовых конфигурациях у договоров есть свои проверки заполнения, но в самописных справочниках за этим следите сами — включите для стандартного реквизита Владелец проверку заполнения в конфигураторе.
Тот же порядок действует при копировании: метод Скопировать() переносит владельца из исходного элемента. Если копия нужна для другого контрагента, перезапишите Владелец до записи.
Если нужен только просмотр, ПолучитьОбъект() избыточен: он читает элемент со всеми табличными частями. Для чтения хватает ссылки или запроса.
Владелец справочника в запросе: разыменование и соединение
В запросе к владельцу обращаются как к обычному полю. Есть два варианта записи.
Разыменование через точку
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Договоры.Ссылка КАК Договор,
| Договоры.Владелец КАК Контрагент,
| Договоры.Владелец.ИНН КАК ИНН
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
| НЕ Договоры.ПометкаУдаления";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Строка(Выборка.Договор) + " — " + Выборка.Контрагент + ", ИНН " + Выборка.ИНН);
КонецЦикла;
Платформа сама построит соединение с таблицей контрагентов. Код короткий и читается легко.
Явное левое соединение
Запрос.Текст =
"ВЫБРАТЬ
| Договоры.Ссылка КАК Договор,
| Контрагенты.Ссылка КАК Контрагент,
| Контрагенты.ИНН КАК ИНН
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
| ПО Договоры.Владелец = Контрагенты.Ссылка";
Явное соединение полезно, когда из таблицы владельца нужно много полей или по ним стоит отбор. План запроса получается предсказуемым, а текст проще отлаживать.
Составной тип владельца
Если владельцев несколько, разыменование Владелец.ИНН заставит платформу соединиться со всеми таблицами-владельцами. На больших базах это тяжело. Сузьте тип явно:
|ВЫБРАТЬ
| Счета.Ссылка,
| ВЫРАЗИТЬ(Счета.Владелец КАК Справочник.Контрагенты).ИНН КАК ИНН
|ИЗ
| Справочник.БанковскиеСчета КАК Счета
|ГДЕ
| Счета.Владелец ССЫЛКА Справочник.Контрагенты
Оператор ССЫЛКА оставит только строки, где владелец — контрагент, а ВЫРАЗИТЬ уберёт лишние соединения.
Обратная задача: все элементы, подчинённые владельцу
Часто нужен не владелец, а наоборот: все договоры конкретного контрагента. Для этого есть три инструмента.
1. Запрос с отбором по владельцу — лучший вариант для списков:
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Договоры.Ссылка
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
| Договоры.Владелец = &Владелец
| И НЕ Договоры.ПометкаУдаления");
Запрос.УстановитьПараметр("Владелец", Контрагент);
МассивДоговоров = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
2. Выборка менеджера справочника. У метода Выбрать() второй параметр — владелец:
Выборка = Справочники.ДоговорыКонтрагентов.Выбрать(, Контрагент);
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
3. Поиск в пределах владельца. Методы НайтиПоНаименованию() и НайтиПоКоду() принимают владельца последним параметром:
Договор = Справочники.ДоговорыКонтрагентов.НайтиПоНаименованию("Основной договор", Истина, , Контрагент);
Без владельца поиск по наименованию вернёт первый попавшийся «Основной договор» любого контрагента. Это частая причина путаницы в обработках загрузки.
Отбор по владельцу в форме
Чтобы в поле «Договор» документа выбирались только договоры выбранного контрагента, в конфигураторе задайте полю свойство «Связи параметров выбора»: параметр Отбор.Владелец, реквизит — Объект.Контрагент. Кода не нужно.
Если список подчинённого справочника открывают из своего кода, отбор передают параметром формы:
ПараметрыФормы = Новый Структура("Отбор", Новый Структура("Владелец", Контрагент));
ОткрытьФорму("Справочник.ДоговорыКонтрагентов.ФормаСписка", ПараметрыФормы);
Как посмотреть владельца справочника в отладчике
Отладчик отвечает на вопрос «что сейчас лежит в реквизите» без правки кода.
- В конфигураторе поставьте точку останова (
F9) на строке, где доступна ссылка или объект. - Запустите отладку (
F5) и выполните действие в режиме 1С:Предприятие. - Когда выполнение остановится, выделите переменную и нажмите
Shift+F9— откроется окно «Выражение». - Введите
Договор.ВладелецилиОбъект.Владелеци нажмите «Рассчитать». - Чтобы узнать точный тип, введите
ТипЗнч(Договор.Владелец).
Через то же окно удобно проверить цепочку целиком: Договор.Владелец.ИНН. Выражение можно добавить в табло, и оно будет пересчитываться на каждом шаге.
Когда ошибка возникает только у одного контрагента из сотни, помогает точка останова с условием. Щёлкните по уже установленной точке правой кнопкой, выберите изменение точки останова и впишите условие, например Выборка.Владелец.ИНН = "7701234567". Выполнение остановится только на нужной строке, а не на каждой итерации цикла.
Если остановка пришлась на клиентскую процедуру, разыменовать ссылку не получится — отладчик вычисляет выражение в том же контексте, что и код. Поставьте точку останова в серверном методе или смотрите Объект.Владелец из данных формы.
Какой способ обращения к владельцу выбрать
| Способ | Когда подходит | Минусы |
|---|---|---|
Ссылка.Владелец | Один элемент, серверный код | Читает весь элемент, медленно в цикле |
ЗначениеРеквизитаОбъекта() (БСП) | Типовая конфигурация, нужно одно-два поля | Есть только при наличии БСП |
ПолучитьОбъект().Владелец | Нужно изменить владельца и записать | Самый тяжёлый: читает табличные части |
Запрос с Владелец | Много элементов, отчёты, отборы | Больше кода |
ВЫРАЗИТЬ + ССЫЛКА | Составной тип владельца | Нужно знать нужный тип заранее |
| Отладчик | Разобраться, что в данных | Не для рабочего кода |
Простое правило: один элемент — ссылка или БСП, коллекция — запрос, изменение — объект.
Пример из практики. Обработка выгружает 5 000 договоров с ИНН контрагентов. Вариант с Договор.Владелец.ИНН в цикле даёт до 5 000 отдельных чтений элементов из базы, вариант с запросом — одно обращение к базе. На файловой базе разница ощущается уже на сотнях строк, на серверной — по нагрузке на СУБД.
Типичные ошибки при работе с владельцем
&НаСервере или &НаСервереБезКонтекста и верните из неё ссылку на владельца. В форме элемента используйте Объект.Владелец.ТипЗнч(Владелец) = Тип("СправочникСсылка.Контрагенты") и ЗначениеЗаполнено(Владелец).НайтиПоНаименованию() не передан владелец. Укажите его четвёртым параметром.Договор.Владелец.Наименование. Перенесите получение данных в один запрос с соединением.Ещё одна ловушка — пометка удаления. Если пометить на удаление владельца, платформа помечает и подчинённые ему элементы. Снятие пометки с контрагента не всегда очевидно для пользователя: договоры могут остаться помеченными, проверьте их отдельно.
Частые вопросы
Можно ли получить владельца владельца?
Элемент.Владелец.Владелец. Каждый уровень — отдельное чтение из базы, поэтому для коллекций лучше запрос.Какой тип у пустого владельца?
ЗначениеЗаполнено(), а не сравнением с Неопределено.Можно ли сделать владельцем документ или регистр?
Как узнать, подчинён ли справочник кому-нибудь, из кода?
Метаданные.Справочники.ИмяСправочника.Владельцы.Количество() > 0. Если больше нуля — у справочника есть реквизит Владелец.