Почему НайтиПоКоду в 1С не находит элемент, но возникает ошибка уникальности при его создании?

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

Каждый разработчик в среде 1С:Предприятие рано или поздно сталкивается с классической задачей: найти существующий элемент справочника по коду, а если он отсутствует — создать новый. Логика простая и понятная, но на практике она часто дает сбой. Сценарий выглядит следующим образом: функция поиска возвращает пустую ссылку (элемент не найден), выполнение переходит к созданию нового объекта, но при вызове метода Записать() система аварийно завершает работу, сообщая о нарушении уникальности кода.

Проанализируем эту ситуацию, разберем причины возникновения этой проблемы и рассмотрим лучшие практики написания надежного, отказоустойчивого кода для поиска и создания объектов в 1С.

Причина №1. Коварный параметр «ПоискПоПолномуКоду»

Основная ошибка автора темы на форуме кроется в вызове метода НайтиПоКоду(). Обратим внимание на его исходную строку кода:

Элемент = Справочники.номенклатура.найтиПоКоду(КОД, Истина);

Давайте разберем по шагам параметры метода НайтиПоКоду(), согласно синтакс-помощнику платформы 1С:

  1. Код (обязательный) — искомый код элемента (число или строка).
  2. ПоискПоПолномуКоду (необязательный, тип Булево, по умолчанию Ложь) — определяет режим поиска.

Разработчики часто путают понятие «точный поиск» и «поиск по полному коду». Передавая значение Истина во второй параметр, программист предполагает, что заставляет платформу искать точное совпадение строки. Однако поведение платформы принципиально иное.

Параметр ПоискПоПолномуКоду = Истина означает, что искомый код должен быть передан в виде полного иерархического пути, в котором коды родительских групп разделены символом косой черты (слэшем) /. Например: "01/02/00-1234".

Если в базе данных существует элемент с кодом "00-1234", но он находится внутри группы (папки) с кодом "02", то при поиске с параметром Истина и строкой поиска "00-1234" платформа будет искать элемент только на самом верхнем уровне иерархии (как если бы у него не было родителей). Естественно, платформа ничего не находит и возвращает пустую ссылку.

Когда же дело доходит до записи нового элемента, платформа пытается записать объект с кодом "00-1234". В этот момент СУБД проверяет уникальность кодов во всем справочнике (или в пределах подчинения) и блокирует запись, так как элемент с таким кодом в базе данных физически существует.

Решение: Для обычного поиска элемента по его индивидуальному коду второй параметр должен иметь значение Ложь (или его можно вообще не указывать, так как это значение по умолчанию).

Посмотрим на пример правильного вызова:


// Поиск по индивидуальному коду элемента во всем справочнике
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоКоду(Код, Ложь);

Причина №2. Использование устаревшего метода СоздатьОбъект()

В коде автора присутствует еще одна критическая ошибка, которая приведет к аварийному завершению работы в любой современной конфигурации на платформе 1С v8:

Элемент = Справочники.Номенклатура.СоздатьОбъект();

Метод СоздатьОбъект() использовался в устаревшей платформе 1С:Предприятие 7.7. Начиная с версии 8.0 и во всех последующих релизах 8.3, для создания новых элементов справочников используется метод СоздатьЭлемент() (или СоздатьГруппу() для создания папок).

Попытка вызвать СоздатьОбъект() вызовет системное исключение: «Метод объекта не обнаружен (СоздатьОбъект)».

Решение: Заменим устаревший метод на актуальный:

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

Причина №3. Риск параллельной работы пользователей (Race Condition)

Рассмотрим более глубокую проблему, с которой сталкиваются высоконагруженные многопользовательские системы. Даже если вы исправите параметры поиска и методы создания, классический алгоритм «найти -> если нет -> создать» уязвим к так называемому «состоянию гонки» (Race Condition).

Представим ситуацию, когда два пользователя (или два фоновых задания при импорте данных) одновременно пытаются обработать один и тот же код номенклатуры:

  1. Сеанс 1 выполняет поиск кода "00-1234" — элемент не найден.
  2. Сеанс 2 параллельно выполняет поиск кода "00-1234" — элемент также не найден.
  3. Сеанс 1 создает новый объект, присваивает ему код и успешно записывает его в базу данных.
  4. Сеанс 2 пытается сделать то же самое, но транзакция прерывается ошибкой нарушения уникальности кода, так как Сеанс 1 уже зафиксировал транзакцию и занял этот код.

Выясним, как с этим бороться. Чтобы исключить подобные ошибки, процедуру поиска и создания необходимо выполнять внутри транзакции с использованием механизма управляемых блокировок.

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


