При интеграции 1С с внешними системами (например, базами данных MSSQL, веб-сервисами или сторонними API через формат JSON) разработчики часто сталкиваются с типовыми трудностями. Перенос простых текстовых или числовых данных обычно не вызывает проблем, но как только дело доходит до заполнения ссылочных реквизитов, работы с табличными частями или обработки дат, начинают возникать скрытые ошибки.
В этой статье мы подробно разберем, как правильно переносить данные из внешнего массива в документ 1С, проанализируем частые ошибки начинающих разработчиков и покажем, как оптимизировать этот процесс для обеспечения высокой скорости работы системы.
Рассмотрим типичную ситуацию. Из внешней базы данных загружается текстовое наименование объекта (например, название курса или контрагента). Разработчик пытается напрямую записать эту строку в реквизит документа, который имеет тип ссылки на справочник:
ДокументОбъект.TEST = ЭлементМассива.TestName; // Ошибка! Попытка записать Строку в Ссылку
В платформе 1С тип данных Строка и тип данных СправочникСсылка — это абсолютно разные сущности. Напрямую присвоить строку ссылочному реквизиту нельзя: платформа либо проигнорирует это присвоение (оставив поле пустым), либо выдаст ошибку несоответствия типов.
Чтобы решить эту задачу, нам необходимо предварительно найти элемент в справочнике по его наименованию. Если такой элемент уже существует в базе данных 1С, мы должны получить его ссылку. Если элемента нет — его нужно создать программно, записать и только после этого получить ссылку на созданный объект.
Анализируя варианты решения, важно обратить внимание на распространенную логическую ошибку, которая часто встречается в коде начинающих программистов:
// Пример кода с логической ошибкой!
Тест = ЭлементМассива.TestName;
ТестСсылка = Справочники.Тест.НайтиПоНаименованию(Тест);
Если НЕ ТестСсылка = Справочники.Тест.ПустаяСсылка Тогда
ТестСсылка = Справочники.Тест.СоздатьЭлемент();
ТестСсылка.Наименование = Тест;
ТестСсылка.Записать();
КонецЕсли;
ДокументОбъект.TEST = ТестСсылка;
Разберем причину ошибки: в данном условии оператор НЕ меняет логику на противоположную. Конструкция буквально говорит системе: «Если ссылка НЕ пустая (то есть элемент НАЙДЕН), то зайди внутрь условия и создай новый дублирующий элемент». Если же элемент действительно не найден (ссылка пустая), код просто пропустит условие, ничего не создаст и запишет в документ пустую ссылку. Кроме того, при создании элемента переменной ТестСсылка ошибочно присваивается сам объект справочника, а не его ссылка.
Давайте исправим эту логику и напишем правильный, отказоустойчивый алгоритм:
ТестНаименование = СокрЛП(ЭлементМассива.TestName);
// Ищем элемент в справочнике по наименованию
ТестСсылка = Справочники.Тест.НайтиПоНаименованию(ТестНаименование);
// Если элемент НЕ найден (то есть ссылка пустая), создаем его
Если ТестСсылка.Пустая() Тогда
НовыйЭлементОбъект = Справочники.Тест.СоздатьЭлемент();
НовыйЭлементОбъект.Наименование = ТестНаименование;
Попытка
НовыйЭлементОбъект.Записать();
ТестСсылка = НовыйЭлементОбъект.Ссылка; // Получаем ссылку созданного элемента
Исключение
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,,
"Не удалось записать элемент справочника: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ТестСсылка = Справочники.Тест.ПустаяСсылка();
КонецПопытки;
КонецЕсли;
// Теперь безопасно заполняем реквизит документа
ДокументОбъект.TEST = ТестСсылка;
---
При заполнении табличных частей документов часто возникает следующая ошибка. Разработчик пытается заполнить разные колонки строки табличной части последовательными вызовами метода Добавить():
// Ошибка! Создается две разные строки вместо одной
ДокументОбъект.Курсы.Добавить().Английский = ЭлементМассива.EngName;
ДокументОбъект.Курсы.Добавить().Испанский = ЭлементМассива.SpainName;
Выясним причину проблемы: метод Добавить() для табличной части при каждом вызове создает **новую пустую строку** в памяти, записывает в указанную колонку значение и возвращает управление. Таким образом, в приведенном выше примере в табличную часть будут добавлены две строки: в первой строке будет заполнена только колонка "Английский" (а "Испанский" останется пустой), а во второй строке — только "Испанский" (при пустом значении "Английский").
Для того чтобы данные разных колонок попадали в одну и ту же физическую строку документа, нам нужно сначала создать строку, получить на нее ссылку в переменную и затем заполнять реквизиты этой конкретной строки. Рассмотрим правильный пример:
// Создаем одну строку и сохраняем ее в переменную "НоваяСтрока"
НоваяСтрока = ДокументОбъект.Курсы.Добавить();
// Заполняем реквизиты созданной строки
НоваяСтрока.Английский = ЭлементМассива.EngName;
НоваяСтрока.Испанский = ЭлементМассива.SpainName;
---
Передавая данные из MSSQL через формат JSON, разработчики регулярно сталкиваются с тем, что даты превращаются в пустые значения (01.01.0001 0:00:00) или отображаются как некорректные текстовые строки.
Дело в том, что стандарт JSON не имеет встроенного типа данных для представления даты и времени. Любая дата при сериализации в JSON преобразуется в строку (обычно в формате ISO 8601, например: "2026-03-30T14:25:00"). Когда платформа 1С считывает JSON, свойство объекта автоматически определяется как обычная Строка. Попытка записать эту строку напрямую в поле с типом Дата приведет к тому, что 1С запишет пустую дату.
Чтобы решить эту проблему, необходимо выполнить преобразование строки в дату. В платформе 1С для этого существуют удобные встроенные инструменты.
Вариант 1. Использование функции ПрочитатьДатуJSON()
Если у вас на руках уже есть строковое представление даты из десериализованного JSON-файла, вы можете быстро преобразовать его с помощью глобального метода:
// Преобразуем строку формата ISO в дату 1С
НоваяСтрока.Дата = ПрочитатьДатуJSON(ЭлементМассива.DateAdd, ФорматДатыJSON.ISO);
Вариант 2. Автоматическое чтение дат при парсинге JSON
Вы можете настроить механизм 1С так, чтобы он автоматически распознавал определенные свойства и конвертировал их в даты непосредственно в момент чтения JSON-файла. Для этого используется функция ПрочитатьJSON() с указанием параметров:
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
// Указываем имя свойства (или массив имен свойств), которые нужно преобразовать в Дату
ИменаСвойствСДатами = "DateAdd"; // Может быть массивом: Новый Массив; Массив.Добавить("DateAdd");
ДанныеИзJSON = ПрочитатьJSON(ЧтениеJSON, Ложь, ИменаСвойствСДатами, ФорматДатыJSON.ISO);
ЧтениеJSON.Закрыть();
---
Предложенный выше вариант решения с поиском элементов справочника с помощью НайтиПоНаименованию() отлично работает на небольших объемах данных. Однако, если ваш массив содержит сотни или тысячи записей, такой подход приведет к критическому падению производительности.
Каждый вызов метода НайтиПоНаименованию() генерирует отдельный запрос к СУБД. Выполнение сотен единичных запросов в цикле нагружает сервер и замедляет работу всей системы. Это классическая проблема производительности "1+N".
Давайте разберем по шагам, как оптимизировать этот алгоритм по правилам стандартов разработки 1С:
Соответствие, где ключом будет строковое наименование, а значением — ссылка на элемент справочника. Это позволит нам моментально получать ссылки в оперативной памяти без обращения к СУБД.Рассмотрим финальный, высокопроизводительный вариант кода, объединяющий все наши решения и оптимизации:
// ШАГ 1: Собираем уникальные наименования для поиска
МассивНаименований = Новый Массив;
Для Каждого ЭлементМассива Из ИсточникДанных Цикл
Если ЭлементМассива.Id1C = "0" Тогда
ИмяТеста = СокрЛП(ЭлементМассива.TestName);
Если НЕ ЗначениеЗаполнено(ИмяТеста) Тогда
Продолжить;
КонецЕсли;
Если МассивНаименований.Найти(ИмяТеста) = Неопределено Тогда
МассивНаименований.Добавить(ИмяТеста);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// ШАГ 2: Ищем существующие элементы справочника одним запросом
КэшСправочника = Новый Соответствие;
Если МассивНаименований.Количество() > 0 Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Тест.Ссылка КАК Ссылка,
| Тест.Наименование КАК Наименование
|ИЗ
| Справочник.Тест КАК Тест
|ГДЕ
| Тест.Наименование В (&МассивНаименований)";
Запрос.УстановитьПараметр("МассивНаименований", МассивНаименований);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КэшСправочника.Вставить(Выборка.Наименование, Выборка.Ссылка);
КонецЦикла;
КонецЕсли;
// ШАГ 3: Заполнение документов в транзакции для максимального ускорения
НачатьТранзакцию();
Попытка
Для Каждого ЭлементМассива Из ИсточникДанных Цикл
Если ЭлементМассива.Id1C = "0" Тогда
ТестИмя = СокрЛП(ЭлементМассива.TestName);
Если НЕ ЗначениеЗаполнено(ТестИмя) Тогда
Продолжить;
КонецЕсли;
// Получаем ссылку из нашего кэша в оперативной памяти
ТестСсылка = КэшСправочника.Получить(ТестИмя);
// Если в кэше ссылки нет — значит элемент не найден, создаем его программно
Если ТестСсылка = Неопределено Тогда
НовыйЭлемент = Справочники.Тест.СоздатьЭлемент();
НовыйЭлемент.Наименование = ТестИмя;
НовыйЭлемент.Записать();
ТестСсылка = НовыйЭлемент.Ссылка;
// Сразу добавляем в кэш, чтобы избежать дублирования при повторном поиске
КэшСправочника.Вставить(ТестИмя, ТестСсылка);
КонецЕсли;
// Создаем и заполняем документ
ДокументОбъект = Документы.Док1.СоздатьДокумент();
ДокументОбъект.TEST = ТестСсылка;
ДокументОбъект.Дата = ТекущаяДата();
// Заполняем табличную часть в одну строку
НоваяСтрока = ДокументОбъект.Курсы.Добавить();
НоваяСтрока.Английский = ЭлементМассива.EngName;
НоваяСтрока.Испанский = ЭлементМассива.SpainName;
// Безопасно преобразуем дату из формата ISO JSON
Если ЗначениеЗаполнено(ЭлементМассива.DateAdd) Тогда
НоваяСтрока.Дата = ПрочитатьДатуJSON(ЭлементМассива.DateAdd, ФорматДатыJSON.ISO);
КонецЕсли;
// Записываем документ
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЦикла;
// Если все документы успешно созданы, фиксируем транзакцию
ЗафиксироватьТранзакцию();
Исключение
// В случае любой ошибки откатываем изменения, чтобы не засорять базу данных
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,,
"Ошибка при пакетном создании документов: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Процесс загрузки прерван из-за ошибки. Подробности в Журнале регистрации.";
КонецПопытки;
Использование транзакций, пакетных запросов и кэширования в оперативной памяти позволяет обрабатывать массивы данных в десятки раз быстрее по сравнению с базовым кодом, исключая зависания системы и блокировки таблиц базы данных.