Универсальные функции получения значений реквизитов объектов (8.2+)

Довольно часто в алгоритмах приходится обращаться к значениям реквизитов различных объектов "через точку". При этом, если объект ссылочного типа, то возможно многократное обращение к БД. В данной статье попробуем унифицировать и оптимизировать этот процесс.

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

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

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

Определимся с параметрами:

  1. Объект — объект, данные которого необходимо получить;
  2. ИменаРеквизитов — Список имен реквизитов, данные которых необходимо получить. Если спиок не указан, то предполагаем, что нужно получить данные всех реквизитов;
  3. ДополнительныеРеквизиты — Структура, с помошью которой можно было бы описать дополнительные данные, которые нужно получить вместе с реквизитами объекта (без использования явных соединений с другими таблицами) или выполнение каких-то действий с полями выборки на языке запросов. В ключе элемента структуры описываем имя реквизита в общем списке реквизитов объекта, в значении — поле выборки или алгоритм обработки полей выборки на языке запросов.

Функция ДанныеРеквизитовОбъекта(Объект, ИменаРеквизитов = Неопределено,
                               
ДополнительныеРеквизиты = Неопределено) Экспорт

Разберем, значения каких реквизитов необходимо получить из базы и приведем список имен к типу данных Массив.

    СтруктураОбъекта = Новый Структура;
   
МетаданныеОбъекта = Объект.Метаданные();

    Если ИменаРеквизитов = Неопределено тогда
       
МассивИменРеквизитов = МассивИменРеквизитовОбъекта(Объект);
    иначе
        Если
ТипЗнч(ИменаРеквизитов) = Тип(«Массив») тогда
           
МассивИменРеквизитов = ИменаРеквизитов;
        иначеЕсли
ТипЗнч(ИменаРеквизитов) = Тип(«Строка») тогда
           
МассивИменРеквизитов = МассивПодстрокИзСтроки(ИменаРеквизитов);
        КонецЕсли;
    КонецЕсли;

Если список имен не задан, то с помощью функции МассивИменРеквизитовОбъекта() получим массив имен всех реквизитов объекта (алгоритм этой функции смотри ниже).

Если список имен задан в виде строки разделенной запятыми, то разложим ее на подстроки с помошью функции МассивПодстрокИзСтроки(). Это не сложная задача, алгоритм этой функции разбирать не будем.

Далее с помошью функции ЭтоСсылка() определим, является ли объект ссылочным типом. Для ссылочных типов значения необходимо получить из БД, в противном случае значения реквизитов хранятся в памяти.

    ЭтоСсылка = ЭтоСсылка(Объект);
   
СсылкаОбъекта = ?(ЭтоСсылка, Объект, Объект.Ссылка);

    ПолучитьДанныеИзОбъекта = НЕ ЭтоСсылка;
    Если
ЭтоСсылка И СсылкаОбъекта.Пустая() тогда
       
// это пустая ссылка (данных в базе нет)
       
ПолучитьДанныеИзОбъекта = Истина;
    КонецЕсли;

Теперь, если данные объекта хранятся в памяти — мы можем их сразу получить, в противном случае необходио подготовиться для составления запроса к БД (переведем список имен реквизитов из типа данных Массив в тип Структура).

    ОсновныеРеквизиты = Новый Структура;
    Для каждого
ИмяРеквизита Из МассивИменРеквизитов Цикл
        Если
ПолучитьДанныеИзОбъекта тогда
           
СтруктураОбъекта.Вставить(ИмяРеквизита, Объект[ИмяРеквизита]);
        иначе
           
ОсновныеРеквизиты.Вставить(ИмяРеквизита);
        КонецЕсли;
    КонецЦикла;

Все готово к составлению запроса чтобы получить значения реквизитов если перед нами объект ссылочного типа, а так же для получения дополнительных реквизитов, описанных в 3-ем параметре функции. Но смысл всех этих действий есть, только если данный объект существует в БД (на него есть ссылка).

