Универсальные функции получения значений реквизитов объектов (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 не будет опубликован. Обязательные поля помечены *