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