Для начала составим текст запроса для полей выборки поочередно обходя структуры Основных и Дополнительных реквизитов

    Если НЕ СсылкаОбъекта.Пустая() тогда

        // сформируем текст выборки по реквизитам
       
ТекстВыборкиРеквизиты = «»;
        Для
индекс = 0 по 1 Цикл
           
СтруктураРеквизитов = ?(индекс = 0, ОсновныеРеквизиты, ДополнительныеРеквизиты);
            Если
СтруктураРеквизитов = Неопределено тогда
                Продолжить;
            КонецЕсли;
            Для каждого
ЭлементСтруктуры из СтруктураРеквизитов Цикл
                Если НЕ
МетаданныеОбъекта.ТабличныеЧасти.Найти(ЭлементСтруктуры.Ключ) = Неопределено тогда
                   
// это имя табличной части
                   
Продолжить;
                КонецЕсли;
               
ТекстВыборкиРеквизиты = ТекстВыборкиРеквизиты
                     + ?(ПустаяСтрока(ТекстВыборкиРеквизиты), «», «,» + Символы.ПС)
                     + ?(
ЗначениеЗаполнено(ЭлементСтруктуры.Значение), ЭлементСтруктуры.Значение, ЭлементСтруктуры.Ключ)
                     +
» КАК » + ЭлементСтруктуры.Ключ;
            КонецЦикла;
        КонецЦикла;

Теперь соберем текст запроса и выполним его. Результат запроса добавим в общую структуру данных объекта

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

    КонецЕсли;

Все данные получены, можно завершать функцию

    Возврат СтруктураОбъекта;

КонецФункции // ДанныеРеквизитовОбъекта()

Достоинства алгоритма:

  1. Может работать с различными типами объектов конфигурации (которые могут иметь ссылки: Справочники, Документы, ПВХ и т.п.);
  2. Получение всех реквизитов за одно обращение к БД;
  3. Данные возвращаются в виде структуры, что позволяет их передать далее на клиент без преобразования;
  4. С помошью Дополнительных реквизитов можно получить вспомогательные данные (обращение к полям выборки через несколько точек) или на уровне запроса выполнить какие-то действия с данными.
  5. Наглядность кода, данные получаются одной функцией, без надобности каждый раз писать запрос к БД с обработкой результата.

Недостатки:

  1. Не реализована возможность получения табличных частей объектов

Дополнительные функции использованные в алгоритме:

// Возвращает массив имен всех реквизитов переданного объекта
//
Функция МассивИменРеквизитовОбъекта(Объект) Экспорт

    МассивИменРеквизитов = Новый Массив;

    Если ТипЗнч(Объект) = Тип(«ОбъектМетаданных») тогда
       
МетаданныеОбъекта = Объект;
    иначе
       
МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипЗнч(Объект));
        Если
МетаданныеОбъекта = Неопределено тогда
            Возврат
МассивИменРеквизитов;
        КонецЕсли;
    КонецЕсли;

    Для индекс = 0 по 1 Цикл
       
КоллекцияРеквизитов = ?(индекс = 0, МетаданныеОбъекта.СтандартныеРеквизиты, МетаданныеОбъекта.Реквизиты);
        Для Каждого
Реквизит Из КоллекцияРеквизитов Цикл
           
МассивИменРеквизитов.Добавить(Реквизит.Имя);
        КонецЦикла;
    КонецЦикла;
    Для каждого
ОбщийРеквизит Из Метаданные.ОбщиеРеквизиты Цикл
        Если
ИспользуетсяОбщийРеквизит(ОбщийРеквизит, МетаданныеОбъекта) тогда
           
МассивИменРеквизитов.Добавить(ОбщийРеквизит.Имя);
        КонецЕсли;
    КонецЦикла;

    Возврат МассивИменРеквизитов;

КонецФункции

// Проверяет используется ли в Объекте указанный общий реквизит
//
Функция ИспользуетсяОбщийРеквизит(ОбщийРеквизит, Объект) Экспорт

    Если ТипЗнч(Объект) = Тип(«ОбъектМетаданных») тогда
       
