При импорте данных из внешних источников (таких как файлы Excel, XML или JSON) разработчики часто сталкиваются с ошибками рантайма. Одна из самых коварных и распространенных ошибок в среде 1С:Предприятие — «Значение не является значением объектного типа». Сегодня мы подробно разберем, почему эта ошибка возникает при работе со ссылками и уникальными идентификаторами (UUID), почему стандартные проверки на пустую ссылку не помогают, и как правильно переписать код загрузки, чтобы раз и навсегда решить эту проблему.
Для начала проанализируем природу самой ошибки. Текст Значение не является значением объектного типа (ИмяРеквизита) сообщает нам о том, что платформа попыталась обратиться к свойству или реквизиту объекта (через точку), но переменная, стоящая слева от точки, не является объектом. В 99% случаев в этой переменной находится значение Неопределено.
Давайте посмотрим на фрагмент кода, в котором происходит сбой:
Если СправочникИмя="Номенклатура" И НЕ ЭтоГруппа Тогда
// Элемент.ОбменДанными.Загрузка = Истина;
Элемент.НаименованиеПолное = Элемент.Наименование;
Если при остановке по ошибке в отладчике мы увидим, что переменная Элемент равна Неопределено, логично задать вопрос: откуда она берется? Переменная является результатом работы функции поиска и создания элементов. Проанализируем ключевой участок функции инициализации элемента:
Если СправочникИмя="Номенклатура" И НЕ ЭтоГруппа Тогда
Если ПустаяСтрока(СтрокаТЗ.УИД) Тогда
Элемент = Справочники.Номенклатура.ПустаяСсылка();
Иначе
УИД = Новый УникальныйИдентификатор(СтрокаТЗ.УИД);
Элемент = Справочники.Номенклатура.ПолучитьСсылку(УИД);
КонецЕсли;
Иначе
Элемент = Справочники[СправочникИмя].НайтиПоНаименованию(Наименование, ИСТИНА, Родитель);
КонецЕсли;
Если Элемент.Push() И НЕ СправочникИмя="Номенклатура" Тогда
Элемент = Справочники[СправочникИмя].НайтиПоНаименованию(Наименование, ИСТИНА,);
КонецЕсли;
Если Элемент.Пустая() Тогда
// Ветка создания нового элемента...
ИначеЕсли (НЕ СправочникИмя="Номенклатура") ИЛИ (СправочникИмя="Номенклатура" И ЭтоГруппа) Тогда
Возврат Элемент.Ссылка;
Иначе
Элемент = Элемент.ПолучитьОбъект();
КонецЕсли;
В этом алгоритме скрыта фундаментальная методическая ошибка работы с объектной моделью 1С. Рассмотрим по шагам, что происходит, когда из Excel считывается Уникальный Идентификатор (УИД), которого еще нет в нашей базе данных:
Элемент = Справочники.Номенклатура.ПолучитьСсылку(УИД);.ПолучитьСсылку() не делает никаких запросов к СУБД. Он просто генерирует в оперативной памяти ссылку с указанным GUID. Ему не важно, существует ли реальная запись в таблице базы данных.Если Элемент.Пустая() Тогда. Но наша ссылка не является пустой! В ней содержится вполне конкретный GUID, переданный из файла. Метод Пустая() возвращает значение Ложь.Иначе и пытается получить объект из базы для редактирования: Элемент = Элемент.ПолучитьОбъект();.ПолучитьОбъект() возвращает системное значение Неопределено.Элемент затирается значением Неопределено. Любая последующая попытка обратиться к ее реквизитам (например, к НаименованиеПолное) вызывает критический сбой.---
Для того чтобы избежать «битых ссылок» (когда ссылка заполнена, но объекта в базе нет), необходимо принудительно проверять физическое наличие записи в базе данных. Сделать это можно тремя основными способами.
Способ 1. Проверка результата ПолучитьОбъект()
Самый простой способ «в лоб» — проверить возвращаемое значение метода ПолучитьОбъект() на равенство Неопределено:
НоменклатураОбъект = СсылкаНаНоменклатуру.ПолучитьОбъект();
Если НоменклатураОбъект = Неопределено Тогда
// Объект в базе отсутствует! Это "битая ссылка".
КонецЕсли;
У этого способа есть один недостаток: если объект в базе существует, платформа считает его целиком в оперативную память. Если объектов много, это может замедлить работу алгоритма.
Способ 2. Запрос к базе данных (оптимально по производительности)
Если вам нужно быстро обработать массив ссылок без лишней нагрузки на память, используйте легкий запрос к таблице справочника:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ПроверяемаяСсылка);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
// Объект физически не существует в базе данных
КонецЕсли;
Способ 3. Использование функций БСП (для типовых конфигураций)
Если вы ведете разработку в конфигурации, созданной на основе Библиотеки Стандартных Подсистем (БСП), вам доступен готовый и очень быстрый метод общего модуля ОбщегоНазначения.СсылкаСуществует():
Если НЕ ОбщегоНазначения.СсылкаСуществует(ПроверяемаяСсылка) Тогда
// Объекта нет в базе данных, требуется его создание
КонецЕсли;
---
Если бизнес-логика загрузки требует, чтобы новый элемент создавался именно с тем Уникальным Идентификатором, который был передан в Excel-файле, нам нужно использовать метод УстановитьСсылкуНового().
Проанализируем ключевые правила использования этого метода:
УстановитьСсылкуНового() вызывается только у нового, еще не записанного объекта справочника или документа.ПолучитьСсылку()), но ни в коем случае не сам объект типа УникальныйИдентификатор.ОбменДанными.Загрузка = Истина.Посмотрим на классический пример создания нового элемента с предопределенным GUID:
// Сначала подготавливаем ссылку
НовыйУИД = Новый УникальныйИдентификатор(СтрокаИзФайла.УИД);
БудущаяСсылка = Справочники.Номенклатура.ПолучитьСсылку(НовыйУИД);
// Теперь создаем сам объект в памяти
НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НовыйОбъект.УстановитьСсылкуНового(БудущаяСсылка);
// Заполняем реквизиты и записываем
НовыйОбъект.Наименование = СтрокаИзФайла.Наименование;
НовыйОбъект.ОбменДанными.Загрузка = Истина;
НовыйОбъект.Записать();
---
Теперь соберем все полученные знания воедино и полностью перепишем исходную функцию ЭлементСправочника. Мы исправим логические ошибки, разделим понятия Ссылки и Объекта, а также обеспечим гарантированное создание элементов в случае передачи отсутствующих в базе УИДов.
&НаСервере
Функция ЭлементСправочника(Наименование, ЭтоГруппа=ЛОЖЬ, Родитель=Неопределено, СтрокаТЗ=Неопределено,
СправочникИмя="Номенклатура", Владелец=Неопределено)
// Обеспечиваем корректность родителя
Если Родитель = Неопределено Тогда
Родитель = Справочники[СправочникИмя].ПустаяСсылка();
КонецЕсли;
НайденнаяСсылка = Справочники[СправочникИмя].ПустаяСсылка();
НовыйОбъектСоздан = Ложь;
ТекущийОбъект = Неопределено;
// 1. Попытка поиска существующей ссылки
Если СправочникИмя = "Номенклатура" И НЕ ЭтоГруппа Тогда
Если НЕ ПустаяСтрока(СтрокаТЗ.УИД) Тогда
УИД = Новый УникальныйИдентификатор(СтрокаТЗ.УИД);
НайденнаяСсылка = Справочники.Номенклатура.ПолучитьСсылку(УИД);
КонецЕсли;
Иначе
НайденнаяСсылка = Справочники[СправочникИмя].НайтиПоНаименованию(Наименование, ИСТИНА, Родитель);
Если НайденнаяСсылка.Пустая() И НЕ СправочникИмя = "Номенклатура" Тогда
НайденнаяСсылка = Справочники[СправочникИмя].НайтиПоНаименованию(Наименование, ИСТИНА);
КонецЕсли;
КонецЕсли;
// 2. Проверяем физическое существование объекта в базе данных
ОбъектСуществует = Ложь;
Если НЕ НайденнаяСсылка.Пустая() Тогда
ТекущийОбъект = НайденнаяСсылка.ПолучитьОбъект();
Если ТекущийОбъект <> Неопределено Тогда
ОбъектСуществует = Истина;
КонецЕсли;
КонецЕсли;
// 3. Создаем новый элемент, если он не был найден
Если НЕ ОбъектСуществует Тогда
Если ЭтоГруппа Тогда
ТекущийОбъект = Справочники[СправочникИмя].СоздатьГруппу();
Иначе
ТекущийОбъект = Справочники[СправочникИмя].СоздатьЭлемент();
Если Владелец <> Неопределено Тогда
ТекущийОбъект.Владелец = Владелец;
КонецЕсли;
КонецЕсли;
// Назначаем ссылке нового созданный УИД из файла, если это необходимо
Если СправочникИмя = "Номенклатура" И НЕ ЭтоГруппа И НЕ НайденнаяСсылка.Пустая() Тогда
ТекущийОбъект.УстановитьСсылкуНового(НайденнаяСсылка);
ТекущийОбъект.ОбменДанными.Загрузка = Истина;
КонецЕсли;
ТекущийОбъект.Наименование = Наименование;
НовыйОбъектСоздан = Истина;
Если НЕ СправочникИмя = "ХарактеристикиНоменклатуры" Тогда
ТекущийОбъект.Родитель = Родитель;
КонецЕсли;
Если СправочникИмя = "ВидыНоменклатуры" Тогда
Сообщить("Не найден вид номенклатуры -" + Наименование + "- , элемент сформирован автоматически", СтатусСообщения.ОченьВажное);
КонецЕсли;
Иначе
// Если объект найден и это группа (или не Номенклатура), перезапись не требуется, возвращаем ссылку
Если (НЕ СправочникИмя = "Номенклатура") ИЛИ (СправочникИмя = "Номенклатура" И ЭтоГруппа) Тогда
Возврат НайденнаяСсылка;
КонецЕсли;
КонецЕсли;
// 4. Безопасное заполнение реквизитов (ТекущийОбъект гарантированно существует в памяти)
Если СправочникИмя = "УпаковкиЕдиницыИзмерения" И НЕ ЭтоГруппа Тогда
Сообщить("Не найдена единица измерения - " + Наименование);
Возврат Справочники.УпаковкиЕдиницыИзмерения.ПустаяСсылка();
КонецЕсли;
Если СправочникИмя = "ВидыНоменклатуры" И НЕ ЭтоГруппа Тогда
ТекущийОбъект.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Товар;
ТекущийОбъект.ВариантОформленияПродажи = Перечисления.ВариантыОформленияПродажи.РеализацияТоваровУслуг;
КонецЕсли;
Если СправочникИмя = "ХарактеристикиНоменклатуры" И НЕ ЭтоГруппа Тогда
ТекущийОбъект.НаименованиеПолное = ТекущийОбъект.Наименование;
КонецЕсли;
Если СправочникИмя = "Номенклатура" И НЕ ЭтоГруппа Тогда
ТекущийОбъект.НаименованиеПолное = ТекущийОбъект.Наименование;
ТекущийОбъект.ВидНоменклатуры = СтрокаТЗ.ВидНоменклатуры_Ссылка;
ТекущийОбъект.ВариантОформленияПродажи = Перечисления.ВариантыОформленияПродажи.РеализацияТоваровУслуг;
ТекущийОбъект.Качество = Перечисления.ГрадацииКачества.Новый;
ТекущийОбъект.СтавкаНДС = Перечисления.СтавкиНДС.НДС20;
КонецЕсли;
// 5. Записываем объект и возвращаем актуальную ссылку
Попытка
ТекущийОбъект.Записать();
Возврат ТекущийОбъект.Ссылка;
Исключение
ЗаписьЖурналаРегистрации("ЗагрузкаДанных", УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка записи объекта: " + ОписаниеОшибки());
ВызватьИсключение "Не удалось записать элемент справочника: " + Наименование;
КонецПопытки;
КонецФункции
---
Чтобы больше никогда не сталкиваться с подобными ошибками при интеграции баз данных и загрузке файлов, рекомендуем придерживаться нескольких простых правил:
ПолучитьСсылку() создает только «оболочку» ссылки, но не сам физический объект. Проверка Ссылка.Пустая() для нее всегда вернет Ложь.Ссылка.ПолучитьОбъект() с обязательной последующей проверкой на равенство Неопределено.УстановитьСсылкуНового() до записи объекта в базу.Применение обновленной архитектуры поиска в вашей функции загрузки полностью устранит падения кода, обеспечит корректную синхронизацию со сторонними системами по UUID и сделает импорт данных стабильным и предсказуемым.