Как прочитать данные справочника из клиентского кода 1С

· 9 мин

Содержание

На клиенте управляемого приложения нет прямого доступа к базе. Объект Запрос там недоступен, а методы менеджера справочника, которые читают базу — Выбрать, НайтиПоКоду, НайтиПоНаименованию — компилируются только на сервере. Поэтому обратиться к справочнику 1С на клиенте всегда означает одно из двух: прочитать реквизит через уже готовую ссылку либо сходить за данными на сервер.

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

Клиент и сервер: почему справочник не читается напрямую

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

На клиенте недоступны:

  • 🔸 объект Новый Запрос — попытка его создать даёт ошибку компиляции «Тип не определён»;
  • 🔸 методы менеджера, читающие базу: Выбрать, ВыбратьИерархически, НайтиПоКоду, НайтиПоНаименованию, НайтиПоРеквизиту;
  • 🔸 метод ПолучитьОбъект() у ссылки.

На клиенте доступны:

  • 🔹 сама ссылка СправочникСсылка и чтение её реквизитов через точку;
  • 🔹 ПустаяСсылка() и сравнение ссылок;
  • 🔹 функция ЗначениеЗаполнено().

Пять способов получить данные справочника на клиенте:

  1. Через реквизиты ссылки — Элемент.Наименование.
  2. Через серверную процедуру с контекстом формы — &НаСервере.
  3. Через внеконтекстный серверный вызов с запросом — &НаСервереБезКонтекста.
  4. Через реквизит формы типа «Динамический список».
  5. Через менеджер справочника внутри серверного метода — НайтиПоКоду и другие.

Способ 1. Чтение реквизитов через ссылку

Если на клиенте уже есть ссылка (выбрана в поле, лежит в реквизите формы, взята из текущих данных списка), к её реквизитам обращаются через точку:

&НаКлиенте
Процедура НоменклатураПриИзменении(Элемент)
    Если ЗначениеЗаполнено(Объект.Номенклатура) Тогда
        Объект.Артикул = Объект.Номенклатура.Артикул;
        Объект.Единица = Объект.Номенклатура.ЕдиницаИзмерения;
    КонецЕсли;
КонецПроцедуры

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

Удобно, пока речь о одном-двух реквизитах одного элемента. Беда начинается в цикле:

Каждое обращение через точку — отдельный скрытый серверный вызов. Цикл по 1000 строк табличной части с чтением Строка.Номенклатура.Артикул внутри — это 1000 походов на сервер. Форма «подвисает» на ровном месте.

Для списков и циклов точечный доступ не подходит — там нужен один запрос (способ 3).

Способ 2. Серверный вызов: директивы компиляции

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

ДирективаГде выполняетсяДоступ к данным формы
&НаКлиентена клиентеда (напрямую)
&НаСерверена сервереда — контекст формы передаётся туда и обратно
&НаСервереБезКонтекстана серверенет — только через параметры
&НаКлиентеНаСервереБезКонтекстаи там, и тамнет

Правило: если серверному методу не нужна вся форма, выбирайте &НаСервереБезКонтекста. Он не гоняет контекст формы по сети, работает быстрее и нагружает сервер меньше. Параметры и возвращаемое значение у него — примитивы и сериализуемые типы (структура, массив, таблица значений, ссылка).

Клиент вызывает серверную функцию и получает готовый результат:

&НаКлиенте
Процедура ЗаполнитьРеквизиты(Команда)
    Данные = ДанныеНоменклатуры(Объект.Номенклатура);
    Объект.Артикул = Данные.Артикул;
    Объект.Единица = Данные.Единица;
КонецПроцедуры

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

Так два реквизита приходят за один серверный вызов вместо двух точечных. ЗаполнитьЗначенияСвойств копирует поля выборки в структуру по совпадению имён.

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

Способ 3. Запрос и временные таблицы для выборки

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

Временные таблицы (ПОМЕСТИТЬ, менеджер временных таблиц) нужны, когда выборка многоступенчатая: сначала отобрать ссылки, сложить их во временную таблицу, затем соединить с остатками, ценами или другими данными. На клиент временная таблица не передаётся — туда уходит только финальный результат.

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Номенклатура
ПОМЕСТИТЬ ВтНоменклатура
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.ВидНоменклатуры = &Вид
    И НЕ Номенклатура.ПометкаУдаления
;
ВЫБРАТЬ
    ВтНоменклатура.Номенклатура КАК Номенклатура,
    Номенклатура.Артикул КАК Артикул
ИЗ
    ВтНоменклатура КАК ВтНоменклатура
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
    ПО Номенклатура.Ссылка = ВтНоменклатура.Номенклатура

Пакетный запрос выполняют и выгружают нужный результат в таблицу значений:

Результат = Запрос.ВыполнитьПакет()[1].Выгрузить();

Полученную таблицу функция возвращает на клиент либо сразу складывает в реквизит формы типа «Таблица значений» (тогда директива будет &НаСервере, чтобы был доступ к данным формы). Один вызов — весь список на руках.

Частая задача — прочитать по одному реквизиту для набора ссылок, которые уже есть на клиенте (например, артикулы для всех строк табличной части). Передайте массив ссылок параметром в серверную функцию и верните соответствие «ссылка → значение»:

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

Так данные для всей табличной части приходят одним запросом вместо десятков точечных обращений. В конфигурации с Библиотекой стандартных подсистем ту же задачу решает ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивСсылок, "Артикул") — метод во множественном числе принимает массив и возвращает соответствие.

Способ 4. Поиск элемента через менеджер справочника

Внутри серверного метода доступен менеджер справочника — через него ищут конкретный элемент, когда ссылки ещё нет, а есть код или значение реквизита:

&НаСервереБезКонтекста
Функция НайтиКонтрагента(ИНН)
    Ссылка = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ИНН);
    Возврат Ссылка;
КонецФункции

Основные методы менеджера:

  • 🔹 НайтиПоКоду("00-0001") — по коду элемента;
  • 🔹 НайтиПоНаименованию("ООО Ромашка", Истина) — по наименованию (второй параметр Истина требует точного совпадения);
  • 🔹 НайтиПоРеквизиту("ИНН", "7701234567") — по значению реквизита.

Все три возвращают ссылку либо пустую ссылку, если ничего не нашлось. Результат проверяют через ЗначениеЗаполнено.

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

Метод Выбрать() перебирает весь справочник целиком — на больших справочниках это дорого, и в прикладном коде его почти всегда заменяют запросом с условием.

Способ 5. Динамический список и расширения формы

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

  • основная таблица — Справочник.Номенклатура (список элементов «как есть»);
  • произвольный запрос — когда нужны соединения, вычисляемые поля или особый порядок.

Отбор и параметры динамического списка меняют программно. Например, передать значение параметра запроса:

&НаСервере
Процедура УстановитьПериод(ПериодОстатков)
    Список.Параметры.УстановитьЗначениеПараметра("Период", ПериодОстатков);
КонецПроцедуры

Отдельно стоит способ получить ссылку на элемент прямо от пользователя — через форму выбора. Когда поле связано со справочником, выбранное значение приходит в обработчик ПриИзменении уже как готовая ссылка на клиенте, и дальше с ней работают через точку. Программно форму выбора открывают методом ОткрытьФорму с указанием имени формы выбора справочника, а результат ловят в обработчике оповещения. Это клиентский путь, когда ссылки заранее нет и её выбирает человек.

Расширения конфигурации (появились в платформе 8.3.6) позволяют добавить на типовую форму справочника свой реквизит, команду или обработчик, не снимая конфигурацию с поддержки. В расширении пишут ту же клиент-серверную связку: обработчик на клиенте вызывает серверный метод, тот читает справочник запросом. Это основной способ доработать типовую форму, когда менять саму конфигурацию нельзя.

Иерархические справочники: родители и группы

У иерархического справочника два варианта устройства:

  • иерархия групп и элементов — есть папки-группы, у элемента свойство ЭтоГруппа;
  • иерархия элементов — родителем выступает обычный элемент, групп нет.

Родитель читается через реквизит Родитель — это ссылка на элемент уровнем выше или пустая ссылка для верхнего уровня. На клиенте её берут через точку: Элемент.Родитель.

Чтобы отобрать все элементы внутри группы вместе с вложенными подгруппами, в запросе на сервере применяют оператор В ИЕРАРХИИ:

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    Номенклатура.Наименование КАК Наименование
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Ссылка В ИЕРАРХИИ(&Группа)
    И НЕ Номенклатура.ЭтоГруппа

Параметр &Группа — ссылка на родительскую группу. Условие НЕ ЭтоГруппа отсекает сами папки, если нужны только конечные элементы. Для построения дерева на форме результат такого запроса выгружают в дерево значений либо используют динамический список с включённой иерархией.

Оптимизация обращений и частые ошибки

Главная цена при работе со справочником на клиенте — число походов на сервер. Отсюда и ошибки.

Чего избегать:

  • ❌ чтение реквизитов через точку в цикле — каждый . превращается в отдельный серверный вызов;
  • ❌ &НаСервере там, где хватает &НаСервереБезКонтекста — лишняя передача контекста формы;
  • ❌ Выбрать() по всему справочнику вместо запроса с условием;
  • ❌ попытка написать Новый Запрос в клиентской процедуре.

Что делать вместо этого:

  • ✅ собрать всё нужное в одной серверной функции и вернуть структурой или таблицей за один вызов;
  • ✅ в циклах и списках работать запросом, а не точечным доступом к реквизитам;
  • ✅ для редко меняющихся справочников (валюты, ставки, виды) кэшировать результат — общий модуль со свойством «Повторное использование возвращаемых значений» на время вызова или сеанса сократит повторные запросы.

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

Какой способ под какую задачу — коротко в виджете ниже.

Какой способ обращения к справочнику выбрать?

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

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