МетаданныеОбъекта = Объект;
    иначе
       
МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипЗнч(Объект));
        Если
МетаданныеОбъекта = Неопределено тогда
            Возврат Ложь;
        КонецЕсли;
    КонецЕсли;

    Если ТипЗнч(ОбщийРеквизит) = Тип(«ОбъектМетаданных») тогда
       
МетаданныеОбщегоРеквизита = ОбщийРеквизит;
    иначе
       
МетаданныеОбщегоРеквизита = Метаданные.ОбщиеРеквизиты.Найти(ОбщийРеквизит);
        Если
МетаданныеОбщегоРеквизита = Неопределено тогда
            Возврат Ложь;
        КонецЕсли;
    КонецЕсли;

    ЭлементСостава = МетаданныеОбщегоРеквизита.Состав.Найти(МетаданныеОбъекта);
    Если
ЭлементСостава = Неопределено тогда
        Возврат Ложь;
    КонецЕсли;

    пИспользованиеОбщегоРеквизита = Метаданные.СвойстваОбъектов.ИспользованиеОбщегоРеквизита;
    Если
ЭлементСостава.Использование = пИспользованиеОбщегоРеквизита.Использовать тогда
        Возврат Истина;
    иначеЕсли
ЭлементСостава.Использование = пИспользованиеОбщегоРеквизита.НеИспользовать тогда
        Возврат Ложь;
    иначе
       
пАвтоИспользованиеОбщегоРеквизита = Метаданные.СвойстваОбъектов.АвтоИспользованиеОбщегоРеквизита;
        Если
МетаданныеОбщегоРеквизита.АвтоИспользование = пАвтоИспользованиеОбщегоРеквизита.Использовать тогда
            Возврат Истина;
        иначе
            Возврат Ложь;
        КонецЕсли;
    КонецЕсли;

КонецФункции

