Почему возникает ошибка при обращении к полю регистра через НаборЗаписей и как правильно изменить запись?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке алгоритмов обновления данных в регистрах сведений разработчики часто сталкиваются с ситуацией, когда код вида НаборЗаписей.ИмяПоля = Значение; вызывает ошибку о том, что поле не обнаружено. Разберем по шагам, почему возникает эта ошибка, как устроен объект РегистрСведенийНаборЗаписей и какими способами можно корректно отредактировать или удалить записи регистра сведений.

Причины ошибки: почему у НабораЗаписей нет полей ресурсов

Проанализируем ситуацию: объект РегистрСведенийНаборЗаписей представляет собой не отдельную строку, а коллекцию строк (таблицу), подчиненную определенному отбору. У самого набора записей есть свойства (такие как Отбор, Модифицированность) и методы (Прочитать(), Записать(), Добавить(), Количество(), Очистить()), но у него нет и не может быть свойств, совпадающих с именами измерений, ресурсов или реквизитов регистра сведений.

Попытка присвоить значение напрямую набору записей равносильна попытке записать данные во всю таблицу целиком без указания конкретной строки. Дополнительно выясним причину распространенного заблуждения, когда кажется, что подобный код ранее работал:

  1. До вызова метода Прочитать() набор записей в оперативной памяти абсолютно пуст, даже если в базе данных по данному отбору есть строки. Соответственно, НаборЗаписей.Количество() возвращает 0.
  2. Если в коде стояло условие Если НаборЗаписей.Количество() = 1 Тогда, ветка с ошибочным обращением к полям набора просто никогда не выполнялась.
  3. Управление сразу уходило в ветку Иначе, где создавалась новая строка через НаборЗаписей.Добавить(), свойства присваивались уже конкретной строке, и вызывался метод Записать(). При этом старая запись в базе молча перезаписывалась новой.

Способ 1. Изменение записи через обращение к строкам набора

Рассмотрим правильный вариант работы через РегистрСведенийНаборЗаписей. Чтобы получить доступ к реквизитам и ресурсам, необходимо сначала выгрузить данные из базы методом Прочитать(), а затем обратиться к конкретной строке коллекции по индексу или в цикле.

Посмотрим на пример изменения записи с проверкой наличия строк:


НаборЗаписей = РегистрыСведений.sh_СистемныйСтатус2ДатаОкончание.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
НаборЗаписей.Прочитать();

Если НаборЗаписей.Количество() > 0 Тогда
    // Обращаемся к конкретной строке по индексу [0]
    СтрокаЗаписи = НаборЗаписей[0];
    
    Если СтрокаЗаписи.ДатаОкончаниеС < Дата И ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеС) Тогда
        СтрокаЗаписи.ДатаОкончаниеС = '0001-01-01';
    КонецЕсли;
    
    // Если требуется полностью очистить набор при сбросе всех дат:
    Если Не ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеС) 
        И Не ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеМ) 
        И Не ЗначениеЗаполнено(СтрокаЗаписи.ДатаОкончаниеСМ) Тогда
        
        НаборЗаписей.Очистить();
    КонецЕсли;
    
    НаборЗаписей.Записать();
КонецЕсли;

Если в регистре по установленному отбору может храниться несколько записей (например, регистр периодический или имеет другие измерения), используем цикл обхода строк:


Для Каждого Запись Из НаборЗаписей Цикл
    Если Запись.ДатаОкончаниеС < Дата И ЗначениеЗаполнено(Запись.ДатаОкончаниеС) Тогда
        Запись.ДатаОкончаниеС = '0001-01-01';
    КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать();

Способ 2. Использование объекта РегистрСведенийМенеджерЗаписи

Если регистр сведений является независимым и нам требуется работать строго с одной конкретной записью, наиболее лаконичным решением будет использование менеджера записи. В отличие от набора, менеджер записи предоставляет прямой доступ к полям одной конкретной строки.

Разберем последовательность работы с менеджером записи:

  1. Создаем менеджер с помощью вызова СоздатьМенеджерЗаписи().
  2. Заполняем ключевые поля записи: все измерения регистра (и Период, если регистр периодический).
  3. Вызываем метод Прочитать().
  4. Если запись существует в базе (метод Выбран() возвращает Истина), модифицируем ресурсы напрямую через свойства объекта или удаляем запись методом Удалить().
  5. Сохраняем результат методом Записать().

Посмотрим на практическую реализацию этого подхода:


МенеджерЗаписи = РегистрыСведений.sh_СистемныйСтатус2ДатаОкончание.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = Выборка.Номенклатура;

// Если регистр периодический, обязательно передаем и период:
// МенеджерЗаписи.Период = Выборка.Период;

МенеджерЗаписи.Прочитать();

Если МенеджерЗаписи.Выбран() Тогда
    МенеджерЗаписи.ДатаОкончаниеС = '0001-01-01';
    
    // Если все даты обнулены — удаляем запись целиком
    Если Не ЗначениеЗаполнено(МенеджерЗаписи.ДатаОкончаниеС) 
        И Не ЗначениеЗаполнено(МенеджерЗаписи.ДатаОкончаниеМ) 
        И Не ЗначениеЗаполнено(МенеджерЗаписи.ДатаОкончаниеСМ) Тогда
        
        МенеджерЗаписи.Удалить();
    Иначе
        МенеджерЗаписи.Записать();
    КонецЕсли;
КонецЕсли;

Оптимизация сопутствующего кода: работа со справочниками и транзакциями

Рассмотрим подробнее ошибки оптимизации, часто встречающиеся в цикле обработки данных:

  1. Избыточное обращение к свойству ссылки: Конструкция Выборка.Номенклатура.Ссылка.ПолучитьОбъект() содержит лишнее звено. Поле выборки запроса Выборка.Номенклатура уже содержит ссылку нужного типа, поэтому правильно писать: Выборка.Номенклатура.ПолучитьОбъект().
  2. Многократная запись одного объекта в цикле: Если для одной номенклатуры изменяются сразу несколько реквизитов, вызывать ПолучитьОбъект() и Записать() на каждое условие категорически не рекомендуется. Получать объект справочника следует один раз, выставлять нужные реквизиты, и записывать его один раз в конце итерации с помощью булева флага модификации.
  3. Использование явных транзакций: Для предотвращения рассогласования данных между регистром и справочником при аварийном завершении цикла группируйте операции в транзакцию с помощью методов НачатьТранзакцию() и ЗафиксироватьТранзакцию() с обработкой ошибок в блоке Попытка ... Исключение.
← На главную