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