Как исправить ошибки при записи в регистр сведений 1С: «Поле объекта не обнаружено» и «Значение поля не соответствует установленному отбору»

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

Работа с регистрами сведений — одна из самых частых задач при разработке в среде «1С:Предприятие 8». Однако начинающие разработчики регулярно сталкиваются с неочевидными на первый взгляд ошибками при записи данных. В этой статье мы подробно разберем две популярные проблемы: ошибку обращения к свойству отбора Установить() и ошибку транзакции при несоответствии записываемых полей установленному отбору. Мы не просто исправим неработающий код с форума, но и проанализируем архитектурные ошибки, рассмотрим разницу между набором записей и менеджером записи, а также напишем оптимальный шаблон решения без критических уязвимостей производительности.

Причина первой ошибки: «Поле объекта не обнаружено (Установить)»

В начале обсуждения автор темы пытается установить отбор по полям регистра сведений:


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

При выполнении этого кода система выбрасывает исключение: Поле объекта не обнаружено (Установить). Выясним, почему так происходит.

В платформе 1С объект РегистрСведенийНаборЗаписей позволяет устанавливать фильтры (отборы) только по ключевым полям. К таким полям относятся:

  1. Измерения регистра — это главные разрезы, в которых хранятся уникальные комбинации данных. По ним платформа автоматически строит индексы СУБД.
  2. Период — доступен для фильтрации, если регистр сведений в конфигураторе определен как периодический.
  3. Регистратор — доступен только для регистров, подчиненных регистратору (с режимом записи «Подчинение регистратору»).

Если при вызове метода Установить() для конкретного свойства отбора вы получаете ошибку «Поле объекта не обнаружено», это означает, что в структуре метаданных вашего регистра это поле создано как Ресурс или Реквизит. Платформа физически не создает свойства в коллекции отборов для ресурсов и реквизитов независимых регистров сведений, так как по ним СУБД не может быстро отфильтровать данные при записи.

Решение: откройте конфигуратор, найдите ваш регистр сведений (в примере автора это РегистрацияКадровыхДокументов) и убедитесь, что поля Документ и Сотрудник находятся строго в ветке «Измерения», а не в «Ресурсах» или «Реквизитах».

Аналогично решается и проблема с ошибкой Поле объекта не обнаружено (Прочитать) (Сообщение 7). Метод Прочитать() доступен только для объекта типа РегистрСведенийНаборЗаписей. Если переменная была случайно переопределена, затерта или инициализирована неверно (например, через менеджер записи или как строка таблицы значений), этот метод вызовет ошибку.

---

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

После исправления структуры измерений автор темы сталкивается со следующей проблемой при проведении документа: Запись не верна! Значение поля не соответствует установленному отбору. Проанализируем причину этой ошибки.

Платформа 1С жестко контролирует целостность базы данных при записи наборов. Когда мы накладываем отбор на набор записей, мы сообщаем платформе: «Я хочу работать исключительно с той областью базы данных, которая соответствует этим критериям».

Рассмотрим фрагмент нерабочего кода автора:


// 1. Устанавливаем отбор по документу из выборки прошлых записей (например, Документ "Прием на работу №1")
НаборЗаписей.Отбор.Документ.Установить(ВыборкаЗаписейРегистра.Документ);

// 2. Добавляем новую строку в набор
НоваяЗапись = НаборЗаписей.Добавить(); 

// 3. Пытаемся присвоить полю "Документ" значение текущего проводимого документа (например, "Трудовой отпуск №5")
НоваяЗапись.Документ = Ссылка; 

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

---

Скрытые архитектурные проблемы кода на форуме

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

  1. Запрос внутри цикла (Антипаттерн N+1). В сообщениях 26 и 37 автор выполняет создание объекта Новый Запрос и метод Запрос.Выполнить() внутри цикла обхода выборки. Если выборка содержит хотя бы 100 строк, система совершит 100 отдельных обращений к СУБД. Это гарантирует медленную работу проведения документов и избыточную нагрузку на сервер приложений 1С.
  2. Чтение всей таблицы через СрезПоследних. Перед записью автор получает срез последних записей по всему регистру. Зачем сканировать всю таблицу СУБД, если нас интересуют только конкретные сотрудники из табличной части нашего проводимого документа?
  3. Непонимание логики перезаписи Набора Записей. Метод НаборЗаписей.Записать() работает по следующему алгоритму:
    • Платформа находит в БД все записи, соответствующие текущему отбору набора.
    • Полностью удаляет (очищает) эти записи в БД.
    • Записывает на их место те строки, которые мы добавили в коллекцию набора в памяти.
    Если вы установили отбор по «чужому» документу, прочитали его, добавили туда запись по текущему документу и вызвали Записать(), вы рискуете затереть или повредить чужие данные в базе данных.

---

Набор записей (НаборЗаписей) против Менеджера записи (МенеджерЗаписи)

Для работы с независимыми регистрами сведений платформа 1С предоставляет два различных инструмента. Выбор между ними зависит от бизнес-задачи.

Набор записей (РегистрСведенийНаборЗаписей)

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


НаборЗаписей = РегистрыСведений.РегистрацияКадровыхДокументов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Документ.Установить(Ссылка);
// Заполняем строки
// ...
НаборЗаписей.Записать();

При этом система автоматически очистит старые номера только по текущему документу (если мы его перепроводим) и запишет новые строки. Это безопасно и быстро.

Менеджер записи (РегистрСведенийМенеджерЗаписи)

Используется, когда необходимо точечно прочитать, изменить или удалить ровно одну уникальную запись в базе данных. Уникальность записи в регистре определяется полным ключом (все измерения + период, если регистр периодический). Менеджер записи работает медленнее при массовых операциях, но удобен для интерактивной работы.

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


