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