Как правильно записывать данные в регистр сведений в 1С и избежать распространенных ошибок

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

Программная работа с регистрами сведений — одна из базовых и наиболее частых задач при разработке в среде «1С:Предприятие». Тем не менее, начинающие программисты регулярно сталкиваются со стандартными ошибками при попытке добавить или обновить записи. В этой статье мы подробно разберем, почему возникают такие ошибки, как «Поле объекта не обнаружено» и «Значение поля не соответствует установленному отбору», проанализируем причины их появления на реальном примере из практики и рассмотрим несколько надежных способов их решения.

Анализ проблемы: типичные ошибки при работе с Набором записей

Рассмотрим ситуацию, когда разработчик пытается обойти в цикле выборку из запроса и записать полученные данные (номенклатуру и код печати) в независимый регистр сведений КодПечатиЯрлыка. В исходном коде автора это выглядело следующим образом:


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

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

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

Разбор ошибки: «Значение поля не соответствует установленному отбору»

Эта ошибка часто возникает при попытке обновить запись в регистре сведений с предварительной установкой отбора. Рассмотрим неработающий код, который часто предлагают на форумах:


ТекущиеЗначение = РегистрыСведений.КодПечатиЯрлыка.СоздатьНаборЗаписей();
ТекущиеЗначение.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
ТекущиеЗначение.Прочитать();
Если ТекущиеЗначение.Количество() Тогда
    ТекущиеЗначение[0].Номенклатура = Выборка.Номенклатура;
    ТекущиеЗначение.КодПечати = КодПечати; // Ошибка!
Иначе
    ТекущиеЗначение.Добавить();
    ТекущиеЗначение.Номенклатура = Выборка.Номенклатура; // Ошибка!
    ТекущиеЗначение.КодПечати = КодПечати; // Ошибка!
КонецЕсли;
ТекущиеЗначение.Записать(Ложь);

Почему этот код вызывает ошибку валидации отбора при вызове метода Записать()? Дело в том, что когда мы устанавливаем отбор по какому-либо измерению (в данном случае Номенклатура), платформа 1С накладывает жесткое условие: все записи, которые будут сохранены в базу данных в рамках этого набора, обязаны иметь точно такое же значение данного измерения, которое указано в отборе.

Поскольку в ветке Иначе разработчик снова присваивает значения самому набору записей (ТекущиеЗначение.Номенклатура = ...), а не добавленной строке, в самом наборе фактически создается пустая строка. При записи система видит, что у новой строки значение измерения Номенклатура не заполнено (равно Неопределено или пустой ссылке), в то время как в отборе установлена конкретная номенклатура из выборки. Это несоответствие и вызывает аварийное завершение транзакции.

Решение №1: Исправленный и корректный вариант работы с Набором записей

Чтобы исправить код, нам необходимо четко разделять сам объект НаборЗаписей и строки, которые в него входят. Давайте посмотрим на правильную реализацию этого алгоритма:


// Создаем объект набора записей
НаборЗаписей = РегистрыСведений.КодПечатиЯрлыка.СоздатьНаборЗаписей();

// Устанавливаем отбор по ключевому измерению
НаборЗаписей.Отбор.Номенклатура.Установить(Выборка.Номенклатура);

// Читаем существующие записи из базы по данному отбору
НаборЗаписей.Прочитать();

Если НаборЗаписей.Количество() > 0 Тогда
    // Если запись уже существует, получаем ее (первую и единственную в данном отборе)
    ТекущаяЗапись = НаборЗаписей[0];
Иначе
    // Если записи нет, добавляем новую строку в набор
    ТекущаяЗапись = НаборЗаписей.Добавить();
    // Обязательно заполняем измерение, соответствующее установленному отбору
    ТекущаяЗапись.Номенклатура = Выборка.Номенклатура;
КонецЕсли;

// Заполняем ресурс значениями
ТекущаяЗапись.КодПечати = КодПечати;

// Записываем набор в базу данных с флагом замещения (Истина)
НаборЗаписей.Записать(Истина);

Обратите внимание: в конце мы используем метод Записать(Истина). Параметр Истина означает, что старые записи, соответствующие установленному отбору, будут полностью замещены новыми данными из нашего набора. Это гарантирует отсутствие дублей в регистре.

Решение №2: Использование Менеджера записи (Альтернативный и более простой подход)

Если вам необходимо работать индивидуально с единичными записями независимого регистра сведений, использование объекта НаборЗаписей может показаться избыточным и громоздким. В платформе 1С предусмотрен более простой и элегантный инструмент — РегистрСведенийМенеджерЗаписи.

Менеджер записи физически представляет собой ровно одну строку регистра. При работе с ним вам не нужно оперировать коллекциями, индексами элементов [0] и методами добавления строк. Платформа берет эту работу на себя.

Рассмотрим, как будет выглядеть код внутри нашего цикла при использовании менеджера записи:


// Создаем менеджер записи для регистра
МенеджерЗаписи = РегистрыСведений.КодПечатиЯрлыка.СоздатьМенеджерЗаписи();

// Заполняем ключевые измерения, по которым система будет искать запись в базе
МенеджерЗаписи.Номенклатура = Выборка.Номенклатура;

// Читаем запись. Если она существует, менеджер заполнится ее ресурсами из базы
МенеджерЗаписи.Прочитать();

// Независимо от того, существовала ли запись ранее, устанавливаем новое значение ресурса
МенеджерЗаписи.КодПечати = КодПечати;

// Метод Записать() автоматически создаст новую запись или обновит существующую
МенеджерЗаписи.Записать();

Преимущества использования Менеджера записи:

Решение №3: Оптимизация производительности (Пакетная запись)

Выполнение операций записи в базу данных внутри цикла выборки (например, Пока Выборка.Следующий() Цикл) — это классический антипаттерн в разработке 1С. Каждый вызов метода Записать() внутри цикла инициирует отдельное обращение к СУБД, открытие транзакции, блокировку таблиц и запись на диск.

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

Способ А. Обертывание цикла в явную транзакцию. Если логика решения все же требует построчной обработки, обязательно оберните цикл в одну общую транзакцию базы данных. Это позволит выполнить запись всех объектов за одно физическое обращение к жесткому диску СУБД:


НачатьТранзакцию();

Пока Выборка.Следующий() Цикл
    МенеджерЗаписи = РегистрыСведений.КодПечатиЯрлыка.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Номенклатура = Выборка.Номенклатура;
    МенеджерЗаписи.КодПечати = КодПечати;
    МенеджерЗаписи.Записать();
КонецЦикла;

ЗафиксироватьТранзакцию();

Способ Б. Групповая запись без отборов. Если вам нужно полностью перезаписать информацию по определенному списку номенклатуры, наиболее эффективным решением будет выгрузка всех результатов запроса в одну Таблицу Значений и последующая загрузка этой таблицы в один общий НаборЗаписей без точечной установки отборов (или с установкой отбора по более крупному разрезу, например по группе номенклатуры), после чего выполняется один-единственный вызов метода Записать().

Давайте подведем итог: всегда внимательно следите за синтаксисом имен полей вашего запроса и метаданных, четко разделяйте объекты коллекций и их элементы, а при работе с единичными записями отдавайте предпочтение объекту МенеджерЗаписи.

← На главную