При работе с платформой 1С:Предприятие часто возникают задачи, связанные с манипуляцией данными в регистрах сведений. Одной из таких задач является изменение значения поля, которое определено как измерение регистра. Однако, как показывает практика, прямое изменение измерения в существующей записи регистра сведений невозможно, что приводит к ошибкам. Рассмотрим подробнее, почему так происходит, и как мы можем решить эту проблему.
Давайте выясним причину, по которой мы не можем напрямую изменить поле, являющееся измерением регистра сведений. В 1С:Предприятии, измерения регистра сведений формируют уникальный ключ записи. Это означает, что комбинация значений всех измерений должна быть уникальной для каждой записи в регистре. Если мы попытаемся изменить значение измерения в уже существующей записи, система воспримет это не как изменение записи, а как попытку изменить ее уникальный идентификатор (ключ).
Представьте, что у вас есть запись с ключом (Измерение1=А, Измерение2=Б). Если вы попытаетесь изменить Измерение1 на В, система будет рассматривать это как попытку создать новую запись с ключом (Измерение1=В, Измерение2=Б) вместо существующей. Поскольку регистр сведений по своей природе не позволяет напрямую "переименовывать" ключи, платформа выдаст ошибку, например, "Запись не верна! Значение поля не соответствует установленному отбору" или "Попытка изменения поля, входящего в ключ записи, через отбор".
Для платформы изменение измерения фактически означает, что должна появиться новая запись с новым набором измерений, а старая запись с прежним набором измерений должна быть либо удалена, либо остаться без изменений, если мы хотим иметь две записи. Таким образом, наша задача сводится к тому, чтобы удалить старую запись и создать новую с измененным значением измерения.
Для решения этой проблемы мы можем использовать комбинацию объектов 1С:Предприятия: НаборЗаписей и МенеджерЗаписи. Этот подход позволяет нам гибко управлять данными, обходя ограничение на прямое изменение измерений. Разберем по шагам предложенное решение, которое было успешно применено в обсуждаемой ситуации.
Прежде всего, нам необходимо получить все записи регистра, которые мы хотим изменить. Мы будем использовать объект НаборЗаписей для чтения данных и последующего их удаления. В нашем случае, мы хотим изменить записи, у которых измерение КВыгрузке установлено в Истина.
Набор = РегистрыСведений.РеестрРамБалок.СоздатьНаборЗаписей();
Набор.Отбор.КВыгрузке.Установить(Истина);
Набор.Прочитать();
Здесь мы создаем НаборЗаписей для регистра РеестрРамБалок. Затем устанавливаем отбор по измерению КВыгрузке, указывая, что нас интересуют только те записи, где это измерение равно Истина. Метод Прочитать() загружает все соответствующие записи в наш набор.
После того как мы получили набор записей, мы можем итерироваться по ним и для каждой записи выполнить процедуру изменения. Поскольку мы не можем изменить измерение напрямую, мы создадим новую запись с нужным значением измерения.
Для Каждого Стр Из Набор Цикл
МЗ = РегистрыСведений.РеестрРамБалок.СоздатьМенеджерЗаписей();
ЗаполнитьЗначенияСвойств(МЗ, Стр);
МЗ.КВыгрузке = Ложь; // Снимаем галку к выгрузке, изменяя измерение
МЗ.Записать();
КонецЦикла;
В этом цикле для каждой записи Стр из нашего набора мы выполняем следующие действия:
МЗ = РегистрыСведений.РеестрРамБалок.СоздатьМенеджерЗаписей();. МенеджерЗаписей – это объект, который позволяет нам работать с одной конкретной записью регистра сведений.ЗаполнитьЗначенияСвойств(МЗ, Стр);. Эта удобная функция позволяет скопировать все значения свойств (измерений, ресурсов, реквизитов) из одной структуры (в данном случае из записи Стр) в другую (в наш МенеджерЗаписей МЗ). Это гарантирует, что все остальные измерения, а также ресурсы и реквизиты, будут скопированы без изменений.МЗ.КВыгрузке = Ложь;. Здесь мы устанавливаем новое значение для нашего измерения КВыгрузке. Важно понимать, что в этот момент в МЗ формируется новый уникальный ключ записи.МЗ.Записать();. Этот метод попытается найти запись с ключом, соответствующим значениям измерений в МЗ. Если такой записи нет, будет создана новая запись. Если запись с таким же набором измерений (т.е. с новым ключом, где КВыгрузке = Ложь, а все остальные измерения совпадают с исходными) уже существует, она будет перезаписана. В нашем случае, поскольку мы меняем булево измерение с Истина на Ложь, скорее всего будет создана новая уникальная запись.После того как мы создали все новые записи с измененным значением измерения, нам необходимо удалить исходные записи, у которых КВыгрузке было Истина. Это мы сделаем с помощью нашего же объекта НаборЗаписей.
Набор.Очистить();
Набор.Записать();
Метод Набор.Очистить() удаляет все записи из памяти нашего объекта Набор. Затем, когда мы вызываем Набор.Записать(), система понимает, что все записи, которые были прочитаны в этот набор (то есть все записи с КВыгрузке = Истина, соответствующие нашему отбору), должны быть удалены из базы данных. Этот подход является элегантным способом массового удаления записей, соответствующих определенному отбору.
Таким образом, полный код решения выглядит следующим образом:
Набор = РегистрыСведений.РеестрРамБалок.СоздатьНаборЗаписей();
Набор.Отбор.КВыгрузке.Установить(Истина);
Набор.Прочитать();
Для Каждого Стр Из Набор Цикл
МЗ = РегистрыСведений.РеестрРамБалок.СоздатьМенеджерЗаписей();
ЗаполнитьЗначенияСвойств(МЗ, Стр);
МЗ.КВыгрузке = Ложь; // Изменяем значение измерения
МЗ.Записать();
КонецЦикла;
Набор.Очистить(); // Удаляем записи из набора в памяти
Набор.Записать(); // Удаляем записи из базы данных, которые были прочитаны в набор
Этот подход гарантирует, что мы эффективно "меняем" значение измерения, удаляя старую запись и создавая новую, сохраняя при этом целостность данных в регистре.
Существует еще один способ решить эту задачу, который был предложен в обсуждении. Он также основан на принципах создания новых записей и удаления старых, но использует Запрос для выборки данных. Давайте разберем его.
Сначала мы формируем запрос, который выберет все необходимые нам данные из регистра сведений, включая все измерения, ресурсы и реквизиты. Важно выбрать все поля, чтобы не потерять информацию.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Рег.*
|ИЗ
| РегистрСведений.РеестрРамБалок КАК Рег
|ГДЕ
| Рег.КВыгрузке = ИСТИНА";
Выборка = Запрос.Выполнить().Выбрать();
В этом запросе мы выбираем все поля (`Рег.*`) из нашего регистра РеестрРамБалок, где измерение КВыгрузке равно ИСТИНА. Результатом выполнения запроса будет объект Выборка, по которому мы можем итерироваться.
Далее мы обходим полученную выборку и для каждой строки создаем новую запись с измененным значением измерения КВыгрузке.
Пока Выборка.Следующий() Цикл
МЗ = РегистрыСведений.РеестрРамБалок.СоздатьМенеджерЗаписей();
ЗаполнитьЗначенияСвойств(МЗ, Выборка);
МЗ.КВыгрузке = Ложь; // Устанавливаем новое значение измерения
МЗ.Записать();
КонецЦикла;
Как и в предыдущем решении, мы используем МенеджерЗаписей для создания новой записи. Функция ЗаполнитьЗначенияСвойств(МЗ, Выборка) копирует все значения из текущей строки выборки в МенеджерЗаписей. Затем мы меняем значение КВыгрузке на Ложь и записываем МенеджерЗаписей, что приводит к созданию новой записи с измененным ключом.
В отличие от предыдущего метода, где НаборЗаписей.Очистить() удобно удалял все отобранные записи, при использовании запроса нам потребуется явно удалять старые записи. Для этого после создания новых записей, мы можем повторно выполнить запрос, но теперь использовать МенеджерЗаписей для удаления каждой исходной записи.
// Повторная выборка для удаления старых записей
ЗапросДляУдаления = Новый Запрос;
ЗапросДляУдаления.Текст =
"ВЫБРАТЬ
| Рег.*
|ИЗ
| РегистрСведений.РеестрРамБалок КАК Рег
|ГДЕ
| Рег.КВыгрузке = ИСТИНА";
ВыборкаДляУдаления = ЗапросДляУдаления.Выполнить().Выбрать();
Пока ВыборкаДляУдаления.Следующий() Цикл
МЗ_Удаление = РегистрыСведений.РеестрРамБалок.СоздатьМенеджерЗаписей();
ЗаполнитьЗначенияСвойств(МЗ_Удаление, ВыборкаДляУдаления);
// Для удаления записи нужно, чтобы МенеджерЗаписей был "прочитан" по существующему ключу.
// Если ЗаполнитьЗначенияСвойств корректно установил все измерения, то можно удалить.
// Если же нужны только измерения, то можно сделать так:
// МЗ_Удаление.Измерение1 = ВыборкаДляУдаления.Измерение1;
// ...
// МЗ_Удаление.КВыгрузке = Истина;
// МЗ_Удаление.Прочитать(); // Считываем существующую запись
МЗ_Удаление.Удалить(); // Удаляем запись с текущим ключом
КонецЦикла;
Этот подход требует более внимательного управления процессом удаления, так как необходимо точно указать ключ записи, которую мы хотим удалить. Это может быть более ресурсоемким и сложным в реализации, чем использование НаборЗаписей.Очистить(), особенно если измерения сложны.
Мы, как разработчики, всегда должны думать о производительности и правильности архитектуры. Давайте проанализируем эти аспекты.
НаборЗаписей независимых регистров сведений. Это режимы РежимЗамещения.Слияние и РежимЗамещения.Удаление.
РежимЗамещения.Слияние позволяет обновлять существующие записи и добавлять новые более эффективно.РежимЗамещения.Удаление может быть использован для массового удаления записей, соответствующих отбору, что потенциально более производительно, чем итерационное удаление через менеджер записи. Однако, в контексте нашей задачи, использование Набор.Очистить() в связке с Набор.Записать() уже является достаточно эффективным методом для удаления отобранных записей.НаборамиЗаписей. Это позволяет собрать изменения в одном объекте и зафиксировать их одной транзакцией, что минимизирует количество обращений к базе данных.В данном случае, измерение КВыгрузке имеет булевый тип. Давайте задумаемся, насколько целесообразно использовать булево поле в качестве измерения регистра сведений, особенно если оно фактически отражает статус записи (например, "к выгрузке" или "не к выгрузке").
КВыгрузке является единственным отличием между записями, то две записи могут иметь одинаковые значения по всем остальным измерениям, отличаясь только этим флагом. Например:
Ресурс (или другой реквизит) также должен отличаться в зависимости от состояния КВыгрузке. Однако, если разница только в булевом флаге, и значения остальных ресурсов/реквизитов должны быть одинаковыми, то это усложняет логику.КВыгрузке является числовым, булевым или другим значением, которое "характеризует" набор измерений, то оно может быть ресурсом. Ресурсы - это данные, хранящиеся на пересечении измерений. Для периодических регистров ресурсов, они возвращаются методами СрезПервых() и СрезПоследних().КВыгрузке является дополнительной, неключевой информацией о записи, то его можно сделать реквизитом. Реквизиты – это дополнительные атрибуты каждой записи. Изменение ресурса или реквизита не приводит к изменению ключа записи, и их можно модифицировать напрямую, без удаления и создания новых записей.Использование булева поля как измерения может быть оправдано, если оно действительно является частью уникального ключа, и мы ожидаем наличие двух "версий" записи (например, "активная" и "архивная") с одинаковыми остальными измерениями. Но если это просто статус, то вынос его в ресурсы или реквизиты упростит работу с регистром в будущем.
КВыгрузке = Ложь и теми же остальными измерениями), то МЗ.Записать() перезапишет ее, а не создаст дубликат.В заключение, проблема изменения поля-измерения в регистре сведений 1С:Предприятия является распространенной, и ее решение, хотя и не прямолинейно, вполне достижимо с использованием стандартных механизмов платформы. Мы рассмотрели эффективный метод через удаление старых и создание новых записей, а также проанализировали важные аспекты производительности и архитектуры, которые помогут вам создавать более надежные и производительные решения.