Менеджеры объектов в 1С 8.3: как получить ссылку и данные
Содержание
Менеджер объекта в 1С — это точка входа ко всем элементам справочника, ко всем документам одного вида или записям регистра. Чтобы обратиться к объекту через менеджер, пишете Справочники.Номенклатура.НайтиПоКоду("00-000123") или Документы.РеализацияТоваровУслуг.СоздатьДокумент(). Имя менеджера совпадает с именем объекта метаданных: у справочника — через Справочники, у документа — через Документы, и так далее.
Типичная ситуация: в обработке нужно найти контрагента, создать на него документ и провести. Всё это — работа через менеджеры. Сначала получаете менеджер справочника и находите ссылку, потом получаете менеджер документа и создаёте объект. Ниже — как обратиться к объекту разными способами: прямой поиск, выборка, менеджеры документов и регистров, динамическое получение менеджера по имени типа. И где такое обращение начинает тормозить большую базу.
Менеджер, ссылка и объект — три разные сущности
Половина ошибок новичков в том, что Справочники.Номенклатура путают с самим элементом справочника. Это разные вещи.
- 🧭 Менеджер (
СправочникМенеджер.Номенклатура) — один на весь вид. Данных конкретного элемента в нём нет. Он умеет искать, создавать, выбирать элементы и выдавать макеты. - 🔗 Ссылка (
СправочникСсылка.Номенклатура) — указатель на конкретный элемент. Через ссылку читают реквизиты (Ссылка.Наименование), но изменить их напрямую нельзя. - 📦 Объект (
СправочникОбъект.Номенклатура) — загруженный в память элемент. Его реквизиты можно менять и сохранять методомЗаписать().
| Сущность | Как получить | Что умеет |
|---|---|---|
| Менеджер | Справочники.Контрагенты | найти, создать, выбрать, макеты |
| Ссылка | Менеджер.НайтиПоКоду(Код) | адресует элемент, читает реквизиты |
| Объект | Ссылка.ПолучитьОбъект() | изменение реквизитов + Записать() |
Запомните цепочку: от менеджера получаете ссылку, от ссылки — объект. В обратную сторону: у объекта есть свойство Ссылка, а у ссылки — метод ПолучитьОбъект().
Три способа получить менеджер нужного объекта
Менеджер достаётся из глобального контекста. Есть три приёма — от самого частого к универсальному.
1. Напрямую по имени вида. Самый читаемый вариант:
МенеджерТоваров = Справочники.Номенклатура;
МенеджерРеализаций = Документы.РеализацияТоваровУслуг;
МенеджерЦен = РегистрыСведений.ЦеныНоменклатуры;
МенеджерОстатков = РегистрыНакопления.ТоварыНаСкладах;
МенеджерСтавок = Перечисления.СтавкиНДС;
2. По строке с именем — через квадратные скобки. Нужен, когда имя вида известно только во время выполнения (пришло параметром, лежит в настройке):
ИмяСправочника = "Номенклатура";
Менеджер = Справочники[ИмяСправочника];
Ссылка = Менеджер.НайтиПоКоду("00-000123");
3. От уже имеющейся ссылки. Если на руках ссылка, а менеджер её вида заранее неизвестен:
Менеджер = Справочники[Ссылка.Метаданные().Имя];
Метод Метаданные() возвращает объект описания метаданных, а его свойство Имя — короткое имя вида (Номенклатура, Контрагенты). Этим именем и адресуем нужный менеджер.
Как найти конкретный объект через менеджер
У менеджера справочника есть готовые методы поиска. Все они возвращают ссылку, а если ничего не нашли — пустую ссылку, не Неопределено и не ошибку.
НайтиПоКоду(Код)— ищет по коду, использует индекс.НайтиПоНаименованию(Наименование)— по наименованию; наименование не уникально, вернётся первый подходящий.НайтиПоРеквизиту(Имя, Значение)— по значению реквизита; работает только для индексируемых непериодических реквизитов.
Результат всегда проверяйте методом Пустая():
СсылкаТовара = Справочники.Номенклатура.НайтиПоКоду("00-000123");
Если СсылкаТовара.Пустая() Тогда
Сообщить("Товар с таким кодом не найден");
Возврат;
КонецЕсли;
Сообщить("Нашли: " + СсылкаТовара.Наименование);
Когда нужно перебрать все элементы, берут выборку методом Выбрать(). Она читает элементы из базы по одному, не загружая весь справочник в память:
Выборка = Справочники.Номенклатура.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Код + " — " + Выборка.Наименование);
КонецЦикла;
Для иерархических справочников есть ВыбратьИерархически() — обход с учётом групп. Пустую ссылку нужного вида даёт метод ПустаяСсылка() — удобно для присваивания реквизиту «ничего».
НайтиПоНаименованию возвращает первый найденный элемент. Если в базе два товара с одинаковым наименованием, можно молча взять не тот. Для однозначного поиска используйте код, артикул или запрос с дополнительным отбором.
Менеджер документа: создание, проведение, поиск по номеру
Менеджер документа отличается от справочника набором методов. Новый документ создают через СоздатьДокумент(), затем заполняют реквизиты и записывают:
Док = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
Док.Дата = ТекущаяДата();
Док.Организация = Организация;
Док.Контрагент = СсылкаКонтрагента;
Док.Записать(РежимЗаписиДокумента.Проведение);
Режим записи выбирают явно: РежимЗаписиДокумента.Запись — просто сохранить, РежимЗаписиДокумента.Проведение — сохранить и провести, РежимЗаписиДокумента.ОтменаПроведения — распровести.
Найти существующий документ по номеру можно методом НайтиПоНомеру(Номер, Дата). Дата нужна, потому что у документов нумерация обычно периодическая и номер уникален только внутри периода:
СсылкаДок = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("0000-000123", Дата);
Подводные камни документов:
- ✅ Номер присваивается только при
Записать(), а не в моментСоздатьДокумент(). До записи полеНомерпустое. - ✅ Проведение может завершиться с отказом (не хватило остатков, не заполнен обязательный реквизит). Оборачивайте запись в
Попытка … Исключение … КонецПопыткии разбирайте причину.
Создание и проведение документа в рабочей базе меняет движения по регистрам. Отлаживайте такой код на копии базы: лишний проведённый документ задвоит остатки и обороты, а потом его придётся искать и помечать на удаление.
Как это складывается в законченную задачу — найти контрагента и выписать на него документ:
// Контрагент по ИНН (реквизит ИНН должен быть индексируемым)
СсылкаКонтрагента = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ИНН);
Если СсылкаКонтрагента.Пустая() Тогда
Сообщить("Контрагент с ИНН " + ИНН + " не найден");
Возврат;
КонецЕсли;
Док = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
Док.Дата = ТекущаяДата();
Док.Организация = Организация;
Док.Контрагент = СсылкаКонтрагента;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Сообщить("Документ не проведён: " + ОписаниеОшибки());
КонецПопытки;
Здесь в одном коротком сценарии задействованы два менеджера: сначала менеджер справочника находит ссылку, затем менеджер документа создаёт и проводит объект. Проведение обёрнуто в Попытка, потому что оно может завершиться отказом.
Менеджеры регистров: данные без перебора элементов
У регистров объектов-элементов нет, поэтому и методы другие. Менеджер регистра отдаёт готовые выборки и срезы.
Регистр сведений
— последнее значение на дату через ПолучитьПоследнее(Период, Отбор). Отбор задают структурой по измерениям:
Отбор = Новый Структура("Номенклатура", СсылкаТовара);
Запись = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(ТекущаяДата(), Отбор);
Цена = Запись.Цена;
Для непериодического регистра есть Получить(Отбор), а для набора записей — СоздатьНаборЗаписей().
Регистр накопления отдаёт итоги методами Остатки(Период, Отбор) и Обороты(…) — они возвращают таблицу значений с ресурсами:
Отбор = Новый Структура("Номенклатура", СсылкаТовара);
ТаблицаОстатков = РегистрыНакопления.ТоварыНаСкладах.Остатки(ТекущаяДата(), Отбор);
Записи регистра меняют не по одной, а набором. Менеджер отдаёт набор методом СоздатьНаборЗаписей(): ему задают отбор, читают методом Прочитать(), правят строки и сохраняют Записать(). Для регистра сведений с режимом «Независимый» доступен ещё СоздатьМенеджерЗаписи() — работа с одной конкретной записью по значениям измерений.
Для разовой задачи методы менеджера удобны. Но когда данных много, корректнее и быстрее собрать их запросом к виртуальной таблице ТоварыНаСкладах.Остатки — об этом ниже.
Как не повесить большую базу: оптимизация обращений
Главный источник тормозов — обращение к базе внутри цикла. Пара приёмов, которые отличают рабочий код от учебного.
- 🚫 Не вызывайте
НайтиПоНаименованиюиПолучитьОбъект()в цикле на тысячах строк. Каждый вызов — отдельное обращение к базе. На 10 000 строк это 10 000 запросов. - 🚫 Не читайте реквизиты через точку в цикле выборки, если их много. Каждое
Выборка.Ссылка.Реквизит— обращение к базе за этим реквизитом. Соберите нужные поля одним запросом. - ✅ Для выборки по условию используйте запрос, а не
Выбрать()с проверкой в цикле. Фильтрация на стороне СУБД всегда дешевле перебора на встроенном языке. - ✅
ПолучитьОбъект()вызывайте только чтобы менять данные. Он грузит весь элемент целиком и ставит объектную блокировку. Чтобы просто прочитать реквизит, хватит ссылки или запроса.
НайтиПоКоду и НайтиПоНаименованию работают по индексу и сами по себе быстрые. Проблема не в них, а в количестве вызовов.
Пример замены цикла поисков на один запрос. Было: перебор строк таблицы и НайтиПоКоду на каждую. Стало — единое обращение к базе, которое сразу вернёт коды и ссылки:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка, Код, Наименование
|ИЗ Справочник.Номенклатура
|ГДЕ Код В (&СписокКодов)";
Запрос.УстановитьПараметр("СписокКодов", СписокКодов);
Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
// Результат.Ссылка — та же ссылка, что вернул бы НайтиПоКоду
КонецЦикла;
Запрос переносит фильтрацию и чтение полей на сторону СУБД: вместо тысячи обращений — одно. На выборках от нескольких сотен строк разница в скорости заметна сразу.
Частые ошибки при работе с менеджерами
Ошибки здесь однотипные и легко узнаются по тексту сообщения платформы.
Отдельно про клиент и сервер: читать реквизиты через ссылку можно и на клиенте (платформа кэширует значения), а вот получать объект, записывать и проводить — только на сервере. Это частая причина необъяснимых на первый взгляд ошибок при переносе кода из обычного приложения в управляемое.
Динамическое обращение к объекту по имени типа
Когда вид объекта известен только во время выполнения, пригодится универсальная функция. Она принимает имя справочника строкой и возвращает ссылку:
Функция НайтиСсылкуПоКоду(ИмяСправочника, Код) Экспорт
Менеджер = Справочники[ИмяСправочника];
Возврат Менеджер.НайтиПоКоду(Код);
КонецФункции
Так пишут обработки загрузки данных, где вид справочника приходит из файла, и универсальные механизмы обмена.
Рядом стоят ещё два приёма динамического обращения:
- Предопределённый элемент по полному имени:
ПредопределенноеЗначение("Справочник.Валюты.Рубль")илиПредопределенноеЗначение("Перечисление.СтавкиНДС.НДС20"). Возвращает ссылку, не обращаясь к менеджеру напрямую. - Пустая ссылка по типу:
Тип("СправочникСсылка.Номенклатура")даёт тип, по которому можно построить пустую ссылку или проверить принадлежность значения виду.
Квадратные скобки работают для всех менеджеров: Документы[ИмяДокумента], РегистрыСведений[ИмяРегистра], Перечисления[ИмяПеречисления]. Имя берите из метаданных (Метаданные().Имя), а не из синонима — синоним это отображаемое название, оно не подходит для обращения.
Частые вопросы
Чем менеджер отличается от ссылки?
Как из менеджера получить объект для редактирования?
НайтиПоКоду, НайтиПоНаименованию), затем у ссылки вызываете ПолучитьОбъект(). Полученный объект меняете и сохраняете методом Записать().Почему НайтиПоНаименованию возвращает не тот элемент?
Можно ли обратиться к менеджеру по строке с именем вида?
Справочники["Контрагенты"] или Справочники[ИмяПеременной]. Имя берите короткое, как в метаданных, а не синоним.