При перегрузке данных из устаревших систем на платформе 1С:Предприятие 7.7 в современные конфигурации на 1С:Предприятие 8 разработчики часто сталкиваются со сложной задачей: как однозначно идентифицировать объекты? В 1С 8 каждый объект изначально обладает глобальным уникальным идентификатором (UUID/GUID), в то время как в 1С 7.7 такого штатного понятия в явном виде нет. Попытки сопоставлять объекты по коду или наименованию часто приводят к дублированию или коллизиям, особенно в иерархических справочниках с нумерацией в пределах подчинения.
В этой статье мы подробно разберем, как устроена идентификация объектов в 1С 7.7, как извлечь уникальный внутренний ID объекта, как сформировать на его основе воспроизводимый «Псевдо-GUID» для миграции и как при необходимости генерировать новые случайные GUID средствами платформы и операционной системы.
Вопреки распространенному мнению, уникальный идентификатор у каждого объекта в 1С 7.7 все-таки есть. Платформа хранит его в базе данных в виде 36-ричного числа (используются цифры 0–9 и латинские буквы A–Z). Этот идентификатор гарантирует абсолютную уникальность объекта в рамках всей информационной базы.
Чтобы получить доступ к этому идентификатору средствами встроенного языка, мы используем системную функцию ЗначениеВСтрокуВнутр(). Если передать в нее ссылку на объект (например, элемент справочника или документ), платформа вернет специальную текстовую строку следующего формата:
{"B","0","0","123","0","0"," 1ABC "}
Проанализируем структуру этой строки подробнее:
B обозначает справочник, O — документ, T — счет).Для того чтобы быстро вытащить «чистый» уникальный идентификатор объекта из внутренней строки, мы можем воспользоваться недокументированной возможностью платформы — функцией _IdToStr(). Она переводит обычное число в 36-ричный формат 1С 7.7.
Рассмотрим практический пример функции, которая извлекает этот идентификатор из любой ссылки:
Функция ПолучитьID(Ссылка)
Перем Стр;
Стр = ЗначениеВСтрокуВнутр(Ссылка);
СЗ = СоздатьОбъект("СписокЗначений");
// Отсекаем фигурные скобки и преобразуем строку в список значений
СЗ.ИзСтрокиСРазделителями(Сред(Стр, 2, СтрДлина(Стр) - 2));
// Возвращаем внутренний 36-ричный код
Возврат _IdToStr(СЗ.ПолучитьЗначение(СЗ.РазмерСписка()));
КонецФункции
Этот метод возвращает компактную строку, которая уникальна для каждого объекта и отлично подходит для использования в качестве ключа сопоставления при выгрузках.
Для построения надежных систем обмена данными нам часто требуется не только получать ID объекта, но и выполнять обратную операцию — находить реальный объект в базе данных по его текстовому идентификатору. Разберем комплексное решение этой задачи с помощью двух универсальных функций.
Первая функция анализирует переданный объект и возвращает подробную структуру его идентификаторов в виде списка значений:
// Функция раскладывает объект на составляющие части ID
Функция ПолучитьИДыОбъекта(Знач тОбъект, тИдСЗ = 0) Экспорт
Перем _Ок, тТипВозврата, тСЗ, тТхт;
тТхт = ТипЗначенияСтр(тИдСЗ);
Если тТхт = "СписокЗначений" Тогда
тИдСЗ.УдалитьВсе();
тТипВозврата = "*";
Иначе
Если тТхт = "Число" Тогда
тТипВозврата = тИдСЗ;
Иначе
тТипВозврата = Число(СокрЛП(тИдСЗ));
КонецЕсли;
тИдСЗ = СоздатьОбъект("СписокЗначений");
тТипВозврата = Макс(0, Мин(4, Окр(тТипВозврата, 0)));
тТипВозврата = ?(тТипВозврата = 4, "ИдИБ", ?(тТипВозврата = 3, "ИдОбъекта", ?(тТипВозврата = 2, "ИдВида", ?(тТипВозврата = 1, "ИдТипа", "*"))));
КонецЕсли;
тСЗ = СоздатьОбъект("СписокЗначений");
тТхт = ТипЗначения(тОбъект);
Если тТхт = 0 Тогда
тТхт = ТипЗначенияСтр(тОбъект);
ИначеЕсли тТхт = 10 Тогда тТхт = "Перечисление";
ИначеЕсли тТхт = 11 Тогда тТхт = "Справочник";
ИначеЕсли тТхт = 12 Тогда тТхт = "Документ";
ИначеЕсли тТхт = 15 Тогда тТхт = "Счет";
КонецЕсли;
Если тТхт = "СписокЗначений" Тогда
тОбъект.Выгрузить(тСЗ);
ИначеЕсли тТхт = "Строка" Тогда
тСЗ.ИзСтрокиСРазделителями(Сред(Лев(тОбъект, СтрДлина(тОбъект) - 1), 2));
ИначеЕсли Найти("СчетПеречислениеСправочникДокумент", тТхт) > 0 Тогда
тОбъект = ЗначениеВСтрокуВнутр(тОбъект);
тСЗ = СоздатьОбъект("СписокЗначений");
тСЗ.ИзСтрокиСРазделителями(Сред(Лев(тОбъект, СтрДлина(тОбъект) - 1), 2));
Иначе
Возврат("");
КонецЕсли;
тТхт = СокрЛП(тСЗ.ПолучитьЗначение(1));
Если ПустоеЗначение(тТхт) = 1 Тогда
тТхт = "U";
Иначе
тТхт = ?(Найти("TOBEU", Лев(тТхт, 1)) = 0, "U", Лев(тТхт, 1));
КонецЕсли;
_Ок = тТхт;
тТхт = тСЗ.ПолучитьЗначение(тСЗ.РазмерСписка());
Если СтрДлина(тТхт) = 13 Тогда
тТхт = Формат(Число(тСЗ.ПолучитьЗначение(4)), "Ч10");
Else
тТхт = Лев(тТхт, 10);
КонецЕсли;
_Ок = _Ок + тТхт;
тТхт = тСЗ.ПолучитьЗначение(тСЗ.РазмерСписка());
Если СтрДлина(тТхт) > 13 Тогда
тТхт = Прав(тТхт, 13);
КонецЕсли;
_Ок = _Ок + Формат(Число(Лев(тТхт, СтрДлина(тТхт) - 3)), "Ч10") + Прав(тТхт, 3);
тИдСЗ.Установить("ИдТипа", Лев(_Ок, 1));
тИдСЗ.Установить("ИдВида", Сред(_Ок, 2, 10));
тИдСЗ.Установить("ИдОбъекта", Сред(_Ок, 12, 10));
тИдСЗ.Установить("ИдИБ", Прав(_Ок, 3));
Если тТипВозврата <> "*" Тогда
_Ок = тИдСЗ.Получить(тТипВозврата);
КонецЕсли;
Возврат(_Ок);
КонецФункции
Вторая функция выполняет обратное преобразование — воссоздает полноценную ссылку на объект 1С по ранее сохраненным фрагментам его идентификатора:
// Функция находит объект базы данных по его составным идентификаторам
Функция ПолучитьОбъектПоИДам(Знач ИдТипа, Знач ИдВида = "", Знач ИдОбъекта = "", Знач ИдИБ = "", Знач тБазаОЛЕ = "") Экспорт
Перем _Ок;
Если ТипЗначенияСтр(ИдТипа) = "СписокЗначений" Тогда
ИдВида = ИдТипа.Получить("ИдВида");
ИдОбъекта = ИдТипа.Получить("ИдОбъекта");
ИдИБ = ИдТипа.Получить("ИдИБ");
ИдТипа = ИдТипа.Получить("ИдТипа");
Если ТипЗначенияСтр(тБазаОЛЕ) = "OLE" Тогда
_Ок = тБазаОЛЕ.ValueFromStringInternal("{""" + ИдТипа + """,""0"",""0"","""
+ ИдВида + """,""0"",""0"",""" + Формат(Число(ИдОбъекта), "Ч10") + Формат(ИдИБ, "С3") + """}");
Иначе
_Ок = ЗначениеИзСтрокиВнутр("{""" + ИдТипа + """,""0"",""0"","""
+ ИдВида + """,""0"",""0"",""" + Формат(Число(ИдОбъекта), "Ч10") + Формат(ИдИБ, "С3") + """}");
КонецЕсли;
ИначеЕсли СтрДлина(ИдТипа) = 24 Тогда
Если Лев(ИдТипа, 1) = "T" Тогда
_Ок = "{""T"",""0"",""0"",""0"",""0"",""0"",""" + Сред(ИдТипа, 2) + """}";
Иначе
_Ок = "{""" + Лев(ИдТипа, 1) + """,""0"",""0"",""" + Сред(ИдТипа, 2, 10) + """,""0"",""0"",""" + Сред(ИдТипа, 12) + """}";
КонецЕсли;
Если ТипЗначенияСтр(тБазаОЛЕ) = "OLE" Тогда
_Ок = тБазаОЛЕ.EvalExpr("ЗначениеИзСтрокиВнутр(""" + СтрЗаменить(_Ок, """", """""") + """)");
Иначе
_Ок = ЗначениеИзСтрокиВнутр(_Ок);
КонецЕсли;
ИначеЕсли Лев(ИдТипа, 1) = "{" Тогда
Если ТипЗначенияСтр(тБазаОЛЕ) = "OLE" Тогда
_Ок = tБазаОЛЕ.EvalExpr("ЗначениеИзСтрокиВнутр(""" + СтрЗаменить(ИдТипа, """", """""") + """)");
Иначе
_Ок = ЗначениеИзСтрокиВнутр(ИдТипа);
КонецЕсли;
КонецЕсли;
Возврат(_Ок);
КонецФункции
Если вы настраиваете правила обмена в программном комплексе «1С:Конвертация данных 2.1» или пишете собственный алгоритм выгрузки в XML, вам понадобится сопоставить 36-ричные ID из 1С 7.7 со стандартным форматом GUID (36 символов, шестнадцатеричное представление вида xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). Для выгрузки данных из информационной базы 7.7 можно использовать доработанную обработку выгрузки для Конвертации данных 2.1.
Для этого мы можем реализовать алгоритм формирования детерминированного Псевдо-GUID. Это означает, что для одного и того же объекта 1С 7.7 на основе его внутренних идентификаторов всегда будет генерироваться один и тот же GUID. Это полностью исключит появление дубликатов при повторных выгрузках.
Алгоритм генерации выглядит следующим образом:
ЗначениеВСтрокуВнутр.[Hex-ID вида объекта]-[Hex-ID объекта]-0000-0000-000000000000.Поскольку исходные данные в 1С 7.7 уникальны, сгенерированный по такой схеме GUID гарантированно будет уникальным в целевой базе 1С 8.
Иногда при интеграции внешних систем требуется не получить ID существующего объекта, а сгенерировать абсолютно новый, случайный GUID (например, для присвоения уникального номера пакету обмена или транзакции).
Рассмотрим несколько вариантов решения этой задачи.
Самый простой способ генерации нового GUID без использования внешних библиотек 1С — это обращение к системной библиотеке Windows:
Функция СоздатьGUID()
TypeLib = CreateObject("Scriptlet.TypeLib");
NewGUID = TypeLib.Guid();
// Избавляемся от лишних символов перевода строки, которые может вернуть COM-объект
Возврат СокрЛП(NewGUID);
КонецФункции
В терминальных сессиях (RDP) у пользователей часто ограничены права на запуск некоторых COM-объектов напрямую. В таких случаях можно использовать встроенный интерпретатор VBScript, который выполнит код внутри изолированного окружения:
Функция СгенерироватьБезопасныйGUID()
ScrptCtrl = СоздатьОбъект("MSScriptControl.ScriptControl");
ScrptCtrl.Language = "VBScript";
ScrptCtrl.AddCode("Function GetGUID() : Set TL = CreateObject(""Scriptlet.TypeLib"") : GetGUID = TL.Guid : End Function");
РезGUID = ScrptCtrl.Run("GetGUID");
Возврат СокрЛП(РезGUID);
КонецФункции
Если в вашей конфигурации подключена и успешно зарегистрирована стандартная внешняя компонента v7plus.dll, вы можете задействовать ее системный объект для генерации:
Функция ПолучитьGUIDЧерезV7Plus()
Если ЗагрузитьВнешнююКомпоненту("v7plus.dll") <> 1 Тогда
Предупреждение("Компонента v7plus.dll не найдена!");
Возврат "";
КонецЕсли;
СистемнаяИнфо = СоздатьОбъект("AddIn.V7SysInfo");
Возврат СистемнаяИнфо.СоздатьGUID();
КонецФункции
Если ваша информационная база 1С 7.7 работает под управлением MS SQL Server, то штатный перебор сотен тысяч объектов через циклы встроенного языка и метод ЗначениеВСтрокуВнутр займет слишком много времени.
Для ускорения процесса разработчики используют внешнюю компоненту 1С++, которая позволяет обращаться к таблицам базы данных напрямую через SQL. В базах данных 1С 7.7 уникальные идентификаторы хранятся в открытом виде:
ID соответствующей таблицы справочника (например, SC123).IDDOC системной таблицы DH* или 1SJOURN.Выполнив быстрый SQL-запрос, вы сможете мгновенно получить таблицу соответствий кодов, наименований и физических идентификаторов для миллионов записей за несколько секунд, минуя медленный объектный интерфейс 1С 7.7.