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