Общий принцип оптимизации заключается в сокрашении количества обращений к базе данных. Для этого в линейном алгоритме анализируем, что за объект перед нами, если это ссылка, то описываем запрос, с помощью которого получаем все данные и в дальнейшем работаем с результатом запроса.
Задача: Описать функции позволяющие получить значения реквизитов различных объектов (независимо от типа объекта). Но ограничимся объектами метаданных конфигурации, которые могут иметь ссылочный тип данных: Справочники, Документы и т.п.
Описанные далее функции желательно располагать в общем серверном модуле без возможности вызова сервера. Если будет возможность вызывать данные функции с клиента, то злоумышленник сможет получить любые данные из базы без особого труда.
Определимся с параметрами:
- Объект — объект, данные которого необходимо получить;
- ИменаРеквизитов — Список имен реквизитов, данные которых необходимо получить. Если спиок не указан, то предполагаем, что нужно получить данные всех реквизитов;
- ДополнительныеРеквизиты — Структура, с помошью которой можно было бы описать дополнительные данные, которые нужно получить вместе с реквизитами объекта (без использования явных соединений с другими таблицами) или выполнение каких-то действий с полями выборки на языке запросов. В ключе элемента структуры описываем имя реквизита в общем списке реквизитов объекта, в значении — поле выборки или алгоритм обработки полей выборки на языке запросов.
Функция ДанныеРеквизитовОбъекта(Объект, ИменаРеквизитов = Неопределено,
ДополнительныеРеквизиты = Неопределено) Экспорт
Разберем, значения каких реквизитов необходимо получить из базы и приведем список имен к типу данных Массив.
СтруктураОбъекта = Новый Структура;
МетаданныеОбъекта = Объект.Метаданные();
Если ИменаРеквизитов = Неопределено тогда
МассивИменРеквизитов = МассивИменРеквизитовОбъекта(Объект);
иначе
Если ТипЗнч(ИменаРеквизитов) = Тип(«Массив») тогда
МассивИменРеквизитов = ИменаРеквизитов;
иначеЕсли ТипЗнч(ИменаРеквизитов) = Тип(«Строка») тогда
МассивИменРеквизитов = МассивПодстрокИзСтроки(ИменаРеквизитов);
КонецЕсли;
КонецЕсли;
Если список имен не задан, то с помощью функции МассивИменРеквизитовОбъекта() получим массив имен всех реквизитов объекта (алгоритм этой функции смотри ниже).
Если список имен задан в виде строки разделенной запятыми, то разложим ее на подстроки с помошью функции МассивПодстрокИзСтроки(). Это не сложная задача, алгоритм этой функции разбирать не будем.
Далее с помошью функции ЭтоСсылка() определим, является ли объект ссылочным типом. Для ссылочных типов значения необходимо получить из БД, в противном случае значения реквизитов хранятся в памяти.
ЭтоСсылка = ЭтоСсылка(Объект);
СсылкаОбъекта = ?(ЭтоСсылка, Объект, Объект.Ссылка);
ПолучитьДанныеИзОбъекта = НЕ ЭтоСсылка;
Если ЭтоСсылка И СсылкаОбъекта.Пустая() тогда
// это пустая ссылка (данных в базе нет)
ПолучитьДанныеИзОбъекта = Истина;
КонецЕсли;
Теперь, если данные объекта хранятся в памяти — мы можем их сразу получить, в противном случае необходио подготовиться для составления запроса к БД (переведем список имен реквизитов из типа данных Массив в тип Структура).
ОсновныеРеквизиты = Новый Структура;
Для каждого ИмяРеквизита Из МассивИменРеквизитов Цикл
Если ПолучитьДанныеИзОбъекта тогда
СтруктураОбъекта.Вставить(ИмяРеквизита, Объект[ИмяРеквизита]);
иначе
ОсновныеРеквизиты.Вставить(ИмяРеквизита);
КонецЕсли;
КонецЦикла;
Все готово к составлению запроса чтобы получить значения реквизитов если перед нами объект ссылочного типа, а так же для получения дополнительных реквизитов, описанных в 3-ем параметре функции. Но смысл всех этих действий есть, только если данный объект существует в БД (на него есть ссылка).
Для начала составим текст запроса для полей выборки поочередно обходя структуры Основных и Дополнительных реквизитов
Если НЕ СсылкаОбъекта.Пустая() тогда
// сформируем текст выборки по реквизитам
ТекстВыборкиРеквизиты = «»;
Для индекс = 0 по 1 Цикл
СтруктураРеквизитов = ?(индекс = 0, ОсновныеРеквизиты, ДополнительныеРеквизиты);
Если СтруктураРеквизитов = Неопределено тогда
Продолжить;
КонецЕсли;
Для каждого ЭлементСтруктуры из СтруктураРеквизитов Цикл
Если НЕ МетаданныеОбъекта.ТабличныеЧасти.Найти(ЭлементСтруктуры.Ключ) = Неопределено тогда
// это имя табличной части
Продолжить;
КонецЕсли;
ТекстВыборкиРеквизиты = ТекстВыборкиРеквизиты
+ ?(ПустаяСтрока(ТекстВыборкиРеквизиты), «», «,» + Символы.ПС)
+ ?(ЗначениеЗаполнено(ЭлементСтруктуры.Значение), ЭлементСтруктуры.Значение, ЭлементСтруктуры.Ключ)
+ » КАК » + ЭлементСтруктуры.Ключ;
КонецЦикла;
КонецЦикла;
Теперь соберем текст запроса и выполним его. Результат запроса добавим в общую структуру данных объекта
// получим реквизиты одним запросом
Если НЕ ПустаяСтрока(ТекстВыборкиРеквизиты) тогда
Запрос = Новый Запрос;
Запрос.Текст = «ВЫБРАТЬ
|» + ТекстВыборкиРеквизиты + «
|ИЗ
| » + МетаданныеОбъекта.ПолноеИмя() + «
|ГДЕ
| Ссылка = &Ссылка»;
Запрос.УстановитьПараметр(«Ссылка», СсылкаОбъекта);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() тогда
Для каждого Колонка из РезультатЗапроса.Колонки Цикл
СтруктураОбъекта.Вставить(Колонка.Имя, Выборка[Колонка.Имя]);
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Все данные получены, можно завершать функцию
Возврат СтруктураОбъекта;
КонецФункции // ДанныеРеквизитовОбъекта()
Достоинства алгоритма:
- Может работать с различными типами объектов конфигурации (которые могут иметь ссылки: Справочники, Документы, ПВХ и т.п.);
- Получение всех реквизитов за одно обращение к БД;
- Данные возвращаются в виде структуры, что позволяет их передать далее на клиент без преобразования;
- С помошью Дополнительных реквизитов можно получить вспомогательные данные (обращение к полям выборки через несколько точек) или на уровне запроса выполнить какие-то действия с данными.
- Наглядность кода, данные получаются одной функцией, без надобности каждый раз писать запрос к БД с обработкой результата.
Недостатки:
- Не реализована возможность получения табличных частей объектов
Дополнительные функции использованные в алгоритме:
// Возвращает массив имен всех реквизитов переданного объекта
//
Функция МассивИменРеквизитовОбъекта(Объект) Экспорт
МассивИменРеквизитов = Новый Массив;
Если ТипЗнч(Объект) = Тип(«ОбъектМетаданных») тогда
МетаданныеОбъекта = Объект;
иначе
МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипЗнч(Объект));
Если МетаданныеОбъекта = Неопределено тогда
Возврат МассивИменРеквизитов;
КонецЕсли;
КонецЕсли;
Для индекс = 0 по 1 Цикл
КоллекцияРеквизитов = ?(индекс = 0, МетаданныеОбъекта.СтандартныеРеквизиты, МетаданныеОбъекта.Реквизиты);
Для Каждого Реквизит Из КоллекцияРеквизитов Цикл
МассивИменРеквизитов.Добавить(Реквизит.Имя);
КонецЦикла;
КонецЦикла;
Для каждого ОбщийРеквизит Из Метаданные.ОбщиеРеквизиты Цикл
Если ИспользуетсяОбщийРеквизит(ОбщийРеквизит, МетаданныеОбъекта) тогда
МассивИменРеквизитов.Добавить(ОбщийРеквизит.Имя);
КонецЕсли;
КонецЦикла;
Возврат МассивИменРеквизитов;
КонецФункции
// Проверяет используется ли в Объекте указанный общий реквизит
//
Функция ИспользуетсяОбщийРеквизит(ОбщийРеквизит, Объект) Экспорт
Если ТипЗнч(Объект) = Тип(«ОбъектМетаданных») тогда
МетаданныеОбъекта = Объект;
иначе
МетаданныеОбъекта = Метаданные.НайтиПоТипу(ТипЗнч(Объект));
Если МетаданныеОбъекта = Неопределено тогда
Возврат Ложь;
КонецЕсли;
КонецЕсли;
Если ТипЗнч(ОбщийРеквизит) = Тип(«ОбъектМетаданных») тогда
МетаданныеОбщегоРеквизита = ОбщийРеквизит;
иначе
МетаданныеОбщегоРеквизита = Метаданные.ОбщиеРеквизиты.Найти(ОбщийРеквизит);
Если МетаданныеОбщегоРеквизита = Неопределено тогда
Возврат Ложь;
КонецЕсли;
КонецЕсли;
ЭлементСостава = МетаданныеОбщегоРеквизита.Состав.Найти(МетаданныеОбъекта);
Если ЭлементСостава = Неопределено тогда
Возврат Ложь;
КонецЕсли;
пИспользованиеОбщегоРеквизита = Метаданные.СвойстваОбъектов.ИспользованиеОбщегоРеквизита;
Если ЭлементСостава.Использование = пИспользованиеОбщегоРеквизита.Использовать тогда
Возврат Истина;
иначеЕсли ЭлементСостава.Использование = пИспользованиеОбщегоРеквизита.НеИспользовать тогда
Возврат Ложь;
иначе
пАвтоИспользованиеОбщегоРеквизита = Метаданные.СвойстваОбъектов.АвтоИспользованиеОбщегоРеквизита;
Если МетаданныеОбщегоРеквизита.АвтоИспользование = пАвтоИспользованиеОбщегоРеквизита.Использовать тогда
Возврат Истина;
иначе
Возврат Ложь;
КонецЕсли;
КонецЕсли;
КонецФункции
— Под процессом подразумевается
— Критерием оптимизации является время доступа к реквизитам? — Тогда использование этих функций скорее всего будет ошибкой — время доступа в среднем довольно значительно возрастет (попробуйте провести эксперименты). Дело в том, что платформа сама довольно эффективно кэширует объекты и в большинстве случаев не производит многократного обращения к БД. Поэтому польза от такого доморощенного кэширования представляется весьма сомнительной, зато накладные расходы очевидны.
Serge F, Так ведь все равно кеширование существует. Просто нужно первым считывать не наименование или код, а любой другой реквизит, тогда объект кешируется полностью. Также и в данном случае, объект или несколько объектов будут в кеше + в данной структуре. Из плюсов остается преобразование данных в структуру для передачи. Для ТЧ можно массив таких структур использовать
(1) опередил, пока я аватар искал))
опять же лишние данные на клиент с сервера тягать — лишний трафик, так вот
Возможно, что при полном чтении всех реквизитов кеширование объекта самой платформы будет выигрывать… Но если мне нужно только пару-тройку реквизитов + еще один подчиненный через точку и больше от объекта мне ничего не надо? в таком случае я получу только эти значения без кеширования всех реквизитов 1С-кой
В БСП есть аналогичные методы.
Отличия от предложенного решения:
— 1С не проверяют существование реквизита, таким образом это ложится на плечи программиста. НО: считаю что это правильно, ибо лишние затраты на проверку метаданных.
— у них можно передавать не только строку, но и структуру, таким образом можно исхитрившись получить табличную часть.
(6) где в БСП их можно найти? Функции писались самостоятельно с нуля, охота посмотреть на реализацию от 1С…
Далее с помошью функции ЭтоСсылка() определим, является ли объект ссылочным типом. Для ссылочных типов значения необходимо получить из БД, в противном случае значения реквизитов хранятся в памяти.
ЭтоСсылка = ЭтоСсылка(Объект);
что это за загадочная функция ???
Описанные далее функции желательно располагать в общем серверном модуле без возможности вызова сервера. Если будет возможность вызывать данные функции с клиента, то злоумышленник сможет получить любые данные из базы без особого труда.
Ох уж эти злоумышленники с углубленным знанием 1С,
видать они недавно устроили массовую атаку со взломом …
…
раз уж так часто в последнее время на Инфостарте
заботятся о информационной безопасности прикладных решений
в самых извращенных ситуациях
(7) см. ОбщийМодуль в БСП 2.1, методы ЗначениеРеквизитаОбъекта, ЗначениеРеквизитовОбъекта и т.д. В более ранних версиях они именовались ПолучитьЗначениеРеквизита, ПолучитьЗначенияРеквизитов.
(8)ЭтоСсылка() возвращает истина если переданный параметр является ссылкой
например, проверив принадлежность типа объекта к ссылочным типам
Справочники.ТипВсеСсылки().СодержитТип(ТипОбъекта)
ИЛИ Документы.ТипВсеСсылки().СодержитТип(ТипОбъекта)
ИЛИ Перечисления.ТипВсеСсылки().СодержитТип(ТипОбъекта)
и т.д.
(11) ммммм…
семантика функции по ее названию ясна и реализация ее тоже примерно понятна.
НО:
Функция явно не типовая (В УПП-1.2 такой нет, есть похожая с другим названием).
Так-что неполохо бы привести ее реализацию в статье.
(12)

