При разработке функциональности в 1С часто возникает задача получить список пользователей, обладающих определенной ролью, и, возможно, их контактную информацию (например, адрес электронной почты) для рассылки сообщений или анализа. Рассмотрим подробно, как решить эту задачу, начиная с базового подхода и постепенно улучшая код с учетом производительности и лучших практик.
Изначально разработчик предложил следующий код, который мы проанализируем и улучшим. Этот код использует прямой запрос к табличной части контактной информации справочника "Пользователи" и поиск пользователей информационной базы по их наименованию.
Функция ПолучитьВсехПользователей(ИмяРоли)
Таб = Новый ТаблицаЗначений;
Таб.Колонки.Добавить("Пользователь");
Таб.Колонки.Добавить("Почта");
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка,
| Пользователи.Представление КАК Почта
|ИЗ
| Справочник.Пользователи.КонтактнаяИнформация КАК Пользователи
|ГДЕ
| Пользователи.Ссылка.ПометкаУдаления = ЛОЖЬ
| И Пользователи.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты)";
Результат = Запрос.Выполнить;
Выборка = Результат.Выбрать;
Пока Выборка.Следующий Цикл
ПользовательИмя = Выборка.Ссылка.Наименование;
ПользовательИБ = ПользователиИнформационнойБазы.НайтиПоИмени(ПользовательИмя);
Если НЕ ПользовательИБ = Неопределено И ПользовательИБ.Роли.Содержит(Метаданные.Роли[ИмяРоли]) Тогда
стр = Таб.Добавить;
стр.Пользователь = Выборка.Ссылка;
стр.Почта= Выборка.Почта;
КонецЕсли;
КонецЦикла;
Возврат Таб;
КонецФункции
В данном коде есть несколько моментов, требующих улучшения:
Справочник.Пользователи.КонтактнаяИнформация КАК Пользователи, где затем используются поля Пользователи.Ссылка, Пользователи.Представление, не является стандартным способом работы с табличной частью. Более корректным будет использование ЛЕВОГО СОЕДИНЕНИЯ.ПользователиИнформационнойБазы.НайтиПоИмени() является устаревшим и менее производительным по сравнению с поиском по уникальному идентификатору. Наименование пользователя может быть изменено, что приведет к ошибкам в поиске, тогда как уникальный идентификатор остается неизменным.ПользовательИБ.Роли.Содержит(Метаданные.Роли[ИмяРоли]) происходит постоянное обращение к коллекции метаданных, что может замедлить выполнение, особенно при большом количестве пользователей.Для начала, мы исправим способ получения контактной информации и добавим параметр, который позволит управлять необходимостью получения почты. Это позволит сделать функцию более гибкой.
Мы используем ЛЕВОЕ СОЕДИНЕНИЕ для таблицы контактной информации, что гарантирует получение всех пользователей, даже если у них нет записи об электронной почте. Также добавим колонки "ЕстьПочта" для удобства обработки.
Функция ПолучитьВсехПользователей(ИмяРоли, Знач СПочтой = Ложь) // Пример: ПолучитьВсехПользователей("ПолныеПрава", Истина);
// РольВМетаданных = Метаданные.Роли[ИмяРоли]; // Эта оптимизация будет добавлена позднее
Таб = Новый ТаблицаЗначений;
Таб.Колонки.Добавить("Пользователь");
Если СПочтой Тогда
Таб.Колонки.Добавить("Почта");
Таб.Колонки.Добавить("ЕстьПочта");
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка,
| Пользователи.ИдентификаторПользователяИБ КАК ИдентификаторПользователяИБ,
| КИ.Представление КАК Почта
|ИЗ
| Справочник.Пользователи КАК Пользователи
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи.КонтактнаяИнформация КАК КИ
| ПО Пользователи.Ссылка = КИ.Ссылка
| И (КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
|ГДЕ
| Пользователи.ПометкаУдаления = ЛОЖЬ";
Выборка = Запрос.Выполнить.Выбрать;
Пока Выборка.Следующий Цикл
// Пока используем старый поиск по наименованию, но скоро исправим
// ПользовательИмя = Выборка.Ссылка.Наименование; // Старый подход
// ПользовательИБ = ПользователиИнформационнойБазы.НайтиПоИмени(ПользовательИмя); // Старый подход
// В этом сообщении уже применяется ИдентификаторПользователяИБ, но без его приведения к типу УникальныйИдентификатор
ПользовательИБ = ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Выборка.ИдентификаторПользователяИБ);
Если ПользовательИБ <> Неопределено И ПользовательИБ.Роли.Содержит(Метаданные.Роли[ИмяРоли]) Тогда
стр = Таб.Добавить;
стр.Пользователь = Выборка.Ссылка;
Если СПочтой Тогда
стр.Почта = Выборка.Почта;
Если ЗначениеЗаполнено(Выборка.Почта) Тогда
стр.ЕстьПочта = Истина;
Иначе
стр.ЕстьПочта = Ложь;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Таб;
КонецФункции
Здесь мы видим следующие изменения:
ЛЕВОЕ СОЕДИНЕНИЕ для контактной информации. Это позволяет получить всех пользователей, даже если у них нет почты, и при этом в колонке Почта будет NULL (Неопределено).ЕстьПочта, которая будет указывать на наличие адреса электронной почты у пользователя.Пользователи.ИдентификаторПользователяИБ, что является важным шагом к более надежному поиску пользователя ИБ.ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Выборка.ИдентификаторПользователяИБ) для поиска пользователя ИБ, что гораздо предпочтительнее поиска по имени.Следующим шагом мы сосредоточимся на двух важных оптимизациях, которые значительно повысят производительность нашего кода.
Как уже было отмечено, поиск пользователя информационной базы по его наименованию через ПользователиИнформационнойБазы.НайтиПоИмени() является неоптимальным и ненадежным. Вместо этого, мы будем использовать метод ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(), который более производителен и гарантирует корректное сопоставление, поскольку уникальный идентификатор пользователя ИБ не изменяется.
Для этого нам необходимо добавить поле Пользователи.ИдентификаторПользователяИБ в запрос, а затем использовать его в цикле.
Обращение к коллекции метаданных Метаданные.Роли[ИмяРоли] внутри каждого шага цикла является избыточным. Объект роли не меняется на протяжении выполнения функции, поэтому мы можем получить его один раз до начала цикла и сохранить в переменную. Это позволит избежать повторных операций поиска в метаданных.
С учетом этих оптимизаций, наш код будет выглядеть так:
Функция ПолучитьВсехПользователей(ИмяРоли, Знач СПочтой = Ложь) // Пример: ПолучитьВсехПользователей("ПолныеПрава", Истина);
РольВМетаданных = Метаданные.Роли[ИмяРоли]; // Объект роли кэшируется один раз до цикла
Таб = Новый ТаблицаЗначений;
Таб.Колонки.Добавить("Пользователь");
Если СПочтой Тогда
Таб.Колонки.Добавить("Почта");
Таб.Колонки.Добавить("ЕстьПочта");
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка,
| Пользователи.ИдентификаторПользователяИБ КАК ИдентификаторПользователяИБ,
| КИ.Представление КАК Почта
|ИЗ
| Справочник.Пользователи КАК Пользователи
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи.КонтактнаяИнформация КАК КИ
| ПО Пользователи.Ссылка = КИ.Ссылка
| И (КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
|ГДЕ
| Пользователи.ПометкаУдаления = ЛОЖЬ";
Выборка = Запрос.Выполнить.Выбрать;
Пока Выборка.Следующий Цикл
ПользовательИБ = ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Выборка.ИдентификаторПользователяИБ);
Если ПользовательИБ <> Неопределено И ПользовательИБ.Роли.Содержит(РольВМетаданных) Тогда // Используем кэшированный объект роли
стр = Таб.Добавить;
стр.Пользователь = Выборка.Ссылка;
Если СПочтой Тогда
стр.Почта = Выборка.Почта;
Если ЗначениеЗаполнено(Выборка.Почта) Тогда
стр.ЕстьПочта = Истина;
Иначе
стр.ЕстьПочта = Ложь;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Таб;
КонецФункции
Эти изменения значительно улучшают производительность и надежность функции.
Мы продолжаем улучшать наш код. Нам необходимо учесть, что поле ИдентификаторПользователяИБ возвращает строковое представление уникального идентификатора. Для метода НайтиПоУникальномуИдентификатору() требуется именно объект типа УникальныйИдентификатор. Также упростим логику заполнения поля "ЕстьПочта".
ИдентификаторПользователяИБПоле Выборка.ИдентификаторПользователяИБ возвращает значение типа строка. Чтобы корректно использовать его в методе ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(), мы должны явно преобразовать его в тип УникальныйИдентификатор с помощью конструктора Новый УникальныйИдентификатор(). Это критически важное исправление, которое гарантирует правильную работу метода поиска.
Проверка на заполненность значения и присвоение булевого результата можно сократить до одной строки, что делает код более лаконичным и читаемым.
Функция ПолучитьВсехПользователей(ИмяРоли, Знач СПочтой = Ложь)
РольВМетаданных = Метаданные.Роли[ИмяРоли];
Таб = Новый ТаблицаЗначений;
Таб.Колонки.Добавить("Пользователь");
Если СПочтой Тогда
Таб.Колонки.Добавить("Почта");
Таб.Колонки.Добавить("ЕстьПочта");
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка,
| Пользователи.ИдентификаторПользователяИБ КАК ИдентификаторПользователяИБ,
| КИ.Представление КАК Почта
|ИЗ
| Справочник.Пользователи КАК Пользователи
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи.КонтактнаяИнформация КАК КИ
| ПО Пользователи.Ссылка = КИ.Ссылка
| И (КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
|ГДЕ
| Пользователи.ПометкаУдаления = ЛОЖЬ";
Выборка = Запрос.Выполнить.Выбрать;
Пока Выборка.Следующий Цикл
// Приведение строки к типу УникальныйИдентификатор
ПользовательИБ = ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Новый УникальныйИдентификатор(Выборка.ИдентификаторПользователяИБ));
// Проверка на Неопределено может быть более лаконичной
Если ПользовательИБ <> Неопределено И ПользовательИБ.Роли.Содержит(РольВМетаданных) Тогда
стр = Таб.Добавить;
стр.Пользователь = Выборка.Ссылка;
Если СПочтой Тогда
стр.Почта = Выборка.Почта;
// Упрощенная логика заполнения ЕстьПочта
стр.ЕстьПочта = ЗначениеЗаполнено(Выборка.Почта);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Таб;
КонецФункции
Мы добились максимально оптимизированного и корректного кода на основе обсуждений на форуме. Теперь рассмотрим дополнительные возможности для дальнейшей оптимизации и лучшие практики.
Для дальнейшего улучшения производительности и соблюдения стандартов разработки в 1С, рассмотрим несколько дополнительных рекомендаций.
Если в большинстве случаев почта не требуется, а количество пользователей велико, мы можем разделить логику на два отдельных запроса: один без соединения с контактной информацией (когда СПочтой = Ложь) и один с соединением (когда СПочтой = Истина). Это позволит избежать лишней работы с базой данных в случаях, когда контактные данные не нужны, уменьшая объем обрабатываемых данных.
Функция ПолучитьВсехПользователей(ИмяРоли, Знач СПочтой = Ложь)
РольВМетаданных = Метаданные.Роли[ИмяРоли];
Таб = Новый ТаблицаЗначений;
Таб.Колонки.Добавить("Пользователь");
Если СПочтой Тогда
Таб.Колонки.Добавить("Почта");
Таб.Колонки.Добавить("ЕстьПочта");
КонецЕсли;
Запрос = Новый Запрос;
Если НЕ СПочтой Тогда
Запрос.Текст = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка,
| Пользователи.ИдентификаторПользователяИБ КАК ИдентификаторПользователяИБ
|ИЗ
| Справочник.Пользователи КАК Пользователи
|ГДЕ
| Пользователи.ПометкаУдаления = ЛОЖЬ";
Иначе // СПочтой = Истина
Запрос.Текст = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка,
| Пользователи.ИдентификаторПользователяИБ КАК ИдентификаторПользователяИБ,
| КИ.Представление КАК Почта
|ИЗ
| Справочник.Пользователи КАК Пользователи
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи.КонтактнаяИнформация КАК КИ
| ПО Пользователи.Ссылка = КИ.Ссылка
| И (КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
|ГДЕ
| Пользователи.ПометкаУдаления = ЛОЖЬ";
КонецЕсли;
Выборка = Запрос.Выполнить.Выбрать;
Пока Выборка.Следующий Цикл
ПользовательИБ = ПользователиИнформационнойБазы.НайтиПоУникальномуИдентификатору(Новый УникальныйИдентификатор(Выборка.ИдентификаторПользователяИБ));
Если ПользовательИБ <> Неопределено И ПользовательИБ.Роли.Содержит(РольВМетаданных) Тогда
стр = Таб.Добавить;
стр.Пользователь = Выборка.Ссылка;
Если СПочтой Тогда
стр.Почта = Выборка.Почта;
стр.ЕстьПочта = ЗначениеЗаполнено(Выборка.Почта);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Таб;
КонецФункции
Этот вариант является наиболее полным и оптимизированным из всех рассмотренных, учитывая все предложения и исправления.
ЛЕВОЕ СОЕДИНЕНИЕ и ВНУТРЕННЕЕ СОЕДИНЕНИЕМы использовали ЛЕВОЕ СОЕДИНЕНИЕ, так как задача подразумевает получение *всех* пользователей с ролью, независимо от наличия у них почты. Если бы требовались *только* пользователи, у которых есть почта (и роль), тогда можно было бы использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ для таблицы контактной информации. Это уменьшило бы количество строк в выборке и потенциально повысило производительность. Однако, такой подход изменил бы изначальный смысл функции.
В конфигурациях, разработанных на базе БСП (как УТ 11.4), существуют готовые механизмы для работы с пользователями и контактной информацией, которые следует предпочесть ручной реализации, если это возможно. БСП предоставляет унифицированные и оптимизированные решения.
ПользователиСлужебныйПовтИсп.РолиДоступны()) или получения ролей для объектов адресации. Однако, для общей задачи получения списка пользователей информационной базы метод ПользователиИнформационнойБазы.ПолучитьПользователей() остается основным.УправлениеКонтактнойИнформацией. Функция КонтактнаяИнформацияОбъекта() может возвращать таблицу значений с полной информацией об объекте. Использование БСП для работы с контактными данными помогает стандартизировать подход и избежать ошибок. В конфигурациях на БСП для хранения контактной информации в справочник или документ добавляется табличная часть "КонтактнаяИнформация" с полями "Тип" (Перечисление.ТипыКонтактнойИнформации) и "Вид" (Справочник.ВидыКонтактнойИнформации).Пример использования БСП для получения всех пользователей ИБ (с последующей фильтрацией по ролям и получением КИ):
// Пример получения всех пользователей ИБ
МассивПользователейИБ = ПользователиИнформационнойБазы.ПолучитьПользователей();
Для Каждого ПользовательИБ Из МассивПользователейИБ Цикл
// ... здесь можно проверять роли и получать контактную информацию через БСП
Если ПользовательИБ.Роли.Содержит(РольВМетаданных) Тогда
// Для получения контактной информации через БСП:
// ТаблицаКонтактнойИнформации = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(ПользовательИБ.Ссылка);
// ... и далее обработка таблицы
КонецЕсли;
КонецЦикла;
Использование механизмов БСП, если они применимы к вашей задаче, является предпочтительным, так как они протестированы, оптимизированы и поддерживают единую архитектуру конфигурации.
Мы прошли путь от базового кода до максимально оптимизированного и корректного решения для получения списка пользователей с определенной ролью и их адресов электронной почты. Мы рассмотрели критически важные аспекты производительности, такие как правильный поиск пользователей информационной базы по уникальному идентификатору, кэширование метаданных и оптимизация запросов. Также мы затронули возможность использования стандартных механизмов БСП для более унифицированного подхода.
Всегда помните о необходимости тестирования производительности ваших запросов и обходов, особенно на больших объемах данных, используя такие инструменты, как замер производительности или технологический журнал 1С.