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