gifts2017

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

Опубликовал Serge F (kyrasol) в раздел Программирование - Практика программирования

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    КонецЕсли;

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

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

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

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

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

Недостатки:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Сергей (ildarovich) 15.05.13 10:48
попробуем унифицировать и оптимизировать этот процесс
- Под процессом подразумевается
обращение к значениям реквизитов различных объектов "через точку"?
- Критерием оптимизации является время доступа к реквизитам? - Тогда использование этих функций скорее всего будет ошибкой - время доступа в среднем довольно значительно возрастет (попробуйте провести эксперименты). Дело в том, что платформа сама довольно эффективно кэширует объекты и в большинстве случаев не производит многократного обращения к БД. Поэтому польза от такого доморощенного кэширования представляется весьма сомнительной, зато накладные расходы очевидны.
2. zling zling (zling) 15.05.13 10:49
Serge F, Так ведь все равно кеширование существует. Просто нужно первым считывать не наименование или код, а любой другой реквизит, тогда объект кешируется полностью. Также и в данном случае, объект или несколько объектов будут в кеше + в данной структуре. Из плюсов остается преобразование данных в структуру для передачи. Для ТЧ можно массив таких структур использовать
3. zling zling (zling) 15.05.13 10:50
(1) опередил, пока я аватар искал))
4. zling zling (zling) 15.05.13 10:55
опять же лишние данные на клиент с сервера тягать - лишний трафик, так вот
5. Serge F (kyrasol) 15.05.13 11:39
Возможно, что при полном чтении всех реквизитов кеширование объекта самой платформы будет выигрывать... Но если мне нужно только пару-тройку реквизитов + еще один подчиненный через точку и больше от объекта мне ничего не надо? в таком случае я получу только эти значения без кеширования всех реквизитов 1С-кой
6. Александр Анисков (vandalsvq) 15.05.13 13:01
В БСП есть аналогичные методы.

Отличия от предложенного решения:
- 1С не проверяют существование реквизита, таким образом это ложится на плечи программиста. НО: считаю что это правильно, ибо лишние затраты на проверку метаданных.
- у них можно передавать не только строку, но и структуру, таким образом можно исхитрившись получить табличную часть.
StepByStep; +1 Ответить 1
7. Serge F (kyrasol) 15.05.13 13:11
(6) где в БСП их можно найти? Функции писались самостоятельно с нуля, охота посмотреть на реализацию от 1С...
8. Юрий Осипов (yuraos) 15.05.13 13:36

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

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


что это за загадочная функция ???
9. Юрий Осипов (yuraos) 15.05.13 13:45

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


Ох уж эти злоумышленники с углубленным знанием 1С,
видать они недавно устроили массовую атаку со взломом ...
...
раз уж так часто в последнее время на Инфостарте
заботятся о информационной безопасности прикладных решений
в самых извращенных ситуациях
10. Александр Анисков (vandalsvq) 15.05.13 14:35
(7) kyrasol, см. ОбщийМодуль в БСП 2.1, методы ЗначениеРеквизитаОбъекта, ЗначениеРеквизитовОбъекта и т.д. В более ранних версиях они именовались ПолучитьЗначениеРеквизита, ПолучитьЗначенияРеквизитов.
11. Serge F (kyrasol) 15.05.13 17:22
(8)ЭтоСсылка() возвращает истина если переданный параметр является ссылкой
например, проверив принадлежность типа объекта к ссылочным типам
Справочники.ТипВсеСсылки().СодержитТип(ТипОбъекта)
ИЛИ Документы.ТипВсеСсылки().СодержитТип(ТипОбъекта)
ИЛИ Перечисления.ТипВсеСсылки().СодержитТип(ТипОбъекта)
и т.д.
12. Юрий Осипов (yuraos) 15.05.13 18:15
(11) kyrasol, ммммм...
семантика функции по ее названию ясна и реализация ее тоже примерно понятна.
НО:
Функция явно не типовая (В УПП-1.2 такой нет, есть похожая с другим названием).
Так-что неполохо бы привести ее реализацию в статье.
13. Юрий Осипов (yuraos) 15.05.13 18:17
(12)
ЗЫ:
А то я по простоте душевной было подумал,
что под какими-то 8.2.х платформами такую встроенную функцию забабахали.
14. Трактор Трактор (Трактор) 16.05.13 13:24
15. PlatonovStepan (Jogeedae) 16.05.13 16:52
Плохо что нет замеров.
В чём выгода неясно.
Я правильно понял принцип?:
КэшРеквизитовОбъекта = Новый ФиксированнаяСтруктура(ПодготовленнаяСтруктураМетаданных[ТипЗнч(Объект));
ЗаполнитьЗначенияСвойств(КэшРеквизитовОбъекта, Объект);
16. Игорь Нешик (ineshyk) 16.05.13 19:50
как Вы контролируете "промах" кэша, когда объект в памяти будет отличатся от объекта БП.
чем не устроила функция в БСП ОбщегоНазначения.ПолучитьЗначенияРеквизитов(Ссылка, ИменаРеквизитов)?
17. Алексей Бочков (Aleksey.Bochkov) 16.05.13 22:06
И ни одного минуса за такой "велосипед".. непорядок :).
Трактор; +1 Ответить 1
18. Аркадий Кучер (Abadonna) 18.05.13 06:00
(17)
И ни одного минуса за такой "велосипед"

А чего минусить, пусть балуется ;) Во всяком случае, не "Отчет по ДР сотрудников" :)))
Трактор; borman; +2 Ответить
19. Вадим Никонов (V.Nikonov) 27.05.13 17:38
Ну, не всякий сможет начинать с "ПодсистемаРазработчика"...
А очки зарабатывать хлопцу надо, дабы безболезненно пользоваться чужими наработками.
20. Вячеслав Гилёв (Gilev.Vyacheslav) 27.05.13 17:52
21. Вадим Никонов (V.Nikonov) 27.05.13 20:28
(20) Gilev.Vyacheslav, насчет качества первых статей у меня лично претензий нет. От себя Плюс поставил.
Просто, есть общая проблема "новичков", при недостаточном количестве собственных публикаций (а у Новичка - это по определению), считаю "нормальным" ставить плюсы, даже не если не "Супер шедевр" публикуется. Хуже - отмечаться на Инфостарте поливая никому не интересную "воду".
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа