Почему метод Прочитать() в Менеджере записи возвращает ложь и как правильно работать с регистрами сведений в 1С

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

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

Анатомия проблемы: Почему Менеджер записи не находит существующую запись

Рассмотрим ситуацию, описанную в первом сообщении темы. Разработчик пытается прочитать запись регистра сведений "Штрихкоды", заполнив только два измерения — Владелец и ЕдиницаИзмерения, после чего метод Прочитать() не находит запись (свойство Выбран() возвращает Ложь), хотя визуально в режиме "1С:Предприятие" или через консоль запросов запись с такими значениями существует.

Давайте проанализируем, как работает объект РегистрСведенийМенеджерЗаписи под капотом платформы. Этот объект предназначен для работы с одной конкретной записью регистра сведений. Ключ этой записи формируется строго на основании значений всех измерений регистра. Если регистр сведений имеет три измерения (например, Владелец, ЕдиницаИзмерения и ХарактеристикаНоменклатуры), а разработчик заполнил только два из них перед вызовом метода Прочитать(), система делает следующее:

  1. Платформа подставляет пустые значения (значения по умолчанию) для всех незаполненных измерений. Для справочников это будет пустая ссылка соответствующего типа (Справочники.ХарактеристикиНоменклатуры.ПустаяСсылка()), для чисел — 0, для строк — пустая строка.
  2. Выполняется запрос к СУБД с отбором по абсолютно всем измерениям ключа.
  3. Если в базе данных нет записи, у которой измерение ХарактеристикаНоменклатуры пустое, СУБД возвращает пустой результат. Платформа фиксирует, что запись не найдена, и метод Выбран() возвращает значение Ложь.

Таким образом, чтобы успешно использовать метод Прочитать() у объекта РегистрСведенийМенеджерЗаписи, вам необходимо знать и явно присвоить значения абсолютно всем измерениям, существующим в данном регистре. В противном случае платформа будет искать запись с пустыми значениями в пропущенных измерениях.

Критическая ошибка: Использование метода Выбран() у Набора записей

В попытках решить проблему разработчики часто переходят на использование объекта РегистрСведенийНаборЗаписей, однако совершают другую грубую ошибку, аналогичную коду из Сообщения 15:


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

Если НовыйШК.Выбран Тогда // Ошибка времени выполнения!
    // Какая-то логика...
КонецЕсли;

Этот код вызовет ошибку времени выполнения (Runtime Error): "Метод объекта не обнаружен (Выбран)".

Дело в том, что объект РегистрСведенийНаборЗаписей представляет собой коллекцию записей. У него принципиально отсутствует метод или свойство Выбран(). Этот метод есть только у РегистрСведенийМенеджерЗаписи и у некоторых других объектов (например, у выборки справочника или документа).

Для того чтобы проверить, содержит ли прочитанный набор записей какие-либо данные, необходимо использовать проверку количества элементов в коллекции:


Если НовыйШК.Количество() > 0 Тогда
    // Записи по заданному отбору существуют в базе данных
КонецЕсли;

Почему при записи набора записей создается дубль, а старая запись не замещается

Разберем еще одно частое недоразумение, с которым столкнулся автор темы. В Сообщении 15 он жалуется, что при использовании набора записей существующая запись в регистре не замещается, а создается новая. Посмотрим на алгоритм, который привел к этой ошибке:

  1. Устанавливается частичный отбор (по номенклатуре и единице измерения).
  2. Вызывается метод Прочитать(). Набор заполняется существующей из базы данных записью (допустим, в ней была заполнена пустая характеристика).
  3. Вызывается метод Добавить(). Платформа добавляет в коллекцию набора вторую строку. Разработчик заполняет эту новую строку значениями, в том числе указывая конкретную характеристику.
  4. Вызывается метод Записать(Истина) с включенным режимом замещения.

В этот момент платформа отрабатывает штатно: она полностью удаляет из базы данных все записи, которые соответствовали установленному отбору (по номенклатуре и единице измерения). А затем записывает в базу все строки, которые находятся в объекте набора записей в оперативной памяти. Поскольку перед этим разработчик прочитал старую запись и добавил новую, в наборе оказалось две строки. Обе эти строки и записываются в базу данных. В результате мы получаем "размножение" записей вместо обновления.

Если ваша задача — изменить существующую запись, у вас есть два основных пути решения проблемы.

Вариант 1. Модификация прочитанного набора записей

Если вам обязательно нужно прочитать существующие записи, проанализировать их и изменить только определенные ресурсы или реквизиты, не меняя ключевые поля (измерения), нужно обходить прочитанный набор в цикле и редактировать существующие строки, а не добавлять новые через метод Добавить(). Рассмотрим пример:


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

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

НовыйШК.Записать(); // Метод Записать() по умолчанию работает в режиме замещения (Истина)

Вариант 2. Запись "вслепую" без предварительного чтения (Рекомендуемый способ)

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


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

// Метод Прочитать() НЕ вызываем! Коллекция набора пуста.

НоваяЗапись = НовыйШК.Добавить();
НоваяЗапись.Владелец = СтрокаДанных.Номенклатура;
НоваяЗапись.ЕдиницаИзмерения = СтрНайденнаяЕдиница.ЕдиницаВБазе;
НоваяЗапись.ХарактеристикаНоменклатуры = СтрокаДанных.ХарактеристикаНоменклатуры;
НоваяЗапись.Штрихкод = СтрНайденнаяЕдиница.Штрихкод;
НоваяЗапись.ТипШтрихкода = ОпределитьТипШтрихкода(СтрНайденнаяЕдиница.Штрихкод);
НоваяЗапись.Качество = Справочники.Качество.Новый;

// При вызове Записать() платформа сотрет в базе все штрихкоды этой номенклатуры
// для данной единицы измерения, и запишет только одну нашу новую строчку.
НовыйШК.Записать(Истина); 

Сравнение производительности и стандарты разработки 1С

Давайте проанализируем архитектурные особенности работы платформы "под капотом", которые напрямую влияют на производительность вашей системы, особенно при пакетной обработке данных (например, загрузке прайс-листов или штрихкодов из файлов Excel). Если вы получаете такие файлы от поставщиков регулярно, для их подготовки к импорту удобно использовать программу автоматического сбора и обработки прайс-листов с электронной почты или FTP.

Объект РегистрСведенийМенеджерЗаписи является высокоуровневой "оберткой". Когда вы вызываете метод Записать() у Менеджера записи, платформа внутри себя выполняет целый ряд неявных ресурсоемких операций:

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

Если вы выполняете обработку файлов в цикле и на каждой итерации вызываете МенеджерЗаписи.Записать(), база данных будет подвергаться огромной избыточной нагрузке. Каждая итерация — это отдельная транзакция записи, создание временных объектов и обращения к СУБД.

Золотое правило стандартов разработки 1С:ИТС:

Что делать, если нужно изменить ключевое поле (измерение) записи?

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

Если вы попытаетесь сделать это через МенеджерЗаписи следующим образом:


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

Если Менеджер.Выбран() Тогда
    Менеджер.ЕдиницаИзмерения = НоваяЕдиница; // Меняем измерение ключа
    Менеджер.Записать();
КонецЕсли;

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

Чтобы правильно изменить ключевое поле с помощью Менеджера записи, вам придется выполнить транзакционное удаление старой записи и последующее создание новой:


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

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

При использовании объекта РегистрСведенийНаборЗаписей с установленным отбором по старым измерениям эта задача решается гораздо лаконичнее, так как механизм замещения автоматически очистит старые данные по отбору при записи нового набора.

← На главную