При разработке алгоритмов обновления данных в регистрах сведений разработчики часто сталкиваются с ситуацией, когда код вида НаборЗаписей.ИмяПоля = Значение; вызывает ошибку о том, что поле не обнаружено. Разберем по шагам, почему возникает эта ошибка, как устроен объект РегистрСведенийНаборЗаписей и какими способами можно корректно отредактировать или удалить записи регистра сведений.
Проанализируем ситуацию: объект РегистрСведенийНаборЗаписей представляет собой не отдельную строку, а коллекцию строк (таблицу), подчиненную определенному отбору. У самого набора записей есть свойства (такие как Отбор, Модифицированность) и методы (Прочитать(), Записать(), Добавить(), Количество(), Очистить()), но у него нет и не может быть свойств, совпадающих с именами измерений, ресурсов или реквизитов регистра сведений.
Попытка присвоить значение напрямую набору записей равносильна попытке записать данные во всю таблицу целиком без указания конкретной строки. Дополнительно выясним причину распространенного заблуждения, когда кажется, что подобный код ранее работал:
Прочитать() набор записей в оперативной памяти абсолютно пуст, даже если в базе данных по данному отбору есть строки. Соответственно, НаборЗаписей.Количество() возвращает 0.Если НаборЗаписей.Количество() = 1 Тогда, ветка с ошибочным обращением к полям набора просто никогда не выполнялась.Иначе, где создавалась новая строка через НаборЗаписей.Добавить(), свойства присваивались уже конкретной строке, и вызывался метод Записать(). При этом старая запись в базе молча перезаписывалась новой.Рассмотрим правильный вариант работы через РегистрСведенийНаборЗаписей. Чтобы получить доступ к реквизитам и ресурсам, необходимо сначала выгрузить данные из базы методом Прочитать(), а затем обратиться к конкретной строке коллекции по индексу или в цикле.
Посмотрим на пример изменения записи с проверкой наличия строк:
НаборЗаписей = РегистрыСведений.sh_СистемныйСтатус2ДатаОкончание.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() > 0 Тогда
// Обращаемся к конкретной строке по индексу [0]
СтрокаЗаписи = НаборЗаписей[0];
Если СтрокаЗаписи.ДатаОкончаниеС < Дата И ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеС) Тогда
СтрокаЗаписи.ДатаОкончаниеС = '0001-01-01';
КонецЕсли;
// Если требуется полностью очистить набор при сбросе всех дат:
Если Не ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеС)
И Не ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеМ)
И Не ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеСМ) Тогда
НаборЗаписей.Очистить();
КонецЕсли;
НаборЗаписей.Записать();
КонецЕсли;
Если в регистре по установленному отбору может храниться несколько записей (например, регистр периодический или имеет другие измерения), используем цикл обхода строк:
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.ДатаОкончаниеС < Дата И ЗначениеЗаполнено(Запись.ДатаОкончаниеС) Тогда
Запись.ДатаОкончаниеС = '0001-01-01';
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать();
Если регистр сведений является независимым и нам требуется работать строго с одной конкретной записью, наиболее лаконичным решением будет использование менеджера записи. В отличие от набора, менеджер записи предоставляет прямой доступ к полям одной конкретной строки.
Разберем последовательность работы с менеджером записи:
СоздатьМенеджерЗаписи().Период, если регистр периодический).Прочитать().Выбран() возвращает Истина), модифицируем ресурсы напрямую через свойства объекта или удаляем запись методом Удалить().Записать().Посмотрим на практическую реализацию этого подхода:
МенеджерЗаписи = РегистрыСведений.sh_СистемныйСтатус2ДатаОкончание.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = Выборка.Номенклатура;
// Если регистр периодический, обязательно передаем и период:
// МенеджерЗаписи.Период = Выборка.Период;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
МенеджерЗаписи.ДатаОкончаниеС = '0001-01-01';
// Если все даты обнулены — удаляем запись целиком
Если Не ЗначениеЗаполнено(МенеджерЗаписи.ДатаОкончаниеС)
И Не ЗначениеЗаполнено(МенеджерЗаписи.ДатаОкончаниеМ)
И Не ЗначениеЗаполнено(МенеджерЗаписи.ДатаОкончаниеСМ) Тогда
МенеджерЗаписи.Удалить();
Иначе
МенеджерЗаписи.Записать();
КонецЕсли;
КонецЕсли;
Рассмотрим подробнее ошибки оптимизации, часто встречающиеся в цикле обработки данных:
Выборка.Номенклатура.Ссылка.ПолучитьОбъект() содержит лишнее звено. Поле выборки запроса Выборка.Номенклатура уже содержит ссылку нужного типа, поэтому правильно писать: Выборка.Номенклатура.ПолучитьОбъект().ПолучитьОбъект() и Записать() на каждое условие категорически не рекомендуется. Получать объект справочника следует один раз, выставлять нужные реквизиты, и записывать его один раз в конце итерации с помощью булева флага модификации.НачатьТранзакцию() и ЗафиксироватьТранзакцию() с обработкой ошибок в блоке Попытка ... Исключение.