Почему структура не работает как ключ соответствия и как это исправить

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

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

Суть проблемы: Сравнение по ссылке vs Сравнение по значению

Проанализируем ситуацию, описанную в теме. Разработчик пытается использовать структуру с полями Объект и Период для поиска уже созданных документов. Проблема заключается в том, как платформа 1С:Предприятие работает с памятью. Объектные типы (Структура, Соответствие, Массив, ТаблицаЗначений) сравниваются по ссылке (адресу в памяти), а не по содержимому свойств.

Рассмотрим код, который приводит к ошибке:


СозданныеОбъекты = Новый Соответствие;
СтруктураКлюч = Новый Структура("ОбъектОграничения, Период");

Для Каждого Строка Из Таблица Цикл
    // ВНИМАНИЕ: Используется один и тот же экземпляр структуры
    СтруктураКлюч.ОбъектОграничения = Строка.Объект;
    СтруктураКлюч.Период = Строка.Период;
            
    ЗначениеСоответствия = СозданныеОбъекты.Получить(СтруктураКлюч);
    
    Если ЗначениеСоответствия = Неопределено Тогда
        // Создаем документ и помещаем в соответствие
        НовыйДок = Документы.НазваниеДокумента.СоздатьДокумент();
        // ... заполнение ...
        СозданныеОбъекты.Вставить(СтруктураКлюч, НовыйДок.Ссылка);
    КонецЕсли;
КонецЦикла;

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

Решение №1: Использование вложенных соответствий

Это один из самых надежных и производительных способов. Вместо одного ключа-структуры мы строим иерархию коллекций. Каждое измерение ключа становится уровнем вложенности. Рассмотрим, как правильно реализовать эту логику, чтобы избежать ошибок обращения к несуществующим элементам.

Разберем алгоритм по шагам:

  1. Проверяем наличие первого уровня ключа (например, Объект).
  2. Если его нет — создаем новое внутреннее Соответствие.
  3. Переходим на второй уровень (Период) и ищем значение.

СозданныеОбъекты = Новый Соответствие; // Главное соответствие

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

Преимущество этого метода в том, что Объект (ссылка на элемент справочника) и Период (тип Дата) являются простыми или ссылочными типами данных, которые корректно сравниваются платформой "по значению".

Решение №2: Конкатенация в строковый ключ

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

Выясним, как сформировать такой ключ правильно. Нам нужно объединить все значимые поля в одну строку, используя уникальный разделитель, который точно не встретится в строковых представлениях данных.


СозданныеОбъекты = Новый Соответствие;

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

Важный нюанс: Метод со строками работает чуть медленнее из-за затрат на преобразование типов и конкатенацию, но он гораздо проще в реализации и чтении кода.

Решение №3: Таблица значений с индексами

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

Проанализируем пример реализации через ТЗ:


КэшДокументов = Новый ТаблицаЗначений;
КэшДокументов.Колонки.Добавить("Объект");
КэшДокументов.Колонки.Добавить("Период");
КэшДокументов.Колонки.Добавить("Ссылка");

// Индексация — критически важный шаг для скорости поиска!
КэшДокументов.Индексы.Добавить("Объект, Период");

Для Каждого Строка Из Таблица Цикл
    Отбор = Новый Структура("Объект, Период", Строка.Объект, Строка.Период);
    НайденныеСтроки = КэшДокументов.НайтиСтроки(Отбор);
    
    Если НайденныеСтроки.Количество() = 0 Тогда
        // Создаем документ...
        НоваяСтрока = КэшДокументов.Добавить();
        НоваяСтрока.Объект = Строка.Объект;
        НоваяСтрока.Период = Строка.Период;
        НоваяСтрока.Ссылка = НоваяСсылка;
    Иначе
        ДокументСсылка = НайденныеСтроки[0].Ссылка;
    КонецЕсли;
КонецЦикла;

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

Подведем итоги

Для корректной работы поиска в коллекциях 1С необходимо соблюдать следующие правила:

  1. Никогда не используйте Структура, Массив или Соответствие в качестве ключа для другого Соответствия, если ваша цель — поиск по значениям их свойств.
  2. Используйте вложенные соответствия для высокой производительности и чистоты типов данных.
  3. Используйте строковую конкатенацию для простых задач и быстрого прототипирования.
  4. Используйте Таблицу Значений с индексами, если требуется поиск по многим полям или последующая аналитическая обработка кэша.

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

← На главную