ЗЫ:
А то я по простоте душевной было подумал,
что под какими-то 8.2.х платформами такую встроенную функцию забабахали.
В похожую тему я тоже когда-то писАлhttp://infostart.ru/profile/10200/blog/960/
Плохо что нет замеров.
В чём выгода неясно.
Я правильно понял принцип?:
КэшРеквизитовОбъекта = Новый ФиксированнаяСтруктура(ПодготовленнаяСтруктураМетаданных[ТипЗнч(Объект));
ЗаполнитьЗначенияСвойств(КэшРеквизитовОбъекта, Объект);
как Вы контролируете «промах» кэша, когда объект в памяти будет отличатся от объекта БП.
чем не устроила функция в БСП ОбщегоНазначения.ПолучитьЗначенияРеквизитов(Ссылка, ИменаРеквизитов)?
И ни одного минуса за такой «велосипед».. непорядок :).
(17)
А чего минусить, пусть балуется 😉 Во всяком случае, не «Отчет по ДР сотрудников» :)))
Ну, не всякий сможет начинать с «ПодсистемаРазработчика»…
А очки зарабатывать хлопцу надо, дабы безболезненно пользоваться чужими наработками.
…
(20) Gilev.Vyacheslav, насчет качества первых статей у меня лично претензий нет. От себя Плюс поставил.
Просто, есть общая проблема «новичков», при недостаточном количестве собственных публикаций (а у Новичка — это по определению), считаю «нормальным» ставить плюсы, даже не если не «Супер шедевр» публикуется. Хуже — отмечаться на Инфостарте поливая никому не интересную «воду».
Понравилось особенно. Долго искал вызов функции в стандартной конфе на основе БСП в модулях с галкой «Вызов сервера». Теперь понятно почему 🙂