Создание и изменение элементов справочника из кода 1С 8.3

· 10 мин

Содержание

Чтобы записать справочник программно, получите объект справочника на сервере, заполните реквизиты и вызовите Записать(). Новый элемент создают через Справочники.ИмяСправочника.СоздатьЭлемент(), существующий открывают через Ссылка.ПолучитьОбъект(). Отдельного метода для записи «нового» элемента у объекта справочника нет: и новый, и изменённый объект пишутся одним и тем же Записать().

Проблемы обычно начинаются не с самого метода, а вокруг него. Код вызван на клиенте, дубли по наименованию, пустые обязательные реквизиты, чужая блокировка, медленная загрузка 20 тысяч позиций. Всё это разобрано ниже на примерах для платформы 1С:Предприятие 8.3 и управляемых форм. Имена справочников и реквизитов в коде условные, подставьте свои.

Объект, ссылка и выборка: с чем работает код записи

В 1С у справочника есть несколько «представлений», и путать их — главный источник ошибок новичков.

ЧтоТипМожно записать?Где доступно
Ссылка на элементСправочникСсылка.НоменклатураНет, только читатьКлиент и сервер
Объект элементаСправочникОбъект.НоменклатураДа, методом Записать()Только сервер
ВыборкаСправочникВыборка.НоменклатураНет, но даёт объект через ПолучитьОбъект()Только сервер
Данные формыДанныеФормыСтруктураЧерез форму или после преобразования в объектКлиент и сервер
МенеджерСправочникМенеджер.НоменклатураНет, создаёт объекты и ищет ссылкиТолько сервер

Отсюда три правила:

  • 🔹 Записать можно только объект. Через ссылку поменять реквизит нельзя: Ссылка.Наименование = "..." вызовет ошибку.
  • 🔹 Объект живёт на сервере. В модуле формы код записи помечают &НаСервере или &НаСервереБезКонтекста, в общем модуле — включают флажок «Сервер».
  • 🔹 Записать() у справочника вызывается без параметров. Режимы записи и проведения есть у документов, у справочников их нет.

При вызове Записать() платформа выполняет обработчики модуля объекта ПередЗаписью и ПриЗаписи, а также подписки на события. Если где-то в них выставлен Отказ = Истина, метод вызывает исключение, и элемент не записывается.

Как создать новый элемент справочника кодом

Базовый пример: функция создаёт позицию номенклатуры и возвращает ссылку на неё.

&НаСервереБезКонтекста
Функция СоздатьНоменклатуру(Наименование, Артикул, Папка)

	НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
	НовыйЭлемент.Наименование = Наименование;
	НовыйЭлемент.Артикул      = Артикул;
	НовыйЭлемент.Родитель     = Папка;
	НовыйЭлемент.Записать();

	Возврат НовыйЭлемент.Ссылка;

КонецФункции

Код элемента

Если у справочника в конфигураторе включена автонумерация, а поле Код вы не заполнили, платформа присвоит код сама при записи. Когда код нужен заранее, например для вывода в лог до записи, вызовите НовыйЭлемент.УстановитьНовыйКод().

Свой код задавайте явно только при загрузке из внешней системы, где коды уже есть. Тогда следите за длиной: код длиннее, чем задано в свойствах справочника, не поместится.

Группы, владельцы и табличные части

  • 📁 Группа в иерархическом справочнике создаётся методом СоздатьГруппу(). Элемент кладут в группу через свойство Родитель.
  • 🔗 Подчинённый справочник (например, договоры контрагента) требует заполнить Владелец. Без владельца элемент не запишется.
  • 📋 Табличная часть заполняется до записи: НоваяСтрока = НовыйЭлемент.ИмяТабличнойЧасти.Добавить();, затем реквизиты строки.
НоваяГруппа = Справочники.Номенклатура.СоздатьГруппу();
НоваяГруппа.Наименование = "Канцтовары";
НоваяГруппа.Записать();

Договор = Справочники.ДоговорыКонтрагентов.СоздатьЭлемент();
Договор.Владелец     = СсылкаНаКонтрагента;
Договор.Наименование = "Основной договор";
Договор.Записать();

Ссылка до записи

Иногда ссылку нужно знать до записи: например, проставить её в другом объекте в той же транзакции. Для этого есть пара методов: Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор) создаёт ссылку, а НовыйЭлемент.УстановитьСсылкуНового(Ссылка) закрепляет её за объектом. Этот же приём используют при загрузке из другой базы, чтобы сохранить исходные GUID элементов.

Изменение существующих элементов: по ссылке и в цикле по выборке

Чтобы изменить один элемент, получите объект по ссылке, поменяйте реквизит и запишите:

&НаСервереБезКонтекста
Процедура ПереименоватьЭлемент(Ссылка, НовоеНаименование)

	Объект = Ссылка.ПолучитьОбъект();
	Если Объект = Неопределено Тогда
		ВызватьИсключение "Элемент не найден в базе";
	КонецЕсли;

	Объект.Наименование = НовоеНаименование;
	Объект.Записать();

