При программном создании цепочек связанных данных — например, загрузке данных о начислениях, когда необходимо на лету создать физическое лицо, сотрудника и добавить строчку в документ начисления зарплаты — разработчики нередко сталкиваются с ошибкой «Объект не найден» или получением «битых» ссылок. Выясним причину этой ошибки, разберем типичные просчеты в логике кода и рассмотрим правильный порядок записи объектов платформы 1С:Предприятие.
Проанализируем механику работы со ссылочными типами данных в платформе 1С. Разработчики часто пытаются заранее сгенерировать ссылку с помощью методов ПолучитьСсылку() или ПолучитьСсылкуНового() и присвоить ее объекту через метод УстановитьСсылкуНового(), надеясь подставить эту ссылку в табличную часть документа еще до того, как справочник физически записан в базу данных.
Посмотрим, к чему это приводит на практике:
УстановитьСсылкуНового() лишь резервирует идентификатор в памяти текущего сеанса, но не создает запись в таблице базы данных. Если вызвать метод Записать() у документа до сохранения элемента справочника, в табличную часть попадает идентификатор несуществующей записи. При обращении к ней или в интерфейсе платформа выдает представление <Объект не найден...>.Записать() у документа (особенно в конфигурациях для расчета зарплаты) срабатывают встроенные проверки и подписки на события. Внутри них выполняются запросы к реквизитам сотрудника и физлица. Если объект еще не сохранен в базе, запрос вернет NULL, что приведет к аварийной остановке с ошибкой.ГоловнойСотрудник в справочнике сотрудников типовых конфигураций ожидает ссылку на справочник Сотрудники, а не строку. Попытка записать туда текстовое представление вызывает ошибку валидации данных.Разберем по шагам надежный и прозрачный способ решения задачи. Чтобы не использовать сложную и чреватую ошибками конструкцию с УстановитьСсылкуНового(), построим логику создания связанных данных последовательно:
ФизическиеЛица.Записать() — после этого у объекта появляется реальная ссылка в базе данных (свойство Ссылка).ФИОФизическихЛиц), передавая полученную валидную ссылку.Сотрудники, связываем его с созданным физическим лицом и сразу записываем.Сотрудник.Ссылка в строку табличной части документа.Посмотрим на пример корректно выстроенного кода для загрузки сотрудников и формирования документа начисления:
ОтсутствующиеСотрудники = Новый ТекстовыйДокумент;
// Создаем документ до цикла, но не записываем его
НачислениеЗарплаты = Документы.НачислениеЗарплаты.СоздатьДокумент();
НачислениеЗарплаты.Дата = ТекущаяДата();
Для Каждого ТЭлемент Из iikoДанные Цикл
ФИОМассив = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ТЭлемент.Сотрудник, " ");
// Добавляем строку в табличную часть документа
тчНачисления = НачислениеЗарплаты.Начисления.Добавить();
// Ищем существующего сотрудника по полному наименованию
СсылкаСотрудник = Справочники.Сотрудники.НайтиПоНаименованию(ТЭлемент.Сотрудник);
Если Не ЗначениеЗаполнено(СсылкаСотрудник) Тогда
// 1. Создаем и записываем физическое лицо
ФизЛицо = Справочники.ФизическиеЛица.СоздатьЭлемент();
ФизЛицо.Наименование = ТЭлемент.Сотрудник;
ФизЛицо.ФИО = ТЭлемент.Сотрудник;
ФизЛицо.Фамилия = ФИОМассив[0];
Если ФИОМассив.Количество() > 1 Тогда
ФизЛицо.Имя = ФИОМассив[1];
КонецЕсли;
Если ФИОМассив.Количество() > 2 Тогда
ФизЛицо.Отчество = ФИОМассив[2];
КонецЕсли;
ФизЛицо.Записать();
СсылкаФизЛицо = ФизЛицо.Ссылка;
// 2. Создаем запись в регистре сведений по полученной ссылке
НаборЗаписей = РегистрыСведений.ФИОФизическихЛиц.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ФизическоеЛицо.Установить(СсылкаФизЛицо);
НовЗапись = НаборЗаписей.Добавить();
НовЗапись.Период = ТекущаяДата();
НовЗапись.ФизическоеЛицо = СсылкаФизЛицо;
НовЗапись.Фамилия = ФизЛицо.Фамилия;
НовЗапись.Имя = ФизЛицо.Имя;
НовЗапись.Отчество = ФизЛицо.Отчество;
НаборЗаписей.Записать(Истина);
// 3. Создаем и записываем сотрудника
Сотрудник = Справочники.Сотрудники.СоздатьЭлемент();
Сотрудник.Наименование = ТЭлемент.Сотрудник;
Сотрудник.ФизическоеЛицо = СсылкаФизЛицо;
// Записываем, чтобы получить ссылку
Сотрудник.Записать();
// Если требуется заполнить головного сотрудника ссылкой на самого себя
Сотрудник.ГоловнойСотрудник = Сотрудник.Ссылка;
Сотрудник.Записать();
СсылкаСотрудник = Сотрудник.Ссылка;
ОтсутствующиеСотрудники.ДобавитьСтроку(ТЭлемент.Сотрудник);
КонецЕсли;
// Заполняем реквизит строки табличной части готовой ссылкой
тчНачисления.Сотрудник = СсылкаСотрудник;
КонецЦикла;
// Документ записывается однократно за пределами цикла
НачислениеЗарплаты.Записать(РежимЗаписиДокумента.Запись);
ОтсутствующиеСотрудники.Записать("D:\ОтсутствующиеСотрудники.txt");
Рассмотрим важный аспект надежности. При последовательном создании элементов может возникнуть ситуация, когда физическое лицо уже записано, а на шаге сохранения сотрудника или документа произошел сбой. Чтобы в базе не оставалось «мусорных» незавершенных данных, обернем обработку каждой строки или всю процедуру целиком в явную транзакцию:
НачатьТранзакцию();
Попытка
// Блок последовательного создания физлица, сотрудника и заполнения документа
// ...
НачислениеЗарплаты.Записать(РежимЗаписиДокумента.Запись);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение "Ошибка при загрузке данных: " + ОписаниеОшибки();
КонецПопытки;
Использование транзакций гарантирует, что либо все связанные объекты (физическое лицо, записи регистра сведений, сотрудник и документ) будут записаны в базу корректно, либо изменения полностью откатятся, сохранив целостность информационной базы.