Как исправить ошибку «Значение не является значением объектного типа (НаименованиеПолное)» при загрузке из Excel в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При импорте данных из внешних источников (таких как файлы Excel, XML или JSON) разработчики часто сталкиваются с ошибками рантайма. Одна из самых коварных и распространенных ошибок в среде 1С:Предприятие — «Значение не является значением объектного типа». Сегодня мы подробно разберем, почему эта ошибка возникает при работе со ссылками и уникальными идентификаторами (UUID), почему стандартные проверки на пустую ссылку не помогают, и как правильно переписать код загрузки, чтобы раз и навсегда решить эту проблему.

Выясняем причину: почему падает код?

Для начала проанализируем природу самой ошибки. Текст Значение не является значением объектного типа (ИмяРеквизита) сообщает нам о том, что платформа попыталась обратиться к свойству или реквизиту объекта (через точку), но переменная, стоящая слева от точки, не является объектом. В 99% случаев в этой переменной находится значение Неопределено.

Давайте посмотрим на фрагмент кода, в котором происходит сбой:


Если СправочникИмя="Номенклатура" И НЕ ЭтоГруппа Тогда
    // Элемент.ОбменДанными.Загрузка = Истина;
    Элемент.НаименованиеПолное = Элемент.Наименование;

Если при остановке по ошибке в отладчике мы увидим, что переменная Элемент равна Неопределено, логично задать вопрос: откуда она берется? Переменная является результатом работы функции поиска и создания элементов. Проанализируем ключевой участок функции инициализации элемента:


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

Если Элемент.Push() И НЕ СправочникИмя="Номенклатура" Тогда
    Элемент = Справочники[СправочникИмя].НайтиПоНаименованию(Наименование, ИСТИНА,);
КонецЕсли;

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

В этом алгоритме скрыта фундаментальная методическая ошибка работы с объектной моделью 1С. Рассмотрим по шагам, что происходит, когда из Excel считывается Уникальный Идентификатор (УИД), которого еще нет в нашей базе данных:

  1. Код успешно отрабатывает строку Элемент = Справочники.Номенклатура.ПолучитьСсылку(УИД);.
  2. Метод ПолучитьСсылку() не делает никаких запросов к СУБД. Он просто генерирует в оперативной памяти ссылку с указанным GUID. Ему не важно, существует ли реальная запись в таблице базы данных.
  3. Далее код проверяет: Если Элемент.Пустая() Тогда. Но наша ссылка не является пустой! В ней содержится вполне конкретный GUID, переданный из файла. Метод Пустая() возвращает значение Ложь.
  4. Программа переходит в ветку Иначе и пытается получить объект из базы для редактирования: Элемент = Элемент.ПолучитьОбъект();.
  5. Так как записи с таким УИД в физической базе данных нет, метод ПолучитьОбъект() возвращает системное значение Неопределено.
  6. В итоге переменная Элемент затирается значением Неопределено. Любая последующая попытка обратиться к ее реквизитам (например, к НаименованиеПолное) вызывает критический сбой.

---

Как проверить, существует ли ссылка в базе данных?

Для того чтобы избежать «битых ссылок» (когда ссылка заполнена, но объекта в базе нет), необходимо принудительно проверять физическое наличие записи в базе данных. Сделать это можно тремя основными способами.

Способ 1. Проверка результата ПолучитьОбъект()

Самый простой способ «в лоб» — проверить возвращаемое значение метода ПолучитьОбъект() на равенство Неопределено:


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

У этого способа есть один недостаток: если объект в базе существует, платформа считает его целиком в оперативную память. Если объектов много, это может замедлить работу алгоритма.

Способ 2. Запрос к базе данных (оптимально по производительности)

Если вам нужно быстро обработать массив ссылок без лишней нагрузки на память, используйте легкий запрос к таблице справочника:


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

Запрос.УстановитьПараметр("Ссылка", ПроверяемаяСсылка);
РезультатЗапроса = Запрос.Выполнить();

Если РезультатЗапроса.Пустой() Тогда
    // Объект физически не существует в базе данных
КонецЕсли;

Способ 3. Использование функций БСП (для типовых конфигураций)

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


Если НЕ ОбщегоНазначения.СсылкаСуществует(ПроверяемаяСсылка) Тогда
    // Объекта нет в базе данных, требуется его создание
КонецЕсли;

---

Правильное создание нового объекта со своим УИД

Если бизнес-логика загрузки требует, чтобы новый элемент создавался именно с тем Уникальным Идентификатором, который был передан в Excel-файле, нам нужно использовать метод УстановитьСсылкуНового().

Проанализируем ключевые правила использования этого метода:

Посмотрим на классический пример создания нового элемента с предопределенным GUID:


// Сначала подготавливаем ссылку
НовыйУИД = Новый УникальныйИдентификатор(СтрокаИзФайла.УИД);
БудущаяСсылка = Справочники.Номенклатура.ПолучитьСсылку(НовыйУИД);

// Теперь создаем сам объект в памяти
НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НовыйОбъект.УстановитьСсылкуНового(БудущаяСсылка);

// Заполняем реквизиты и записываем
НовыйОбъект.Наименование = СтрокаИзФайла.Наименование;
НовыйОбъект.ОбменДанными.Загрузка = Истина;
НовыйОбъект.Записать();

---

Рефакторинг функции поиска и создания элементов

Теперь соберем все полученные знания воедино и полностью перепишем исходную функцию ЭлементСправочника. Мы исправим логические ошибки, разделим понятия Ссылки и Объекта, а также обеспечим гарантированное создание элементов в случае передачи отсутствующих в базе УИДов.


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

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

---

Резюме и правила хорошего тона при импорте данных

Чтобы больше никогда не сталкиваться с подобными ошибками при интеграции баз данных и загрузке файлов, рекомендуем придерживаться нескольких простых правил:

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

← На главную