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