При работе с Системой компоновки данных (СКД) разработчики часто сталкиваются с ситуацией, когда отчет прекрасно работает с настройками по умолчанию, но начинает выдавать непредсказуемые ошибки при интерактивной настройке пользователем. Одной из наиболее коварных является ошибка «Поле объекта не обнаружено (ТекстЗапроса)» или аналогичные сбои при обращении к свойствам макета компоновки в процедуре ПриКомпоновкеРезультата().
В этой статье мы подробно разберем, почему возникает данная проблема, как работает механизм оптимизации запросов в СКД, и какие подходы к программному выводу отчетов гарантируют стабильную работу системы при любых действиях пользователя.
Рассмотрим классический сценарий. У нас есть относительно простой отчет, построенный на СКД, в котором настроены две группировки полей — например, «День» и «Ссылка». В режиме «1С:Предприятие» пользователь решает временно отключить одну из группировок (снимает флажок использования напротив поля «День» в настройках отчета).
После этого при попытке сформировать отчет система аварийно завершает работу с ошибкой в процедуре модуля объекта отчета:
МакетКомпоновки = КомпоновщикМакета.Выполнить(
СхемаКомпоновкиДанных,
КомпоновщикНастроек.Настройки,
ДанныеРасшифровки
);
При анализе макета выясняется, что в свойстве МакетКомпоновки.НаборыДанных отсутствует ожидаемый набор или при обращении к нему выбрасывается исключение. Выясним, почему поведение платформы меняется из-за простого снятия галочки в настройках группировки.
Первое, что необходимо понять — Система компоновки данных выполняет глубокую оптимизацию исполняемого кода «на лету». Когда мы вызываем метод КомпоновщикМакета.Выполнить(), платформа анализирует, какие поля реально требуются для построения итогового отчета.
Если пользователь отключает группировку, убирает поля из выбранных и не использует их в отборах или условном оформлении, СКД делает вывод, что данные поля (и, возможно, весь связанный с ними набор данных) вообще не нужны для построения результата. В этот момент запускается механизм оптимизации:
МакетКомпоновки.НаборыДанных окажется пустой или не будет содержать искомого элемента.МакетКомпоновки.НаборыДанных.НаборДанных1), система выдаст ошибку, так как этого элемента больше физически нет в структуре.Проанализируем структуру компоновщика настроек. Объект КомпоновщикНастроек состоит из нескольких важных коллекций настроек:
КомпоновщикНастроек.Настройки — это фиксированные настройки, которые заданы разработчиком в конфигураторе по умолчанию. Они статичны и не меняются при действиях пользователя на форме.КомпоновщикНастроек.ПользовательскиеНастройки — именно здесь хранятся все изменения, которые пользователь внес интерактивно (снял галочки с группировок, поменял отборы, добавил новые выбранные поля).КомпоновщикНастроек.ФиксированныеНастройки — настройки, которые накладываются программно и не могут быть изменены пользователем.Если при программном выводе в метод КомпоновщикМакета.Выполнить() передавать напрямую свойство КомпоновщикНастроек.Настройки, платформа получит «чистые» настройки конфигуратора, полностью проигнорировав пользовательские изменения с формы. Это приводит к рассинхронизации состояния интерфейса и исполняемого кода, вызывая непредвиденные ошибки структуры данных.
Для того чтобы объединить все три слоя настроек в один результирующий объект, необходимо использовать метод ПолучитьНастройки():
ИтоговыеНастройки = КомпоновщикНастроек.ПолучитьНастройки();
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, ИтоговыеНастройки, ДанныеРасшифровки);
Часто разработчики путают структуру схемы компоновки (где хранится исходный текст запроса) и структуру сгенерированного макета компоновки. Обратите внимание на следующие особенности:
НаборДанныхЗапросМакетаКомпоновкиДанных нет свойства ТекстЗапроса. Настоящий текст сформированного SQL-подобного запроса хранится в свойстве Запрос.НаборыДанных нельзя без указания конкретного элемента. Правильный синтаксис выглядит так: МакетКомпоновки.НаборыДанных.ИмяВашегоНабора.Запрос или через обращение по индексу МакетКомпоновки.НаборыДанных[0].Запрос.Запрос у них в макете не будет в принципе. Попытка обратиться к нему вызовет ошибку «Поле объекта не обнаружено».Рассмотрим два корректных варианта реализации вывода отчета в модуле объекта, которые помогут избежать подобных ошибок.
Если ваша конфигурация построена на базе Библиотеки Стандартных Подсистем (БСП), ручное создание процессоров вывода и компоновщиков макета в 99% случаев является избыточным. Стандартные процедуры БСП уже содержат в себе все необходимые проверки, корректно объединяют пользовательские настройки и обрабатывают вывод диаграмм.
Достаточно переписать процедуру ПриКомпоновкеРезультата следующим образом:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка) Экспорт
// Передаем управление стандартному механизму БСП
ОтчетыСервер.ВывестиОтчет(ЭтотОбъект, СхемаКомпоновкиДанных, КомпоновщикНастроек.Настройки, ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка);
// Если требуется выполнить дополнительные действия, например, вывести собственные диаграммы:
ВывестиДиаграммы(ДокументРезультат);
КонецПроцедуры
Метод ОтчетыСервер.ВывестиОтчет() самостоятельно получит объединенные настройки, инициализирует процессоры и выведет результат в табличный документ, предотвратив падение платформы.
Если вам требуется тонкий контроль над процессом генерации макета (например, программное изменение текста запроса «на лету» перед выводом) и вы не можете использовать БСП, необходимо правильно инициализировать настройки вручную.
Проанализируем пошаговый алгоритм правильного программного вывода:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка) Экспорт
СтандартнаяОбработка = Ложь;
ДокументРезультат.Очистить();
// ШАГ 1. Обязательно получаем объединенные настройки (дефолтные + пользовательские + фиксированные)
ИтоговыеНастройки = КомпоновщикНастроек.ПолучитьНастройки();
// ШАГ 2. Создаем компоновщик макета
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// ШАГ 3. Выполняем компоновку макета на основе объединенных настроек
МакетКомпоновки = КомпоновщикМакета.Выполнить(
СхемаКомпоновкиДанных,
ИтоговыеНастройки, // Передаем ИтоговыеНастройки вместо КомпоновщикНастроек.Настройки!
ДанныеРасшифровки
);
// ШАГ 4. Проверяем наличие наборов данных перед обращением к ним (защита от оптимизации СКД)
Если МакетКомпоновки.НаборыДанных.Количество() > 0 Тогда
Для Каждого НаборДанных Из МакетКомпоновки.НаборыДанных Цикл
// Проверяем тип набора данных, так как свойство "Запрос" есть только у НаборДанныхЗапросМакетаКомпоновкиДанных
Если ТипЗнч(НаборДанных) = Тип("НаборДанныхЗапросМакетаКомпоновкиДанных") Тогда
// Здесь при необходимости можно модифицировать текст запроса
ТекстЗапроса = НаборДанных.Запрос;
КонецЕсли;
КонецЦикла;
КонецЕсли;
// ШАГ 5. Инициализируем процессор компоновки данных
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
// ШАГ 6. Выводим результат в табличный документ
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных, Истина);
ПроцессорВывода.ЗакончитьВывод();
// Дополнительная обработка результатов (например, вывод пользовательских диаграмм)
ВывестиДиаграммы(ДокументРезультат);
КонецПроцедуры
Использование метода КомпоновщикНастроек.ПолучитьНастройки() гарантирует, что компоновщик макета отработает в полном соответствии с тем, что пользователь видит на экране. Если группировка отключена пользователем, СКД корректно перестроит макет без генерации внутренних ошибок обращения к свойствам.