Менеджер = РегистрыСведений.РегистрацияКадровыхДокументов.СоздатьМенеджерЗаписи();
Менеджер.Период = Дата;
Менеджер.Документ = Ссылка;
Менеджер.Сотрудник = ТекущийСотрудник;
Менеджер.Прочитать(); // Ищем строго одну запись по ключу в БД

Если Менеджер.Выбран() Тогда
    // Запись существует, можем обновить ресурс
    Менеджер.НомерПорядковый = НовыйНомер;
    Менеджер.Записать();
КонецЕсли;

---

Шаблон правильного решения задачи сквозной нумерации

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

Решим эту задачу с помощью одного оптимального запроса и записи данных единым набором записей по документу-регистратору:


Процедура ПрисвоитьПорядковыеНомераДокумента() Экспорт

    // Шаг 1. Одним запросом получаем данные табличной части документа,
    // левым соединением проверяем наличие уже присвоенных номеров в регистре
    // и отдельным пакетом получаем текущий максимальный номер в системе.
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    ТрудовыеОтпуска.Сотрудник КАК Сотрудник,
        |    ЕСТЬNULL(РегистрацияКадровых.НомерПорядковый, 0) КАК НомерВРегистре,
        |    ЕСТЬNULL(РегистрацияКадровых.БукваНомера, """") КАК БукваНомераВРегистре
        |ПОМЕСТИТЬ ВТ_СотрудникиДокумента
        |ИЗ
        |    Документ.ТрудовыеОтпускаОрганизаций.РаботникиОрганизации КАК ТрудовыеОтпуска
        |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РегистрацияКадровыхДокументов КАК РегистрацияКадровых
        |        ПО ТрудовыеОтпуска.Ссылка = РегистрацияКадровых.Документ
        |            И ТрудовыеОтпуска.Сотрудник = РегистрацияКадровых.Сотрудник
        |ГДЕ
        |    ТрудовыеОтпуска.Ссылка = &Ссылка
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    МАКСИМУМ(РегистрацияКадровыхДокументов.НомерПорядковый) КАК МаксНомер
        |ПОМЕСТИТЬ ВТ_МаксНомер
        |ИЗ
        |    РегистрСведений.РегистрацияКадровыхДокументов КАК РегистрацияКадровыхДокументов
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    ВТ_СотрудникиДокумента.Сотрудник КАК Сотрудник,
        |    ВТ_СотрудникиДокумента.НомерВРегистре КАК НомерВРегистре,
        |    ВТ_СотрудникиДокумента.БукваНомераВРегистре КАК БукваНомераВРегистре,
        |    ЕСТЬNULL(ВТ_МаксНомер.МаксНомер, 0) КАК ТекущийМаксимальныйНомер
        |ИЗ
        |    ВТ_СотрудникиДокумента КАК ВТ_СотрудникиДокумента,
        |    ВТ_МаксНомер КАК ВТ_МаксНомер";

    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    
    РезультатЗапроса = Запрос.Выполнить();
    Если РезультатЗапроса.Пустой() Тогда
        Возврат; // В документе нет сотрудников для обработки
    КонецЕсли;
    
    Выборка = РезультатЗапроса.Выбрать();
    
    // Инициализируем переменную для сквозного подсчета номеров в оперативной памяти
    НовыйПорядковыйНомер = 0;
    МаксимальныйНомерПолучен = Ложь;

    // Шаг 2. Создаем один набор записей с отбором строго по текущему документу.
    // При записи этот набор полностью перезапишет только те строки, которые относятся к нашей Ссылке.
    НаборЗаписей = РегистрыСведений.РегистрацияКадровыхДокументов.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Документ.Установить(Ссылка);
    
    Пока Выборка.Следующий() Цикл
        
        // Получаем максимальный номер из базы данных один раз при первом проходе цикла
        Если Не МаксимальныйНомерПолучен Тогда
            НовыйПорядковыйНомер = Выборка.ТекущийМаксимальныйНомер;
            МаксимальныйНомерПолучен = Истина;
        КонецЕсли;

        // Создаем новую запись в наборе
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Период     = Дата;
        НоваяЗапись.Документ   = Ссылка; // Значение строго соответствует нашему отбору!
        НоваяЗапись.Сотрудник  = Выборка.Сотрудник;
        НоваяЗапись.Автор      = ЭтотОбъект.Автор;
        НоваяЗапись.Отредактировано = Справочники.Пользователи.ПустаяСсылка();
        
        // Шаг 3. Анализируем, был ли уже назначен номер для этого сотрудника по этому документу
        Если Выборка.НомерВРегистре > 0 Тогда
            // Если документ перепроводится и номер уже был — сохраняем старый номер
            НоваяЗапись.НомерПорядковый = Выборка.НомерВРегистре;
            НоваяЗапись.БукваНомера     = Выборка.БукваНомераВРегистре;
        Иначе
            // Если сотрудник добавлен впервые — инкрементируем максимальный номер по регистру
            НовыйПорядковыйНомер = НовыйПорядковыйНомер + 1;
            НоваяЗапись.НомерПорядковый = НовыйПорядковыйНомер;
            НоваяЗапись.БукваНомера     = "А"; // Или ваша кастомная логика префиксов букв
        КонецЕсли;
        
    КонецЦикла;
    
    // Шаг 4. Записываем весь подготовленный набор в рамках одной быстрой транзакции.
    Попытка
        НаборЗаписей.Записать();
    Исключение
        ВызватьИсключение "Не удалось записать номера документов в регистр! Ошибка: " + ОписаниеОшибки();
    КонецПопытки;

КонецПроцедуры

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

← На главную