При разработке и рефакторинге конфигураций 1С:Предприятие 8.3 программисты часто сталкиваются с различными подходами к работе с данными, в том числе с регистрами сведений. Иногда можно обнаружить код, который, на первый взгляд, выполняет нужную функцию, но при более глубоком рассмотрении оказывается неэффективным и избыточным. Давайте рассмотрим одну из таких ситуаций, когда некорректное использование объектов НаборЗаписей и МенеджерЗаписи приводит к лишним операциям с базой данных.
Предположим, перед нами стоит задача установить или обновить значение свойства для конкретного объекта в регистре сведений. В одном из проектов мы обнаружили следующий код, который использовался для этой цели. Он повторялся для каждого свойства, что делало проблему масштабируемой.
Вот как выглядел типичный фрагмент:
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя);
НаборЗаписей.Отбор.Свойство.Установить(СтатусCOF000047);
НаборЗаписей.Очистить();
НаборЗаписей.Записать();
Запись = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
Запись.Объект = ДокЗаказаПокупателя;
Запись.Свойство = СтатусCOF000047;
Запись.Значение = Справочники.А_СтатусыЗаказов.НайтиПоНаименованию("Возврат");
Запись.Записать();
А вот более обширный пример, демонстрирующий повторение этой логики:
СтатусCOF000050 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000050"); //Строка 28 - [N] Новый
СтатусCOF000047 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000047"); //СпрСтатус 29 - Новый
СтатусCOF000027 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000027"); //Строка 30 - N
Если Строка(Статус) = "Новый" Тогда
//28 строка
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя);
НаборЗаписей.Отбор.Свойство.Установить(СтатусCOF000050);
НаборЗаписей.Очистить();
НаборЗаписей.Записать();
Запись = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
Запись.Объект = ДокЗаказаПокупателя;
Запись.Свойство = СтатусCOF000050;
Запись.Значение = "[N] Новый";
Запись.Записать();
//29 строка
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя);
НаборЗаписей.Отбор.Свойство.Установить(СтатусCOF000047);
НаборЗаписей.Очистить();
НаборЗаписей.Записать();
Запись = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
Запись.Объект = ДокЗаказаПокупателя;
Запись.Свойство = СтатусCOF000047;
Запись.Значение = Справочники.А_СтатусыЗаказов.НайтиПоНаименованию("Новый");
Запись.Записать();
//30 строка
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя);
НаборЗаписей.Отбор.Свойство.Установить(СтатусCOF000027);
НаборЗаписей.Очистить();
НаборЗаписей.Записать();
Запись = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
Запись.Объект = ДокЗаказаПокупателя;
Запись.Свойство = СтатусCOF000027;
Запись.Значение = "N";
Запись.Записать();
ИначеЕсли Строка(Статус) = "В обработке" Тогда
На первый взгляд, код может показаться рабочим, ведь он действительно устанавливает нужное значение. Однако если присмотреться, мы заметим явную избыточность: сначала записи по отбору очищаются с помощью НаборЗаписей, а затем новая или обновленная запись создается с помощью МенеджераЗаписи. Это приводит к двойному обращению к базе данных там, где достаточно одного.
Чтобы понять, почему представленный код неоптимален, давайте разберем, как работают объекты НаборЗаписей и МенеджерЗаписи в платформе 1С:Предприятие 8.3.
Объект НаборЗаписей:
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя); НаборЗаписей.Отбор.Свойство.Установить(СтатусCOF000047); — эти строки определяют, какие записи из регистра будут затрагиваться операциями с этим набором.НаборЗаписей.Очистить(); — эта команда удаляет *все* записи из самого объекта НаборЗаписей. После этого объект НаборЗаписей становится пустым.НаборЗаписей.Записать(); — этот метод применяет изменения, накопленные в объекте НаборЗаписей, к базе данных. Если в наборе установлен отбор, и сам набор пуст (как после Очистить()), то при вызове Записать() из базы данных удаляются все записи, соответствующие установленному отбору. То есть, происходит операция удаления. Если отбор не установлен и набор пуст, то Записать() приведет к полной очистке всего регистра!Объект МенеджерЗаписи:
Запись = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи(); — создаем объект для работы с одной записью регистра.Запись.Объект = ДокЗаказаПокупателя; Запись.Свойство = СтатусCOF000047; Запись.Значение = Справочники.А_СтатусыЗаказов.НайтиПоНаименованию("Возврат");Запись.Записать(); — этот метод осуществляет операцию записи одной записи в базу данных. Если в регистре уже существует запись с такими же значениями измерений (ключевых полей), то эта существующая запись будет обновлена. Если такой записи нет, она будет добавлена как новая.Таким образом, в представленном проблемном коде мы наблюдаем следующую последовательность действий:
НаборЗаписей, а затем командой НаборЗаписей.Записать() удаляют эти записи из базы данных. Это первое обращение к базе данных.МенеджерЗаписи, заполняют его и командой Запись.Записать() добавляют или обновляют (в данном случае, скорее всего, добавляют, так как предыдущим шагом записи были удалены) одну запись в базе данных. Это второе обращение к базе данных.Мы видим, что для выполнения одной задачи (обновления или создания одной записи) выполняются две избыточные операции с базой данных: сначала удаление, потом добавление/обновление. Это не только замедляет работу системы, но и делает код менее читаемым и сложным для понимания.
МенеджераЗаписи и НабораЗаписейДля эффективной работы с регистрами сведений важно понимать назначение и сценарии использования МенеджераЗаписи и НабораЗаписей.
МенеджерЗаписиКогда нам нужно создать, обновить или прочитать одну уникальную запись регистра сведений (уникальность определяется набором значений всех измерений), наиболее подходящим инструментом является МенеджерЗаписи. Он сам позаботится о том, чтобы либо обновить существующую запись (если измерения совпадают), либо создать новую.
Пример корректного использования МенеджераЗаписи:
// Определяем значения для измерений и ресурса
ДокЗаказаПокупателя = Документы.ЗаказПокупателя.НайтиПоНомер("00000001", Дата("20230101")); // Пример получения документа
СтатусCOF000047 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000047");
ЗначениеСтатуса = Справочники.А_СтатусыЗаказов.НайтиПоНаименованию("Возврат");
// Создаем менеджер записи
Запись = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
// Заполняем измерения
Запись.Объект = ДокЗаказаПокупателя;
Запись.Свойство = СтатусCOF000047;
// Заполняем ресурс
Запись.Значение = ЗначениеСтатуса;
// Записываем запись в регистр.
// Если запись с такими же измерениями уже существует, она будет обновлена.
// Если не существует, будет создана новая.
Запись.Записать();
Такой подход является единственно верным и эффективным для операций с одиночной записью.
НаборЗаписейЕсли нам необходимо работать сразу с множеством записей регистра сведений, например, массово добавлять новые записи, изменять существующие или удалять группу записей по определенному условию, тогда нам потребуется НаборЗаписей.
Важное поведение НаборЗаписей.Записать():
Метод НаборЗаписей.Записать() по умолчанию работает в режиме замещения. Это означает, что:
НабораЗаписей установлен отбор, то при вызове Записать() все существующие записи в базе данных, соответствующие этому отбору, будут удалены, а затем вместо них будут записаны все записи, которые в данный момент находятся в объекте НаборЗаписей.Записать() приведет к полной очистке всего регистра, а затем к записи всех записей из объекта НаборЗаписей.Таким образом, если ваша цель — обновить или добавить несколько записей, и вы используете НаборЗаписей, вам не нужно предварительно вызывать Очистить(), а затем Записать(). Достаточно сформировать набор записей с нужным отбором и новыми/измененными данными, а затем однократно вызвать Записать().
Пример корректного использования НабораЗаписей для массового обновления/добавления:
// Определяем общие измерения для отбора
ДокЗаказаПокупателя = Документы.ЗаказПокупателя.НайтиПоНомер("00000001", Дата("20230101")); // Пример получения документа
// Определяем свойства
СтатусCOF000050 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000050");
СтатусCOF000047 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000047");
СтатусCOF000027 = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("COF000027");
// Создаем набор записей
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя); // Устанавливаем отбор по объекту
// Читаем текущие записи по отбору (если нужно их изменить)
// НаборЗаписей.Прочитать();
// Если цель - полностью перезаписать свойства объекта, то Прочитать() не требуется
// Добавляем или изменяем записи в наборе
// Запись 1
НоваяЗапись1 = НаборЗаписей.Добавить();
НоваяЗапись1.Объект = ДокЗаказаПокупателя;
НоваяЗапись1.Свойство = СтатусCOF000050;
НоваяЗапись1.Значение = "[N] Новый";
// Запись 2
НоваяЗапись2 = НаборЗаписей.Добавить();
НоваяЗапись2.Объект = ДокЗаказаПокупателя;
НоваяЗапись2.Свойство = СтатусCOF000047;
НоваяЗапись2.Значение = Справочники.А_СтатусыЗаказов.НайтиПоНаименованию("Новый");
// Запись 3
НоваяЗапись3 = НаборЗаписей.Добавить();
НоваяЗапись3.Объект = ДокЗаказаПокупателя;
НоваяЗапись3.Свойство = СтатусCOF000027;
НоваяЗапись3.Значение = "N";
// Записываем набор в регистр.
// Все записи регистра, соответствующие отбору (по ДокЗаказаПокупателя),
// будут заменены на записи из НабораЗаписей.
НаборЗаписей.Записать();
В этом примере мы установили отбор только по Объекту. Это означает, что все записи для этого ДокЗаказаПокупателя будут удалены и заменены новыми, которые мы добавили в НаборЗаписей. Если бы мы хотели влиять только на конкретное свойство, то отбор должен был бы включать и это свойство, но тогда бы пришлось работать с МенеджеромЗаписи, как показано выше, так как это операция над одной записью.
Исходя из нашего анализа, мы можем сформулировать следующие рекомендации для написания чистого и эффективного кода при работе с регистрами сведений:
МенеджерЗаписи: Если ваша задача — установить или изменить значение одного ресурса для одной записи регистра сведений, где уникальность определяется всеми измерениями, используйте МенеджерЗаписи. Это наиболее прямой и производительный способ.
НаборЗаписей: Если вам нужно массово добавить, изменить или удалить несколько записей, объединенных общим отбором, используйте НаборЗаписей. Сформируйте все необходимые записи в наборе, а затем однократно вызовите Записать().
Очистить(): Метод Очистить() у НабораЗаписей редко требуется в комбинации с последующим добавлением записей, так как Записать() сам по себе имеет замещающее поведение по умолчанию. Используйте Очистить() только если ваша единственная цель — удалить все записи по установленному отбору, не добавляя ничего взамен.
НабораЗаписей. Они позволяют более тонко управлять тем, как записи в наборе взаимодействуют с существующими записями в базе данных.
РежимЗамещения.Добавление: Добавляет новые записи, игнорируя существующие.РежимЗамещения.Замещение (по умолчанию): Удаляет по отбору и записывает все записи из набора.РежимЗамещения.Слияние: Обновляет записи с совпадающими ключевыми полями и добавляет новые. Это часто наиболее оптимальный режим, когда требуется обновить часть записей и добавить новые без полной перезаписи всего сегмента.РежимЗамещения.Удаление: Удаляет записи, совпадающие по ключевым полям с записями в наборе.Использование РежимЗамещения.Слияние, например, может значительно оптимизировать операции, когда изменяется лишь небольшая часть записей в большом сегменте данных.
Пример использования РежимЗамещения.Слияние:
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ДокЗаказаПокупателя);
// ... Добавляем или изменяем записи в НаборЗаписей ...
НаборЗаписей.Записать(Истина, РежимЗамещения.Слияние);
В мире разработки 1С:Предприятие 8.3 стремление к чистому, понятному и эффективному коду — это не просто прихоть, а необходимость. Неэффективные конструкции, подобные рассмотренной, могут незаметно накапливаться, приводя к снижению производительности системы и усложнению ее поддержки. При рефакторинге и разработке нового функционала всегда старайтесь использовать инструменты платформы в соответствии с их прямым назначением, отдавая предпочтение МенеджеруЗаписи для единичных операций и НаборуЗаписей для пакетных, с учетом современных возможностей платформы, таких как режимы замещения. Это позволит нам создавать более производительные и поддерживаемые решения.