Создание и изменение элементов справочника из кода 1С 8.3
Содержание
Чтобы записать справочник программно, получите объект справочника на сервере, заполните реквизиты и вызовите Записать(). Новый элемент создают через Справочники.ИмяСправочника.СоздатьЭлемент(), существующий открывают через Ссылка.ПолучитьОбъект(). Отдельного метода для записи «нового» элемента у объекта справочника нет: и новый, и изменённый объект пишутся одним и тем же Записать().
Проблемы обычно начинаются не с самого метода, а вокруг него. Код вызван на клиенте, дубли по наименованию, пустые обязательные реквизиты, чужая блокировка, медленная загрузка 20 тысяч позиций. Всё это разобрано ниже на примерах для платформы 1С:Предприятие 8.3 и управляемых форм. Имена справочников и реквизитов в коде условные, подставьте свои.
Объект, ссылка и выборка: с чем работает код записи
В 1С у справочника есть несколько «представлений», и путать их — главный источник ошибок новичков.
| Что | Тип | Можно записать? | Где доступно |
|---|---|---|---|
| Ссылка на элемент | СправочникСсылка.Номенклатура | Нет, только читать | Клиент и сервер |
| Объект элемента | СправочникОбъект.Номенклатура | Да, методом Записать() | Только сервер |
| Выборка | СправочникВыборка.Номенклатура | Нет, но даёт объект через ПолучитьОбъект() | Только сервер |
| Данные формы | ДанныеФормыСтруктура | Через форму или после преобразования в объект | Клиент и сервер |
| Менеджер | СправочникМенеджер.Номенклатура | Нет, создаёт объекты и ищет ссылки | Только сервер |
Отсюда три правила:
- 🔹 Записать можно только объект. Через ссылку поменять реквизит нельзя:
Ссылка.Наименование = "..."вызовет ошибку. - 🔹 Объект живёт на сервере. В модуле формы код записи помечают
&НаСервереили&НаСервереБезКонтекста, в общем модуле — включают флажок «Сервер». - 🔹
Записать()у справочника вызывается без параметров. Режимы записи и проведения есть у документов, у справочников их нет.
При вызове Записать() платформа выполняет обработчики модуля объекта ПередЗаписью и ПриЗаписи, а также подписки на события. Если где-то в них выставлен Отказ = Истина, метод вызывает исключение, и элемент не записывается.
Как создать новый элемент справочника кодом
Базовый пример: функция создаёт позицию номенклатуры и возвращает ссылку на неё.
&НаСервереБезКонтекста
Функция СоздатьНоменклатуру(Наименование, Артикул, Папка)
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = Наименование;
НовыйЭлемент.Артикул = Артикул;
НовыйЭлемент.Родитель = Папка;
НовыйЭлемент.Записать();
Возврат НовыйЭлемент.Ссылка;
КонецФункции
Код элемента
Если у справочника в конфигураторе включена автонумерация, а поле Код вы не заполнили, платформа присвоит код сама при записи. Когда код нужен заранее, например для вывода в лог до записи, вызовите НовыйЭлемент.УстановитьНовыйКод().
Свой код задавайте явно только при загрузке из внешней системы, где коды уже есть. Тогда следите за длиной: код длиннее, чем задано в свойствах справочника, не поместится.
Группы, владельцы и табличные части
- 📁 Группа в иерархическом справочнике создаётся методом
СоздатьГруппу(). Элемент кладут в группу через свойствоРодитель. - 🔗 Подчинённый справочник (например, договоры контрагента) требует заполнить
Владелец. Без владельца элемент не запишется. - 📋 Табличная часть заполняется до записи:
НоваяСтрока = НовыйЭлемент.ИмяТабличнойЧасти.Добавить();, затем реквизиты строки.
НоваяГруппа = Справочники.Номенклатура.СоздатьГруппу();
НоваяГруппа.Наименование = "Канцтовары";
НоваяГруппа.Записать();
Договор = Справочники.ДоговорыКонтрагентов.СоздатьЭлемент();
Договор.Владелец = СсылкаНаКонтрагента;
Договор.Наименование = "Основной договор";
Договор.Записать();
Ссылка до записи
Иногда ссылку нужно знать до записи: например, проставить её в другом объекте в той же транзакции. Для этого есть пара методов: Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор) создаёт ссылку, а НовыйЭлемент.УстановитьСсылкуНового(Ссылка) закрепляет её за объектом. Этот же приём используют при загрузке из другой базы, чтобы сохранить исходные GUID элементов.
Изменение существующих элементов: по ссылке и в цикле по выборке
Чтобы изменить один элемент, получите объект по ссылке, поменяйте реквизит и запишите:
&НаСервереБезКонтекста
Процедура ПереименоватьЭлемент(Ссылка, НовоеНаименование)
Объект = Ссылка.ПолучитьОбъект();
Если Объект = Неопределено Тогда
ВызватьИсключение "Элемент не найден в базе";
КонецЕсли;
Объект.Наименование = НовоеНаименование;
Объект.Записать();
КонецПроцедуры
Проверка на Неопределено нужна для «битых» ссылок: если элемент удалили, ПолучитьОбъект() вернёт не объект, а Неопределено.
Поиск элемента перед изменением
У менеджера справочника есть готовые методы поиска:
НайтиПоКоду(Код)— по коду;НайтиПоНаименованию(Наименование, Истина)— второй параметр включает точное совпадение;НайтиПоРеквизиту("ИНН", ЗначениеИНН)— по реквизиту.
Все три возвращают ссылку. Если ничего не найдено, это пустая ссылка, проверяйте её через Ссылка.Пустая(). При нескольких совпадениях метод вернёт одно из них, какое — не гарантируется. Для реквизитов, где дубли возможны, надёжнее запрос.
Групповая обработка через запрос
Для изменения многих элементов отберите ссылки запросом, а объекты получайте по одному:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Родитель = &Папка
| И НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления";
Запрос.УстановитьПараметр("Папка", Папка);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Объект = Выборка.Ссылка.ПолучитьОбъект();
Объект.ВидНоменклатуры = НовыйВид;
Объект.Записать();
КонецЦикла;
Не тяните в запрос все реквизиты, если меняете один. Объект всё равно перечитывается целиком при ПолучитьОбъект(), а лишние поля только нагружают сервер.
Если условие простое, можно обойтись без запроса: Справочники.Номенклатура.Выбрать(Папка) даёт выборку элементов указанного родителя, у неё тоже есть ПолучитьОбъект().
Проверки перед записью: заполнение, уникальность кода, дубли
Программная запись не делает того, что делает форма. Проверку заполнения и контроль дублей по смыслу придётся вызывать самим.
Заполнение обязательных реквизитов
Проверка заполнения (свойство «Проверка заполнения» у реквизитов и обработчик ОбработкаПроверкиЗаполнения) при Записать() из кода не запускается. Её вызывает форма при интерактивной записи. В коде вызывайте явно:
Если Не НовыйЭлемент.ПроверитьЗаполнение() Тогда
ВызватьИсключение "Не заполнены обязательные реквизиты";
КонецЕсли;
НовыйЭлемент.Записать();
Метод вернёт Ложь и добавит сообщения пользователю о каждом незаполненном поле.
Уникальность кода
Если в свойствах справочника включён контроль уникальности, запись элемента с уже занятым кодом вызовет исключение о неуникальном значении поля «Код». Учтите область контроля: в иерархическом справочнике код может проверяться в пределах всего справочника или только внутри подчинения, в подчинённом справочнике — внутри владельца. Это видно в свойствах объекта в конфигураторе.
Дубли по наименованию и реквизитам
По наименованию, ИНН или артикулу платформа уникальность не контролирует. Проверка «найти, и если нет — создать» без блокировки ломается, когда два сеанса выполняют её одновременно: оба не находят элемент и оба его создают.
Надёжная схема — поиск и запись в одной транзакции под управляемой блокировкой:
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Контрагенты");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
Ссылка = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ИНН);
Если Ссылка.Пустая() Тогда
НовыйЭлемент = Справочники.Контрагенты.СоздатьЭлемент();
НовыйЭлемент.Наименование = Наименование;
НовыйЭлемент.ИНН = ИНН;
НовыйЭлемент.Записать();
Ссылка = НовыйЭлемент.Ссылка;
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Блокировка без указания полей закрывает на запись весь справочник до конца транзакции. Держите транзакцию короткой, иначе другие пользователи будут ждать, а при долгом ожидании получат ошибку блокировки.
Если в типовой конфигурации уже есть механизм поиска дублей (например, проверка контрагента по ИНН), лучше вызвать его, чем писать свой.
Запись справочника из управляемой формы и с клиента
На клиенте нет ни менеджеров справочников, ни объектов. Вызов Справочники.Номенклатура.СоздатьЭлемент() в клиентской процедуре даст ошибку Переменная не определена (Справочники). Решение одно — передать управление на сервер.
Создание элемента по кнопке
&НаКлиенте
Процедура СоздатьЭлементКоманда(Команда)
НоваяСсылка = СоздатьЭлементНаСервере(Наименование);
ОповеститьОбИзменении(НоваяСсылка);
КонецПроцедуры
&НаСервереБезКонтекста
Функция СоздатьЭлементНаСервере(Наименование)
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = Наименование;
НовыйЭлемент.Записать();
Возврат НовыйЭлемент.Ссылка;
КонецФункции
&НаСервереБезКонтекста не тащит данные формы на сервер, поэтому такой вызов быстрее, чем &НаСервере. ОповеститьОбИзменении() обновит открытые списки, иначе новый элемент появится в них только после ручного обновления.
Запись из формы самого элемента
В форме элемента данные лежат в реквизите Объект. Это не СправочникОбъект, а структура данных формы. Варианты записи:
- Штатно. Вызвать на клиенте
Записать()у формы. Отработают все обработчики формы (ПередЗаписью,ПередЗаписьюНаСервере,ПриЗаписиНаСервере,ПослеЗаписи), проверка заполнения и оповещения. Этот путь предпочтителен. - Через преобразование. На сервере получить объект
ОбъектСпр = РеквизитФормыВЗначение("Объект"), изменить и записать, затем вернуть в формуЗначениеВРеквизитФормы(ОбъектСпр, "Объект"). Нужен, когда в процессе записи вызываются методы модуля объекта.
Если сначала записать объект через РеквизитФормыВЗначение, а потом пользователь нажмёт «Записать» в форме, но вы не вернули объект обратно в реквизит, форма попытается записать устаревшую версию данных. Получится ошибка о том, что объект изменён другим пользователем или сеансом.
Объектная блокировка
Форма при редактировании ставит пессимистическую блокировку на объект. В коде её ставят методом Объект.Заблокировать(): если элемент открыт у другого пользователя, вызов закончится исключением. Оберните его в Попытка, чтобы показать понятное сообщение, а не системное.
Массовая запись справочника: транзакции и скорость
Загрузка прайса на 15–20 тысяч позиций «в лоб» упирается в три вещи: поиск в цикле, обработчики записи и одна гигантская транзакция.
Что ускоряет загрузку
| Приём | Эффект | Риск |
|---|---|---|
Один запрос на поиск всех существующих кодов вместо НайтиПоКоду в цикле | Убирает тысячи обращений к базе | Нет |
| Запись порциями по 500–1000 элементов в транзакции | Меньше накладных расходов, короткие блокировки | При сбое откатится только текущая порция |
ОбменДанными.Загрузка = Истина | Пропускает обработчики ПередЗаписью/ПриЗаписи и подписки | Не выполнится логика конфигурации: регистрация изменений, заполнение служебных полей |
Запись только изменённых элементов (Объект.Модифицированность()) | Не трогает неизменённые записи | Нет |
Режим ОбменДанными.Загрузка отключает бизнес-логику конфигурации. Включайте его, только если точно знаете, какие обработчики пропускаете, и проверьте результат на копии базы.
Пример загрузки порциями
Процедура ЗагрузитьНоменклатуру(ТаблицаДанных) Экспорт
// Один запрос: какие коды уже есть в базе
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код В (&Коды)";
Запрос.УстановитьПараметр("Коды", ТаблицаДанных.ВыгрузитьКолонку("Код"));
Существующие = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Существующие.Вставить(Выборка.Код, Выборка.Ссылка);
КонецЦикла;
РазмерПорции = 500;
Счетчик = 0;
НачатьТранзакцию();
Попытка
Для Каждого Строка Из ТаблицаДанных Цикл
Ссылка = Существующие.Получить(Строка.Код);
Если Ссылка = Неопределено Тогда
Объект = Справочники.Номенклатура.СоздатьЭлемент();
Объект.Код = Строка.Код;
Иначе
Объект = Ссылка.ПолучитьОбъект();
КонецЕсли;
Объект.Наименование = Строка.Наименование;
Если Объект.ЭтоНовый() Или Объект.Модифицированность() Тогда
Объект.Записать();
КонецЕсли;
Счетчик = Счетчик + 1;
Если Счетчик % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Пара пояснений к примеру:
- 🧩
Модифицированность()становитсяИстинапри любом присваивании реквизиту, даже тем же значением. Чтобы не перезаписывать неизменённые элементы, сравнивайте значения перед присваиванием. - 🧩 Если коды в файле числовые, а в справочнике строковые, приводите их к строке заранее, иначе поиск по соответствию промахнётся. Для чисел используйте
Формат(Число, "ЧГ=0"), чтобы не получить пробелы в разрядах. - 🧩 Порция 500 — ориентир. На файловой базе с медленным диском её уменьшают, на клиент-серверной с SQL можно увеличить.
Почему нельзя ловить исключение внутри транзакции и идти дальше
Если Записать() упал внутри транзакции, транзакция уже помечена как ошибочная. Перехватить исключение и продолжить запись следующих элементов не получится: фиксация завершится ошибкой. Для загрузки «пропускать плохие строки» выносите каждую проблемную запись в свою транзакцию или проверяйте данные до записи.
Права и привилегированный режим
Фоновое задание или обработка, запущенная пользователем без права изменения справочника, упадёт с ошибкой Нарушение прав доступа!. Для служебной загрузки код в серверном общем модуле можно выполнить с УстановитьПривилегированныйРежим(Истина). Не включайте его в коде, который вызывается с параметрами от пользователя без проверки: так легко открыть запись туда, куда пользователю нельзя.
Ошибки при программной записи справочника
Частые вопросы
Как скопировать элемент справочника программно?
Скопировать(). Метод вернёт новый, ещё не записанный объект с теми же реквизитами и табличными частями. Если включён контроль уникальности кода, очистите Код или задайте новый, затем поменяйте наименование и запишите.Как пометить элемент на удаление из кода?
УстановитьПометкуУдаления(Истина). Метод сам записывает объект, отдельный Записать() не нужен. Метод Удалить() удаляет элемент сразу и без контроля ссылок, поэтому в рабочих базах его не используют.Можно ли записать справочник в файловой базе внутри транзакции?
Как узнать, новый ли объект перед записью?
ЭтоНовый() возвращает Истина, пока объект не записан. В обработчике ПередЗаписью модуля объекта так отличают создание от изменения.Почему после записи элемент не виден в открытом списке?
ОповеститьОбИзменении(Ссылка) или обновите список вручную.