При разработке функционала хранения связанных данных (например, адресов доставки контрагентов) начинающие разработчики нередко выбирают независимый непериодический регистр сведений. Однако при попытке программного добавления записей возникает типичная проблема: при одинаковом составе измерений запись либо затирает предыдущую, либо система выдает ошибку о нарушении уникальности данных. Разберем внутренние механизмы платформы 1С, выясним причину такого поведения и рассмотрим корректные пути решения.
Проанализируем структуру регистра сведений в реляционной СУБД. В непериодическом независимом регистре сведений совокупность всех измерений формирует первичный ключ таблицы базы данных. Это фундаментальное правило архитектуры 1С: на одно уникальное сочетание значений измерений может приходиться только один набор ресурсов и реквизитов. Платформа физически не может хранить две разные строки, у которых полностью совпадают значения всех измерений.
Рассмотрим, что происходит при выполнении типового программного кода работы с объектом НаборЗаписей:
НаборЗаписей = РегистрыСведений.АдресаДоставки.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Стр.Объект);
НаборЗаписей.Отбор.Перевозчик.Установить(Стр.Перевозчик);
НаборЗаписей.Отбор.Получатель.Установить(Стр.Получатель);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Объект = Стр.Объект;
НоваяЗапись.Перевозчик = Стр.Перевозчик;
НоваяЗапись.Получатель = Стр.Получатель;
НоваяЗапись.АдресПолучателя = Стр.АдресПолучателя;
НаборЗаписей.Записать(Ложь);
Разберем различия в поведении в зависимости от параметра замещения метода Записать():
НаборЗаписей.Записать(Истина) (или без параметров): параметр Замещение = Истина очищает в базе данных все существующие записи, попадающие под установленный отбор, и записывает те строки, которые находятся в наборе в памяти. Если перед добавлением строки не был вызван метод НаборЗаписей.Прочитать(), старые данные безвозвратно удаляются, и в регистре остается только одна вновь созданная строка.НаборЗаписей.Записать(Ложь): флаг замещения выключен, и платформа пытается вставить новую запись в таблицу СУБД без предварительного удаления старых строк. Если запись с такой комбинацией измерений уже присутствует, база данных возвращает ошибку нарушения уникальности первичного ключа.Если для бизнес-логики принципиально хранить несколько разных адресов для одной и той же связки контрагента, перевозчика и получателя именно в регистре сведений, необходимо обеспечить уникальность ключа записи. Для этого в состав измерений добавляют еще одно поле.
Существует два основных способа формирования такого измерения:
Число. Главный недостаток этого метода — отсутствие встроенного автоинкремента в регистрах сведений 1С. Разработчику приходится самостоятельно вычислять следующий свободный номер запросом к базе (через функцию МАКСИМУМ(НомерЗаписи) + 1). В многопользовательской среде это требует явной блокировки диапазона регистра через объект БлокировкаДанных, иначе при одновременной записи возникнет коллизия номеров.Строка (длиной 36 символов). При создании новой строки генерируется уникальный строковый GUID с помощью конструкции Строка(Новый УникальныйИдентификатор()). Такой подход полностью исключает риск дублирования ключей и не требует наложения тяжелых блокировок для расчета очередного номера.Обратим внимание на типы данных измерений. Использование неограниченных или слишком длинных строк в измерениях — грубая ошибка проектирования. В большинстве СУБД (например, Microsoft SQL Server) максимальный размер индекса первичного ключа ограничен 900 байтами. Если суммарная длина всех строковых измерений превысит допустимый предел, запись в регистр завершится аварийной системной ошибкой. Все длинные описательные поля (адреса, телефоны, комментарии) всегда должны размещаться в ресурсах или реквизитах регистра.
Когда требуется изменить или перезаписать строго одну запись регистра, использование набора записей с фильтрами часто бывает избыточным. Для работы с единичной записью в платформе 1С предусмотрен объект МенеджерЗаписи.
Посмотрим на пример правильного чтения и изменения строки через менеджер:
Менеджер = РегистрыСведений.АдресаДоставки.СоздатьМенеджерЗаписи();
Менеджер.Объект = Стр.Объект;
Менеджер.Перевозчик = Стр.Перевозчик;
Менеджер.Получатель = Стр.Получатель;
// Считываем текущую запись из базы данных
Менеджер.Прочитать();
Если Менеджер.Выбран() Тогда
// Запись найдена, обновляем нужный ресурс
Менеджер.ТелефонПолучателя = Стр.ТелефонПолучателя;
Менеджер.Записать();
КонецЕсли;
Метод Прочитать() загружает значения ресурсов и реквизитов по заданным ключевым измерениям, а свойство Выбран() показывает, существует ли уже такая строка в информационной базе.
Проанализируем прикладную задачу: хранение множества адресов доставки контрагента с возможностью их интерактивного редактирования прямо в форме. Использование регистра сведений в данном сценарии создает лишние сложности синхронизации данных. Платформа 1С предоставляет более подходящие инструменты:
АдресаДоставки, подчиненный справочнику Контрагенты. Справочник обеспечивает уникальность каждой записи за счет собственной ссылки (UUID). В нем можно хранить любое количество одинаковых по содержанию адресов, добавлять предопределенные реквизиты и стандартно выводить список на форму владельца без ручного программного кода записи наборов.АдресаДоставки непосредственно в справочник Контрагенты. Это полностью снимает вопросы транзакционной записи и синхронизации табличных полей формы.