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