Функция ПолучитьСоздатьЭлементБезопасно(ЗначениеКода) Экспорт
    
    РезультатСсылка = Справочники.Номенклатура.ПустаяСсылка();
    КодДляПоиска = СокрЛП(ЗначениеКода);
    
    НачатьТранзакцию();
    Попытка
        // Накладываем управляемую блокировку на конкретный код справочника
        Блокировка = Новый БлокировкаДанных;
        ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
        ЭлементБлокировки.УстановитьПараметр("Код", КодДляПоиска);
        Блокировка.Заблокировать();
        
        // Теперь мы уверены, что никто параллельно не создает элемент с таким кодом
        РезультатСсылка = Справочники.Номенклатура. НайтиПоКоду(КодДляПоиска, Ложь);
        
        Если РезультатСсылка.Пустая() Тогда
            НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
            НовыйОбъект.Код = КодДляПоиска;
            НовыйОбъект.Наименование = "Номенклатура " + КодДляПоиска;
            НовыйОбъект.Записать();
            РезультатСсылка = НовыйОбъект.Ссылка;
        КонецЕсли;
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации("Ошибки.СозданиеНоменклатуры",
            УровеньЖурналаРегистрации.Ошибка,,, 
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение;
    КонецПопытки;
    
    Возврат РезультатСсылка;
КонецФункции

Причина №4. Проблемы с длиной кода и концевыми пробелами

В СУБД строковые типы фиксированной длины дополняются пробелами справа. Если в конфигураторе для справочника задана длина кода, например, 9 символов, то при записи кода меньшей длины платформа автоматически заполняет недостающие символы пробелами.

Если мы записываем код "00-12" (длина 5 символов), в базу фактически запишется "00-12 " (с четырьмя пробелами в конце). При последующем поиске важно передавать точную строку. Несоответствие концевых пробелов или наличие неразрывных пробелов может привести к тому, что НайтиПоКоду() не обнаружит существующую запись.

Именно поэтому всегда рекомендуется использовать функцию СокрЛП() (сократить левые и правые пробелы) при работе со строковыми кодами и избегать ручного формирования кодовой строки без контроля ее итоговой длины.

Причина №5. Проверка на пустоту ссылки по стандартам 1С

В исходном коде автора для проверки результатов поиска используется конструкция:

Если ЗначениеЗаполнено(Элемент) Тогда

Несмотря на то, что функция ЗначениеЗаполнено() корректно определяет пустую ссылку и возвращает Ложь, этот метод не является оптимальным для проверки ссылочных типов.

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

Рассмотрим правильный вариант проверки:


// Стандартный и быстрый способ проверки ссылки в 1С
Если Не Элемент.Пустая() Тогда
    Возврат Элемент;
КонецЕсли;

Резюме и правильный шаблон функции

Мы подробно разобрали все узкие места исходного кода: от неправильного понимания параметров встроенных функций платформы до архитектурных рисков совместной работы пользователей в общей базе данных.

Подводя итог, сформируем качественный шаблон функции «Найти или Создать» для справочника на платформе 1С:Предприятие 8.3:


&НаСервере
Функция НайтиИлиСоздатьНоменклатуру(ЗначениеКода)
    
    // Очищаем код от лишних пробелов
    ОчищенныйКод = СокрЛП(ЗначениеКода);
    
    // Пытаемся найти элемент стандартным поиском без флага иерархии
    СсылкаНаЭлемент = Справочники.Номенклатура.НайтиПоКоду(ОчищенныйКод, Ложь);
    
    // Если элемент успешно найден, сразу возвращаем ссылку
    Если Не СсылкаНаЭлемент.Пустая() Тогда
        Возврат СсылкаНаЭлемент;
    КонецЕсли;
    
    // Если не найден, запускаем безопасный процесс создания в транзакции
    НачатьТранзакцию();
    Попытка
        // Блокируем код от параллельного создания другими сеансами
        Блокировка = Новый БлокировкаДанных;
        ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
        ЭлементБлокировки.УстановитьПараметр("Код", ОчищенныйКод);
        Блокировка.Заблокировать();
        
        // Повторный поиск внутри транзакции (на случай, если кто-то успел создать за миллисекунду до нас)
        СсылкаНаЭлемент = Справочники.Номенклатура.НайтиПоКоду(ОчищенныйКод, Ложь);
        
        Если СсылкаНаЭлемент.Пустая() Тогда
            НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
            НовыйЭлемент.Код = ОчищенныйКод;
            НовыйЭлемент.Наименование = "Новый элемент " + ОчищенныйКод;
            НовыйЭлемент.Записать();
            СсылкаНаЭлемент = НовыйЭлемент.Ссылка;
        КонецЕсли;
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписатьРаботуСбояВЛог(ОписаниеОшибки());
        ВызватьИсключение "Не удалось создать элемент справочника Номенклатура: " + ОписаниеОшибки();
    КонецПопытки;
    
    Возврат СсылкаНаЭлемент;
    
КонецФункции

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

← На главную