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