При разработке на платформе 1С:Предприятие 8 одной из наиболее частых задач является подготовка и передача данных между сервером и клиентом, а также сериализация данных в формат JSON или XML для интеграции со сторонними системами. Поскольку специализированные объекты, такие как ТаблицаЗначений или ВыборкаИзРезультатаЗапроса, не могут существовать на стороне тонкого клиента или напрямую передаваться через HTTP-сервисы, разработчики используют универсальную альтернативу — массив структур.
Однако при программном создании массива структур начинающие (а иногда и опытные) разработчики сталкиваются с неприятным поведением системы: после завершения цикла обхода выборки все элементы массива оказываются заполнены абсолютно одинаковыми данными, соответствующими последней записи. Давайте подробно разберем, почему возникает эта проблема, изучим физику процессов в памяти платформы и рассмотрим три надежных способа решения данной задачи.
Проанализируем типичный пример некорректного кода, с которого часто начинается обсуждение этой проблемы на форумах:
ТестМассив = Новый Массив;
Пока Выборка.Следующий() Цикл
ТестСтруктура = Новый Структура("Выборка", Выборка);
ТестМассив.Добавить(ТестСтруктура);
КонецЦикла;
На первый взгляд код выглядит логично: на каждой итерации цикла создается новая структура, в нее записывается текущее состояние выборки, после чего структура добавляется в массив. Но при отладке мы обнаруживаем, что все элементы массива содержат одни и те же данные из самой последней строки выборки.
Суть проблемы кроется в специфике работы объекта типа ВыборкаИзРезультатаЗапроса. Этот объект является ссылочным типом данных (указателем) на системный поток данных. Когда мы вызываем метод Выборка.Следующий(), платформа 1С не пересоздает объект выборки в памяти. Она лишь перемещает внутренний указатель (курсор) на следующую строку результата запроса. Сам объект Выборка остается тем же самым физическим объектом в оперативной памяти компьютера.
Когда мы пишем код Новый Структура("Выборка", Выборка), мы не копируем текущие значения полей (например, Номенклатуру или Количество) в структуру. Вместо этого мы помещаем в структуру ссылку на сам объект выборки. В результате в наш массив раз за разом добавляются структуры, каждая из которых ссылается на один и тот же единственный экземпляр объекта Выборка. Когда цикл завершается, выборка позиционируется на последней строке (или за ней), и при обращении к любому элементу нашего массива мы видим одно и то же финальное состояние этого общего объекта.
Чтобы решить эту проблему, нам необходимо копировать не сам объект выборки, а конкретные значения его свойств на каждом шаге цикла. Рассмотрим наиболее эффективные способы сделать это.
Если нам требуется передать ограниченный и заранее известный набор полей, самым простым и производительным решением будет явное создание структуры с указанием конкретных свойств и их значений прямо в конструкторе.
Рассмотрим практический пример реализации этого подхода:
ТестМассив = Новый Массив;
Пока Выборка.Следующий() Цикл
// Явно копируем примитивные типы и ссылки на объекты базы данных
ТестСтруктура = Новый Структура("Номенклатура, Количество, Цена",
Выборка.Номенклатура,
Выборка.Количество,
Выборка.Цена);
ТестМассив.Добавить(ТестСтруктура);
КонецЦикла;
Преимущества этого способа:
Основным недостатком является рутина написания кода при большом количестве полей (например, если в структуре должно быть 20-30 колонок) — в таких ситуациях этот способ становится неудобным для поддержки.
Если полей много, или их состав может динамически меняться, ручное перечисление свойств усложняет код. В этом случае на помощь приходит встроенный метод платформы ЗаполнитьЗначенияСвойств().
Давайте разберем по шагам, как правильно использовать эту функцию:
ТестМассив = Новый Массив;
// Задаем строку с именами полей, которые мы хотим извлечь
СписокСвойств = "Номенклатура,Количество,Цена,Сумма,Склад,Характеристика";
Пока Выборка.Следующий() Цикл
// Создаем пустую заготовку структуры с заданными ключами
ТестСтруктура = Новый Структура(СписокСвойств);
// Копируем значения из текущей строки выборки в структуру по совпадающим именам
ЗаполнитьЗначенияСвойств(ТестСтруктура, Выборка);
ТестМассив.Добавить(ТестСтруктура);
КонецЦикла;
Важная техническая деталь: Метод ЗаполнитьЗначенияСвойств() копирует значения только в те свойства приемника, которые уже существуют в объекте-приемнике. Если бы мы создали пустую структуру через Новый Структура(), а затем вызвали заполнение, структура так и осталась бы пустой. Поэтому мы обязаны передать строку с именами колонок в конструктор структуры на этапе ее создания.
Если в процессе алгоритма вы уже выгрузили результат запроса в объект ТаблицаЗначений, ручной обход строк в цикле писать не обязательно. В составе Библиотеки стандартных подсистем (БСП) в общем модуле ОбщегоНазначения присутствует крайне полезная и оптимизированная функция ТаблицаЗначенийВМассив().
Давайте проанализируем её исходный код и разберем логику оптимизации, которую применили разработчики фирмы "1С":
Функция ТаблицаЗначенийВМассив(ТаблицаЗначений) Экспорт
Массив = Новый Массив();
СтруктураСтрокой = "";
НужнаЗапятая = Ложь;
// Шаг 1. Формируем строку колонок один раз вне цикла
Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл
Если НужнаЗапятая Тогда
СтруктураСтрокой = СтруктураСтрокой + ",";
КонецЕсли;
СтруктураСтрокой = СтруктураСтрокой + Колонка.Имя;
НужнаЗапятая = Истина;
КонецЦикла;
// Шаг 2. Обходим строки таблицы значений
Для Каждого Строка Из ТаблицаЗначений Цикл
// Быстро создаем структуру по заготовленной строке ключей
НоваяСтрока = Новый Структура(СтруктураСтрокой);
// Заполняем структуру данными текущей строки
ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);
Массив.Добавить(НоваяСтрока);
КонецЦикла;
Возврат Массив;
КонецФункции
Почему этот код работает быстро и считается эталонным?
Новый Структура(СтруктураСтрокой) работает в разы быстрее на уровне платформы, чем динамическое добавление свойств через метод структуры Вставить() внутри цикла.Выбор оптимального метода зависит от архитектурных ограничений и объемов обрабатываемых данных:
Структура имеет довольно большой "вес" в памяти по сравнению с обычной строкой таблицы. Если ваш запрос возвращает более 50 000 - 100 000 строк, конвертация всей этой таблицы в массив структур может привести к лавинообразному росту потребления ОЗУ и даже к падению рабочего процесса сервера 1С. В таких случаях старайтесь обрабатывать данные порциями или оптимизируйте сам запрос, чтобы не передавать лишнюю информацию на клиентскую сторону.Таким образом, понимание разницы между ссылочными объектами и примитивными значениями в платформе 1С позволяет не только избегать досадных ошибок с "одинаковыми значениями" в массиве, но и писать по-настоящему быстрый, надежный и оптимизированный код.