При интеграции различных информационных систем на платформе 1С:Предприятие разработчики часто сталкиваются с необходимостью использования механизма Внешних источников данных (ВИД). Этот инструмент позволяет напрямую подключаться к сторонним базам данных (MS SQL, PostgreSQL, Oracle и др.) и работать с их таблицами так, будто это стандартные объекты конфигурации.
Однако при попытке записи данных во внешние источники, особенно если целевая СУБД является базой данных другой конфигурации 1С, возникает ряд неочевидных проблем. Самая распространенная из них — это запись ссылочных полей, которые в СУБД представлены в виде двоичных данных binary(16) (уникальных идентификаторов или GUID). В этой статье мы подробно разберем, почему стандартные методы преобразования не работают, как правильно настроить внешний источник данных в конфигураторе и какие существуют архитектурные альтернативы прямой записи в SQL.
Рассмотрим ситуацию, с которой столкнулся автор темы. Он попытался добавить запись в таблицу внешнего источника данных, которая является физической таблицей регистра сведений другой базы 1С (таблица dbo._InfoRg4834 «Аналоги номенклатуры»). Поля связи (измерения «Номенклатура» и «Аналог») имеют тип Двоичные данные.
Для преобразования ссылки 1С в двоичные данные автор использовал следующий код:
Функция ПолучитьДВ_Товара(ссылка)
гуид = ссылка.уникальныйИдентификатор();
возврат Base64Значение(гуид);
КонецФункции
Этот подход содержит критическую ошибку. Функция Base64Значение() ожидает на вход строку, закодированную по стандарту Base64. Строковое представление УникальногоИдентификатора (например, "11223344-5566-7788-9900-AABBCCDDEEFF") не является строкой Base64. В результате функция возвращает некорректный или пустой буфер двоичных данных. Платформа при попытке выполнить метод Записать() набора записей не выдает явных ошибок, но в базу данных СУБД ничего не записывается, так как данные не проходят валидацию или СУБД отвергает неверный формат.
Вторая проблема заключается в специфике хранения уникальных идентификаторов в СУБД платформой 1С. Порядок байтов в UUID на стороне сервера приложений 1С отличается от порядка байтов, в котором они физически записываются в таблицы баз данных MS SQL или PostgreSQL. Это так называемый байт-свапинг (byte-swapping).
Самый простой и надежный способ подружить 1С с уникальными идентификаторами внешнего источника — это переопределить типы полей в метаданных внешнего источника данных. Нам не нужно вручную преобразовывать ссылки в двоичный формат, если платформа может сделать это автоматически.
Выполним следующие шаги для настройки:
dbo._InfoRg4834).Номенклатура и Аналог).Двоичные данные на УникальныйИдентификатор.Записать() у набора записей внешнего источника не сможет сопоставить данные и выполнить модификацию в СУБД.После этого код записи существенно упрощается. Нам больше не нужны сложные функции конвертации. Платформа 1С самостоятельно преобразует объект типа УникальныйИдентификатор в двоичное представление binary(16) с учетом всех правил байт-свапинга конкретной СУБД:
ВнИст = ВнешниеИсточникиДанных.Базе1с.таблицы.dbo__InfoRg4834.СоздатьНаборЗаписей();
ВнИст.Отбор.Номенклатура.ВидСравнения = ВидСравнения.Равно;
ВнИст.Отбор.Номенклатура.Использование = Истина;
ВнИст.Отбор.Номенклатура.Значение = Левое.УникальныйИдентификатор();
ВнИст.Отбор.Аналог.ВидСравнения = ВидСравнения.Равно;
ВнИст.Отбор.Аналог.Использование = Истина;
ВнИст.Отбор.Аналог.Значение = Правое.УникальныйИдентификатор();
ВнИст.Прочитать();
Если ВнИст.Количество() = 0 Тогда
НоваяСтрока = ВнИст.Добавить();
НоваяСтрока.Номенклатура = Левое.УникальныйИдентификатор();
НоваяСтрока.Аналог = Правое.УникальныйИдентификатор();
КонецЕсли;
Попытка
ВнИст.Записать();
Исключение
Сообщить("Ошибка при записи во внешний источник: " + ОписаниеОшибки());
КонецПопытки;
Рассмотрим ситуацию, когда изменить тип поля во внешнем источнике данных в конфигураторе по каким-то причинам невозможно (например, это таблица сторонней системы, где GUID хранится в текстовом виде или специфическом бинарном формате, отличном от стандартов 1С). В этом случае нам придется написать функцию ручной конвертации с перестановкой байтов.
Давайте разберем алгоритм перестановки блоков GUID. Стандартный GUID в 1С выглядит так: 11223344-5566-7788-9900-AABBCCDDEEFF. Он состоит из пяти групп символов. В СУБД эти группы записываются в следующем порядке:
[Группа 4][Группа 5][Группа 3][Группа 2][Группа 1]
Для нашего примера целевая hex-строка должна выглядеть так: 9900AABBCCDDEEFF7788556611223344. Только после такой перестановки hex-строку можно конвертировать в массив байт и передавать как двоичные данные.
Реализуем этот алгоритм на языке 1С:
Функция ПолучитьБинарныйИдентификатор(СсылкаНаОбъект) Экспорт
УИДСтрока = СтрЗаменить(НРег(Строка(СсылкаНаОбъект.УникальныйИдентификатор())), "-", "");
// Разбиваем строку GUID на блоки по правилам СУБД 1С
Блок1 = Сред(УИДСтрока, 1, 8);
Блок2 = Сред(УИДСтрока, 9, 4);
Блок3 = Сред(УИДСтрока, 13, 4);
Блок4 = Сред(УИДСтрока, 17, 4);
Блок5 = Сред(УИДСтрока, 21, 12);
// Собираем hex-строку в правильной последовательности байт
HexСтрокаСУБД = Блок4 + Блок5 + Блок3 + Блок2 + Блок1;
// Преобразуем hex-строку в ДвоичныеДанные
Возврат ПолучитьДвоичныеДанныеИзГексСтроки(HexСтрокаСУБД);
КонецФункции
Функция ПолучитьДвоичныеДанныеИзГексСтроки(Знач HexСтрока)
// Создаем буфер двоичных данных нужной длины (16 байт)
ЗаписьДанных = Новый ЗаписьДанных(Новый ФайлВыводаКод(16));
Для Индекс = 1 По 16 Цикл
БайтСтр = Сред(HexСтрока, (Индекс - 1) * 2 + 1, 2);
ЧислоБайт = ЧислоИзHex(БайтСтр);
ЗаписьДанных.ЗаписатьБайт(ЧислоБайт);
КонецЦикла;
Результат = ЗаписьДанных.Закрыть();
Возврат Результат;
КонецФункции
Функция ЧислоИзHex(Знач ЗначениеHex)
// Простая функция конвертации двух символов HEX в десятичное число от 0 до 255
СписокСимволов = "0123456789abcdef";
Символ1 = Сред(ЗначениеHex, 1, 1);
Символ2 = Сред(ЗначениеHex, 2, 1);
Возврат (Найти(СписокСимволов, Символ1) - 1) * 16 + (Найти(СписокСимволов, Символ2) - 1);
КонецФункции
Используя этот метод, мы получим валидный объект типа ДвоичныеДанные, который СУБД успешно распознает как идентификатор ссылки.
Несмотря на то, что технически прямая запись в базу данных 1С через Внешние источники данных возможна (после исправления типов или байт-свапинга), делать это в продуктивных средах категорически не рекомендуется по следующим причинам:
ПередЗаписью, ПриЗаписи), подписки на события и не регистрируются изменения на планах обмена. Это приведет к нарушению логической целостности базы и сбоям в последующей синхронизации данных.Для интеграции двух баз 1С лучшим решением будет создание простого HTTP-сервиса на стороне базы-приемника. Это решает все проблемы с безопасностью, кэшированием и лицензированием.
Давайте посмотрим, как просто реализовать этот механизм. На стороне базы-приемника создадим HTTP-сервис с шаблоном пути /analogs и методом POST. Код обработчика запроса на стороне приемника:
Функция ДобавитьАналог(Запрос)
// Получаем структуру данных из тела JSON-запроса
СтрокаТела = Запрос.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаТела);
Данные = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Преобразуем строковые GUID обратно в ссылки базы-приемника
НоменклатураСсылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(Данные.Номенклатура));
АналогСсылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(Данные.Аналог));
// Записываем данные стандартными средствами платформы
МенеджерЗаписи = РегистрыСведений.АналогиНоменклатуры.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = НоменклатураСсылка;
МенеджерЗаписи.Аналог = АналогСсылка;
Попытка
МенеджерЗаписи.Записать(Истина);
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки("{""status"":""success""}");
Исключение
Ответ = Новый HTTPСервисОтвет(500);
Ответ.УстановитьТелоИзСтроки("{""error"":""" + ОписаниеОшибки() + """}");
КонецПопытки;
Возврат Ответ;
КонецФункции
На стороне базы-отправителя отправка данных будет выглядеть следующим образом:
Соединение = Новый HTTPСоединение("server-name", , "пользователь", "пароль");
Запрос = Новый HTTPЗапрос("/ИмяИБ/hs/api/analogs");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ДанныеДляОтправки = Новый Структура;
ДанныеДляОтправки.Вставить("Номенклатура", Строка(Левое.УникальныйИдентификатор()));
ДанныеДляОтправки.Вставить("Аналог", Строка(Правое.УникальныйИдентификатор()));
ЗаписатьJSON(ЗаписьJSON, ДанныеДляОтправки);
СтрокаТела = ЗаписьJSON.Закрыть();
Запрос.УстановитьТелоИзСтроки(СтрокаТела);
Попытка
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Сообщить("Запись успешно добавлена через HTTP-сервис!");
Иначе
Сообщить("Ошибка при добавлении: " + Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
Сообщить("Не удалось связаться с сервером: " + ОписаниеОшибки());
КонецПопытки;
Используя этот подход, мы гарантируем сохранность данных, корректную работу транзакций, вызов всех бизнес-процедур платформы и полное соответствие лицензионной политике 1С.