КонецПроцедуры

Проверка на Неопределено нужна для «битых» ссылок: если элемент удалили, ПолучитьОбъект() вернёт не объект, а Неопределено.

Поиск элемента перед изменением

У менеджера справочника есть готовые методы поиска:

  • НайтиПоКоду(Код) — по коду;
  • НайтиПоНаименованию(Наименование, Истина) — второй параметр включает точное совпадение;
  • НайтиПоРеквизиту("ИНН", ЗначениеИНН) — по реквизиту.

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

Групповая обработка через запрос

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

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Номенклатура.Ссылка КАК Ссылка
|ИЗ
|	Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|	Номенклатура.Родитель = &Папка
|	И НЕ Номенклатура.ЭтоГруппа
|	И НЕ Номенклатура.ПометкаУдаления";
Запрос.УстановитьПараметр("Папка", Папка);

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	Объект = Выборка.Ссылка.ПолучитьОбъект();
	Объект.ВидНоменклатуры = НовыйВид;
	Объект.Записать();
КонецЦикла;

Не тяните в запрос все реквизиты, если меняете один. Объект всё равно перечитывается целиком при ПолучитьОбъект(), а лишние поля только нагружают сервер.

Если условие простое, можно обойтись без запроса: Справочники.Номенклатура.Выбрать(Папка) даёт выборку элементов указанного родителя, у неё тоже есть ПолучитьОбъект().

Проверки перед записью: заполнение, уникальность кода, дубли

Программная запись не делает того, что делает форма. Проверку заполнения и контроль дублей по смыслу придётся вызывать самим.

Заполнение обязательных реквизитов

Проверка заполнения (свойство «Проверка заполнения» у реквизитов и обработчик ОбработкаПроверкиЗаполнения) при Записать() из кода не запускается. Её вызывает форма при интерактивной записи. В коде вызывайте явно:

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

Метод вернёт Ложь и добавит сообщения пользователю о каждом незаполненном поле.

Уникальность кода

Если в свойствах справочника включён контроль уникальности, запись элемента с уже занятым кодом вызовет исключение о неуникальном значении поля «Код». Учтите область контроля: в иерархическом справочнике код может проверяться в пределах всего справочника или только внутри подчинения, в подчинённом справочнике — внутри владельца. Это видно в свойствах объекта в конфигураторе.

Дубли по наименованию и реквизитам

По наименованию, ИНН или артикулу платформа уникальность не контролирует. Проверка «найти, и если нет — создать» без блокировки ломается, когда два сеанса выполняют её одновременно: оба не находят элемент и оба его создают.

Надёжная схема — поиск и запись в одной транзакции под управляемой блокировкой:

НачатьТранзакцию();
Попытка
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("Справочник.Контрагенты");
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
	Блокировка.Заблокировать();

	Ссылка = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ИНН);
	Если Ссылка.Пустая() Тогда
		НовыйЭлемент = Справочники.Контрагенты.СоздатьЭлемент();
		НовыйЭлемент.Наименование = Наименование;
		НовыйЭлемент.ИНН = ИНН;
		НовыйЭлемент.Записать();
		Ссылка = НовыйЭлемент.Ссылка;
	КонецЕсли;

	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

Блокировка без указания полей закрывает на запись весь справочник до конца транзакции. Держите транзакцию короткой, иначе другие пользователи будут ждать, а при долгом ожидании получат ошибку блокировки.

Если в типовой конфигурации уже есть механизм поиска дублей (например, проверка контрагента по ИНН), лучше вызвать его, чем писать свой.

Запись справочника из управляемой формы и с клиента

На клиенте нет ни менеджеров справочников, ни объектов. Вызов Справочники.Номенклатура.СоздатьЭлемент() в клиентской процедуре даст ошибку Переменная не определена (Справочники). Решение одно — передать управление на сервер.

Создание элемента по кнопке

&НаКлиенте
Процедура СоздатьЭлементКоманда(Команда)
	НоваяСсылка = СоздатьЭлементНаСервере(Наименование);
	ОповеститьОбИзменении(НоваяСсылка);
КонецПроцедуры

&НаСервереБезКонтекста
Функция СоздатьЭлементНаСервере(Наименование)
	НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
	НовыйЭлемент.Наименование = Наименование;
	НовыйЭлемент.Записать();
	Возврат НовыйЭлемент.Ссылка;
КонецФункции

&НаСервереБезКонтекста не тащит данные формы на сервер, поэтому такой вызов быстрее, чем &НаСервере. ОповеститьОбИзменении() обновит открытые списки, иначе новый элемент появится в них только после ручного обновления.

Запись из формы самого элемента

