При работе с регистрами сведений в среде 1С:Предприятие 8 начинающие разработчики часто сталкиваются с досадной ошибкой: «Поле объекта не обнаружено» при попытке установить отбор по какому-либо измерению в наборе записей. Ситуация становится особенно запутанной, когда измерение в конфигураторе точно есть, а программно обратиться к нему через свойство Отбор не получается. В этой статье мы подробно разберем, почему так происходит, и какими способами можно обойти это платформенное ограничение.
Для начала проанализируем техническую суть проблемы. В системе 1С существует два режима записи в регистр сведений: «Независимый» и «Подчинение регистратору». Если для регистра установлен режим «Подчинение регистратору», это означает, что жизненный цикл каждой записи жестко привязан к документу (регистратору). На уровне базы данных СУБД записи такого регистра физически сгруппированы по уникальному идентификатору документа.
Согласно документации (Синтаксис-помощнику), объект РегистрСведенийНаборЗаписей.Отбор для таких регистров имеет критическую особенность: отбор возможен только по регистратору. Платформа не позволяет напрямую отфильтровать записи по сотруднику, номенклатуре или складу на этапе чтения набора из базы данных. Если вы попытаетесь написать НаборЗаписей.Отбор.Сотрудник.Установить(Ссылка), система выдаст ошибку, так как объект Отбор в данном контексте содержит только одно доступное поле — Регистратор.
Рассмотрим пошагово, как решить задачу модификации конкретных строк в таком наборе.
Этот способ считается классическим и наиболее безопасным при работе непосредственно с объектом НаборЗаписей. Поскольку мы не можем ограничить выборку на уровне SQL-запроса через Отбор, нам придется прочитать все записи конкретного документа в оперативную память, а затем «вычистить» лишнее.
Проанализируем алгоритм действий:
Посмотрим на пример кода:
НаборЗаписей = РегистрыСведений.ПлановыеНачисленияРаботниковОрганизаций.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ТекущийРег.Регистратор);
НаборЗаписей.Прочитать();
// Создаем массив для хранения записей, которые подлежат удалению
УдаляемыеЗаписи = Новый Массив;
Для Каждого Запись Из НаборЗаписей Цикл
// Если запись не соответствует нашему критерию (например, другой сотрудник)
Если Запись.Сотрудник <> ТекущийСотрудник.Ссылка Тогда
УдаляемыеЗаписи.Добавить(Запись);
КонецЕсли;
КонецЦикла;
// Удаляем накопленные записи из самого набора
Для Каждого УдаляемаяЗапись Из УдаляемыеЗаписи Цикл
НаборЗаписей.Удалить(УдаляемаяЗапись);
КонецЦикла;
// Сохраняем измененный набор в базу данных
НаборЗаписей.Записать();
Важный нюанс: Обратите внимание, что мы не удаляем записи сразу внутри первого цикла. Это стандартное правило программирования в 1С: нельзя изменять коллекцию в процессе её обхода через Для Каждого, так как это приведет к сбиванию индексов и пропуску строк.
Если записей у одного регистратора очень много, ручной обход массива может быть не самым быстрым решением. В 1С есть мощный инструмент — ТаблицаЗначений, которая поддерживает метод Скопировать() со структурой фильтра. Это позволяет выполнить фильтрацию данных гораздо лаконичнее.
Рассмотрим этот вариант подробнее:
НаборЗаписей = РегистрыСведений.ПлановыеНачисленияРаботниковОрганизаций.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Reгистратор.Установить(ТекущийРег.Регистратор);
НаборЗаписей.Прочитать();
// Выгружаем все записи документа в таблицу значений
ТЗНабора = НаборЗаписей.Выгрузить();
// Оставляем в ТЗ только те строки, которые соответствуют нашему измерению
// Это работает как мгновенный фильтр
СтруктураФильтра = Новый Структура("Сотрудник", ТекущийСотрудник.Ссылка);
ТЗНабора = ТЗНабора.Скопировать(СтруктураФильтра);
// Загружаем отфильтрованные данные обратно в набор
НаборЗаписей.Загрузить(ТЗНабора);
НаборЗаписей.Записать();
Этот метод удобен тем, что вы можете передать в Структуру сразу несколько измерений, и платформа сама эффективно отберет нужные строки. Однако помните, что метод Записать() без параметров (или с параметром Истина) полностью перезаписывает движения документа по этому регистру. Если ваша ТЗНабора в результате фильтрации окажется пустой, вы удалите все записи документа по данному регистратору.
Выясним причину, по которой чтение набора может быть неоптимальным. Если в вашем документе (например, «Начисление зарплаты») тысячи сотрудников, а вам нужно поменять запись только по одному из них, команда НаборЗаписей.Прочитать() вытянет из базы данных все строки. Это создает нагрузку на сервер и сеть.
Проанализируем ситуацию: стоит ли вообще создавать набор, если записи по конкретному сотруднику в этом документе может и не быть? Для этого перед работой с набором рекомендуется выполнить легкий запрос.
Пример оптимизированного подхода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1
|ИЗ
| РегистрСведений.ПлановыеНачисленияРаботниковОрганизаций КАК Рег
|ГДЕ
| Рег.Регистратор = &Регистратор
| И Рег.Сотрудник = &Сотрудник";
Запрос.УстановитьПараметр("Регистратор", ТекущийРег.Регистратор);
Запрос.УстановитьПараметр("Сотрудник", ТекущийСотрудник);
Если НЕ Запрос.Выполнить().Пустой() Тогда
// Только если записи существуют, приступаем к работе с набором
// Здесь размещается один из методов, описанных выше
КонецЕсли;
Работа с наборами записей в транзакции требует особого внимания к блокировкам. Поскольку мы работаем с подчиненным регистром, крайне желательно использовать объект БлокировкаДанных. Это предотвратит ситуацию, когда два пользователя одновременно пытаются изменить записи одного и того же регистратора, что могло бы привести к потере данных или ошибкам СУБД.
Посмотрим, как правильно установить блокировку:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ПлановыеНачисленияРаботниковОрганизаций");
ЭлементБлокировки.УстановитьПараметр("Регистратор", ТекущийРег.Регистратор);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Далее следует чтение, фильтрация и запись набора
Мы выяснили, что в подчиненных регистрах сведений нельзя устанавливать отбор по измерениям напрямую через свойство Отбор набора записей. Это ограничение архитектуры 1С. Для решения задачи мы всегда должны:
ТаблицуЗначений для удаления лишних строк или поиска нужных.Записать() заменяет старые записи в базе новыми данными из набора.Использование Ключа Записи (RecordKey) может помочь для точечной идентификации строки, но для записи изменений в подчиненном регистре объект НаборЗаписей остается основным и самым надежным инструментом.