22 Comments

  1. ildarovich
    попробуем унифицировать и оптимизировать этот процесс

    — Под процессом подразумевается

    обращение к значениям реквизитов различных объектов «через точку»?

    — Критерием оптимизации является время доступа к реквизитам? — Тогда использование этих функций скорее всего будет ошибкой — время доступа в среднем довольно значительно возрастет (попробуйте провести эксперименты). Дело в том, что платформа сама довольно эффективно кэширует объекты и в большинстве случаев не производит многократного обращения к БД. Поэтому польза от такого доморощенного кэширования представляется весьма сомнительной, зато накладные расходы очевидны.

    Reply ↓
  2. zling

    Serge F, Так ведь все равно кеширование существует. Просто нужно первым считывать не наименование или код, а любой другой реквизит, тогда объект кешируется полностью. Также и в данном случае, объект или несколько объектов будут в кеше + в данной структуре. Из плюсов остается преобразование данных в структуру для передачи. Для ТЧ можно массив таких структур использовать

    Reply ↓
  3. zling

    (1) опередил, пока я аватар искал))

    Reply ↓
  4. zling

    опять же лишние данные на клиент с сервера тягать — лишний трафик, так вот

    Reply ↓
  5. kyrasol

    Возможно, что при полном чтении всех реквизитов кеширование объекта самой платформы будет выигрывать… Но если мне нужно только пару-тройку реквизитов + еще один подчиненный через точку и больше от объекта мне ничего не надо? в таком случае я получу только эти значения без кеширования всех реквизитов 1С-кой

    Reply ↓
  6. vandalsvq

    В БСП есть аналогичные методы.

    Отличия от предложенного решения:

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

    — у них можно передавать не только строку, но и структуру, таким образом можно исхитрившись получить табличную часть.

    Reply ↓
  7. kyrasol

    (6) где в БСП их можно найти? Функции писались самостоятельно с нуля, охота посмотреть на реализацию от 1С…

    Reply ↓
  8. yuraos

    Далее с помошью функции ЭтоСсылка() определим, является ли объект ссылочным типом. Для ссылочных типов значения необходимо получить из БД, в противном случае значения реквизитов хранятся в памяти.

    ЭтоСсылка = ЭтоСсылка(Объект);

    что это за загадочная функция ???

    Reply ↓
  9. yuraos

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

    Ох уж эти злоумышленники с углубленным знанием 1С,

    видать они недавно устроили массовую атаку со взломом …

    …

    раз уж так часто в последнее время на Инфостарте

    заботятся о информационной безопасности прикладных решений

    в самых извращенных ситуациях

    Reply ↓
  10. vandalsvq

    (7) см. ОбщийМодуль в БСП 2.1, методы ЗначениеРеквизитаОбъекта, ЗначениеРеквизитовОбъекта и т.д. В более ранних версиях они именовались ПолучитьЗначениеРеквизита, ПолучитьЗначенияРеквизитов.

    Reply ↓
  11. kyrasol

    (8)ЭтоСсылка() возвращает истина если переданный параметр является ссылкой

    например, проверив принадлежность типа объекта к ссылочным типам

    Справочники.ТипВсеСсылки().СодержитТип(ТипОбъекта)

    ИЛИ Документы.ТипВсеСсылки().СодержитТип(ТипОбъекта)

    ИЛИ Перечисления.ТипВсеСсылки().СодержитТип(ТипОбъекта)

    и т.д.

    Reply ↓
  12. yuraos

    (11) ммммм…

    семантика функции по ее названию ясна и реализация ее тоже примерно понятна.

    НО:

    Функция явно не типовая (В УПП-1.2 такой нет, есть похожая с другим названием).

    Так-что неполохо бы привести ее реализацию в статье.

    Reply ↓
  13. yuraos

    (12)

    ЗЫ:

    А то я по простоте душевной было подумал,

    что под какими-то 8.2.х платформами такую встроенную функцию забабахали.

    Reply ↓
  14. Трактор

    В похожую тему я тоже когда-то писАл http://infostart.ru/profile/10200/blog/960/

    Reply ↓
  15. Jogeedae

    Плохо что нет замеров.

    В чём выгода неясно.

    Я правильно понял принцип?:

    КэшРеквизитовОбъекта = Новый ФиксированнаяСтруктура(ПодготовленнаяСтруктураМетаданных[ТипЗнч(Объект));

    ЗаполнитьЗначенияСвойств(КэшРеквизитовОбъекта, Объект);

    Reply ↓
  16. ineshyk

    как Вы контролируете «промах» кэша, когда объект в памяти будет отличатся от объекта БП.

    чем не устроила функция в БСП ОбщегоНазначения.ПолучитьЗначенияРеквизитов(Ссылка, ИменаРеквизитов)?

    Reply ↓
  17. Aleksey.Bochkov

    И ни одного минуса за такой «велосипед».. непорядок :).

    Reply ↓
  18. Abadonna

    (17)

    И ни одного минуса за такой «велосипед»

    А чего минусить, пусть балуется 😉 Во всяком случае, не «Отчет по ДР сотрудников» :)))

    Reply ↓
  19. V.Nikonov

    Ну, не всякий сможет начинать с «ПодсистемаРазработчика»…

    А очки зарабатывать хлопцу надо, дабы безболезненно пользоваться чужими наработками.

    Reply ↓
  20. Gilev.Vyacheslav

    …

    Reply ↓
  21. V.Nikonov

    (20) Gilev.Vyacheslav, насчет качества первых статей у меня лично претензий нет. От себя Плюс поставил.

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

    Reply ↓
  22. jobkostya1c8
    Описанные далее функции желательно располагать в общем серверном модуле без возможности вызова сервера. Если будет возможность вызывать данные функции с клиента, то злоумышленник сможет получить любые данные из базы без особого труда.

    Понравилось особенно. Долго искал вызов функции в стандартной конфе на основе БСП в модулях с галкой «Вызов сервера». Теперь понятно почему 🙂

    Reply ↓

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *