Как правильно создать массив структур при обходе выборки или таблицы значений в 1С?

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

При разработке на платформе 1С:Предприятие 8 одной из наиболее частых задач является подготовка и передача данных между сервером и клиентом, а также сериализация данных в формат JSON или XML для интеграции со сторонними системами. Поскольку специализированные объекты, такие как ТаблицаЗначений или ВыборкаИзРезультатаЗапроса, не могут существовать на стороне тонкого клиента или напрямую передаваться через HTTP-сервисы, разработчики используют универсальную альтернативу — массив структур.

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

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

Проанализируем типичный пример некорректного кода, с которого часто начинается обсуждение этой проблемы на форумах:


ТестМассив = Новый Массив;
Пока Выборка.Следующий() Цикл            
    ТестСтруктура = Новый Структура("Выборка", Выборка);
    ТестМассив.Добавить(ТестСтруктура);
КонецЦикла;

На первый взгляд код выглядит логично: на каждой итерации цикла создается новая структура, в нее записывается текущее состояние выборки, после чего структура добавляется в массив. Но при отладке мы обнаруживаем, что все элементы массива содержат одни и те же данные из самой последней строки выборки.

Суть проблемы кроется в специфике работы объекта типа ВыборкаИзРезультатаЗапроса. Этот объект является ссылочным типом данных (указателем) на системный поток данных. Когда мы вызываем метод Выборка.Следующий(), платформа 1С не пересоздает объект выборки в памяти. Она лишь перемещает внутренний указатель (курсор) на следующую строку результата запроса. Сам объект Выборка остается тем же самым физическим объектом в оперативной памяти компьютера.

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

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

Способ №1: Явное указание полей выборки при инициализации структуры

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

Рассмотрим практический пример реализации этого подхода:


ТестМассив = Новый Массив;
Пока Выборка.Следующий() Цикл            
    // Явно копируем примитивные типы и ссылки на объекты базы данных
    ТестСтруктура = Новый Структура("Номенклатура, Количество, Цена", 
                                    Выборка.Номенклатура, 
                                    Выборка.Количество, 
                                    Выборка.Цена);
    ТестМассив.Добавить(ТестСтруктура);
КонецЦикла;

Преимущества этого способа:

  1. Высокая производительность: Конструктор структуры сразу инициализирует нужные ключи и присваивает им значения за один системный шаг.
  2. Экономия памяти: Не создаются промежуточные объекты, не выгружаются лишние данные.
  3. Простота отладки: Код нагляден, сразу видно, какие именно поля будут переданы на следующий этап обработки.

Основным недостатком является рутина написания кода при большом количестве полей (например, если в структуре должно быть 20-30 колонок) — в таких ситуациях этот способ становится неудобным для поддержки.

Способ №2: Автоматическое заполнение через ЗаполнитьЗначенияСвойств

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

Давайте разберем по шагам, как правильно использовать эту функцию:


ТестМассив = Новый Массив;
// Задаем строку с именами полей, которые мы хотим извлечь
СписокСвойств = "Номенклатура,Количество,Цена,Сумма,Склад,Характеристика";

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

Важная техническая деталь: Метод ЗаполнитьЗначенияСвойств() копирует значения только в те свойства приемника, которые уже существуют в объекте-приемнике. Если бы мы создали пустую структуру через Новый Структура(), а затем вызвали заполнение, структура так и осталась бы пустой. Поэтому мы обязаны передать строку с именами колонок в конструктор структуры на этапе ее создания.

Способ №3: Использование стандартных функций БСП (на примере выгрузки Таблицы Значений)

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

Давайте проанализируем её исходный код и разберем логику оптимизации, которую применили разработчики фирмы "1С":


Функция ТаблицаЗначенийВМассив(ТаблицаЗначений) Экспорт
    
    Массив = Новый Массив();
    СтруктураСтрокой = "";
    НужнаЗапятая = Ложь;
    
    // Шаг 1. Формируем строку колонок один раз вне цикла
    Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл
        Если НужнаЗапятая Тогда
            СтруктураСтрокой = СтруктураСтрокой + ",";
        КонецЕсли;
        СтруктураСтрокой = СтруктураСтрокой + Колонка.Имя;
        НужнаЗапятая = Истина;
    КонецЦикла;
    
    // Шаг 2. Обходим строки таблицы значений
    Для Каждого Строка Из ТаблицаЗначений Цикл
        // Быстро создаем структуру по заготовленной строке ключей
        НоваяСтрока = Новый Структура(СтруктураСтрокой);
        // Заполняем структуру данными текущей строки
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);
        Массив.Добавить(НоваяСтрока);
    КонецЦикла;
    
    Возврат Массив;
    
КонецФункции

Почему этот код работает быстро и считается эталонным?

Когда и какой метод лучше использовать?

Выбор оптимального метода зависит от архитектурных ограничений и объемов обрабатываемых данных:

Таким образом, понимание разницы между ссылочными объектами и примитивными значениями в платформе 1С позволяет не только избегать досадных ошибок с "одинаковыми значениями" в массиве, но и писать по-настоящему быстрый, надежный и оптимизированный код.

← На главную