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