В форме элемента данные лежат в реквизите Объект. Это не СправочникОбъект, а структура данных формы. Варианты записи:

  1. Штатно. Вызвать на клиенте Записать() у формы. Отработают все обработчики формы (ПередЗаписью, ПередЗаписьюНаСервере, ПриЗаписиНаСервере, ПослеЗаписи), проверка заполнения и оповещения. Этот путь предпочтителен.
  2. Через преобразование. На сервере получить объект ОбъектСпр = РеквизитФормыВЗначение("Объект"), изменить и записать, затем вернуть в форму ЗначениеВРеквизитФормы(ОбъектСпр, "Объект"). Нужен, когда в процессе записи вызываются методы модуля объекта.

Если сначала записать объект через РеквизитФормыВЗначение, а потом пользователь нажмёт «Записать» в форме, но вы не вернули объект обратно в реквизит, форма попытается записать устаревшую версию данных. Получится ошибка о том, что объект изменён другим пользователем или сеансом.

Объектная блокировка

Форма при редактировании ставит пессимистическую блокировку на объект. В коде её ставят методом Объект.Заблокировать(): если элемент открыт у другого пользователя, вызов закончится исключением. Оберните его в Попытка, чтобы показать понятное сообщение, а не системное.

Массовая запись справочника: транзакции и скорость

Загрузка прайса на 15–20 тысяч позиций «в лоб» упирается в три вещи: поиск в цикле, обработчики записи и одна гигантская транзакция.

Что ускоряет загрузку

ПриёмЭффектРиск
Один запрос на поиск всех существующих кодов вместо НайтиПоКоду в циклеУбирает тысячи обращений к базеНет
Запись порциями по 500–1000 элементов в транзакцииМеньше накладных расходов, короткие блокировкиПри сбое откатится только текущая порция
ОбменДанными.Загрузка = ИстинаПропускает обработчики ПередЗаписью/ПриЗаписи и подпискиНе выполнится логика конфигурации: регистрация изменений, заполнение служебных полей
Запись только изменённых элементов (Объект.Модифицированность())Не трогает неизменённые записиНет

Режим ОбменДанными.Загрузка отключает бизнес-логику конфигурации. Включайте его, только если точно знаете, какие обработчики пропускаете, и проверьте результат на копии базы.

Пример загрузки порциями

Процедура ЗагрузитьНоменклатуру(ТаблицаДанных) Экспорт

	// Один запрос: какие коды уже есть в базе
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Номенклатура.Код КАК Код,
	|	Номенклатура.Ссылка КАК Ссылка
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Код В (&Коды)";
	Запрос.УстановитьПараметр("Коды", ТаблицаДанных.ВыгрузитьКолонку("Код"));

	Существующие = Новый Соответствие;
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		Существующие.Вставить(Выборка.Код, Выборка.Ссылка);
	КонецЦикла;

	РазмерПорции = 500;
	Счетчик = 0;

	НачатьТранзакцию();
	Попытка
		Для Каждого Строка Из ТаблицаДанных Цикл

			Ссылка = Существующие.Получить(Строка.Код);
			Если Ссылка = Неопределено Тогда
				Объект = Справочники.Номенклатура.СоздатьЭлемент();
				Объект.Код = Строка.Код;
			Иначе
				Объект = Ссылка.ПолучитьОбъект();
			КонецЕсли;

			Объект.Наименование = Строка.Наименование;

			Если Объект.ЭтоНовый() Или Объект.Модифицированность() Тогда
				Объект.Записать();
			КонецЕсли;

			Счетчик = Счетчик + 1;
			Если Счетчик % РазмерПорции = 0 Тогда
				ЗафиксироватьТранзакцию();
				НачатьТранзакцию();
			КонецЕсли;

		КонецЦикла;
		ЗафиксироватьТранзакцию();
	Исключение
		Если ТранзакцияАктивна() Тогда
			ОтменитьТранзакцию();
		КонецЕсли;
		ВызватьИсключение;
	КонецПопытки;

КонецПроцедуры

Пара пояснений к примеру:

  • 🧩 Модифицированность() становится Истина при любом присваивании реквизиту, даже тем же значением. Чтобы не перезаписывать неизменённые элементы, сравнивайте значения перед присваиванием.
  • 🧩 Если коды в файле числовые, а в справочнике строковые, приводите их к строке заранее, иначе поиск по соответствию промахнётся. Для чисел используйте Формат(Число, "ЧГ=0"), чтобы не получить пробелы в разрядах.
  • 🧩 Порция 500 — ориентир. На файловой базе с медленным диском её уменьшают, на клиент-серверной с SQL можно увеличить.

Почему нельзя ловить исключение внутри транзакции и идти дальше

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

Права и привилегированный режим

Фоновое задание или обработка, запущенная пользователем без права изменения справочника, упадёт с ошибкой Нарушение прав доступа!. Для служебной загрузки код в серверном общем модуле можно выполнить с УстановитьПривилегированныйРежим(Истина). Не включайте его в коде, который вызывается с параметрами от пользователя без проверки: так легко открыть запись туда, куда пользователю нельзя.

Ошибки при программной записи справочника

Диагностика: выберите симптом

Частые вопросы

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