На моих проектах суть работы с COM-соединением состояла из следующих шагов:
1. Получение большого количества таблиц из базы источника. Данные получаются в основном запросами и программным интрефейсом.
2. Преобразование ссылочных полей в примитивные типы данных, которые можно прочитать на стороне базы приёмника.
3. Тестирование кода сначала без COM-соединения. Для этого код пишется во внешней обработке для базы источника.
4. Перенос кода в базу приёмник. Здесь и начинается самое весёлое! Сделать так, чтоб код, который уже отлажен, заработал через COM-соединение.
5. Преобразование таблиц из COM-объекта в тип "Таблица значений".
6. Поиск/создание ссылок. Поиск идёт по ключевым полям, которые на шаге 2 были преобразованы в примитивные типы данных.
7. Загрузка данных.
Все проблемы, с которыми я столкнулся, были на шаге 4 и 5. И именно об этих двух шагах моя публикация.
Но для того, чтоб статья содержала полную информацию, начну с начала.
Этап 1. Подключение к базе.
Для подключения нужны 4 параметра:
1. Имя сервера
2. Имя базы
3. Имя пользователя
4. Пароль.
Также стоит помнить, что у пользователя должны быть права на внешнее соединение.
Обычно задаю эти параметры в коде или в регистре сведений. В коде выглядит так:
//Установим параметры по умолчанию
Сервер = "Srvr=""s-dcr-db02.int""";
ИмяБазы = "Ref=""1c8mskrzn""";
ИмяПользователя = "Usr=""Филатов Павел""";
Пароль = "Pwd=""какой-то пароль""";
ПутьКБазе = Сервер + ";" + ИмяБазы + ";" + ИмяПользователя + ";" + Пароль;
Для подключения в управляемых формах обычно создаю 3 переменных:
Код кнопки подключиться к базе:
&НаКлиенте
Процедура ПодключитьсяКБазе(Команда)
ПодключитьсяКБазеНаСервере();
КонецПроцедуры
&НаСервере
Процедура ПодключитьсяКБазеНаСервере()
Если ПодключениеУстановлено Тогда
Если ЗначениеЗаполнено(АдресCOMСоединения) Тогда
УдалитьИзВременногоХранилища(АдресCOMСоединения);
КонецЕсли;
ПодключениеУстановлено = Ложь;
Иначе
Если Не ЗначениеЗаполнено(ПутьКБазе) Тогда
Возврат;
КонецЕсли;
COMСоединение = Новый COMObject("V83.COMConnector");
Попытка
ПодключениеКБазе = COMСоединение.Connect(ПутьКБазе);
//Если ранее было помещено соединение, удаляем его и помещаем новое
Если ЗначениеЗаполнено(АдресCOMСоединения) Тогда
УдалитьИзВременногоХранилища(АдресCOMСоединения);
Иначе
АдресCOMСоединения = ПоместитьВоВременноеХранилище(Новый Структура("ПодключениеКБазе", ПодключениеКБазе), Новый УникальныйИдентификатор);
КонецЕсли;
ПодключениеУстановлено = Истина;
Исключение
Инфо = ИнформацияОбОшибке();
Возврат;
КонецПопытки;
КонецЕсли;
Элементы.ПодключитьсяКБазе.Пометка = ПодключениеУстановлено;
КонецПроцедуры // ПодключитьсяКБазеУТНаСервере()
Сразу расскажу, зачем кладу во временное хранилище...
Даже если в модуле описать переменную ПодключениеКБазе как серверную, то это не гарантирует, что при следующем переходе с клиента на сервер она Вас там "будет ждать".
Поэтому она помещается в хранилище, а адрес запоминается в переменной формы "АдресCOMСоединения".
При следующем серверном вызове пишу такой код:
Если ЗначениеЗаполнено(АдресCOMСоединения) Тогда
СтруктураДанных = ПолучитьИзВременногоХранилища(АдресCOMСоединения);
ПодключениеКБазе = СтруктураДанных.ПодключениеКБазе;
Иначе
Возврат;
КонецЕсли;
//Получаем обработку как объект. К ней в модуле объекта привязаны ключевые переменные с данными.
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
ОбработкаОбъект.ПодключениеКБазе = ПодключениеКБазе;
ОбработкаОбъект.МенеджерВременныхТаблиц = ПодключениеКБазе.NewObject("МенеджерВременныхТаблиц");
И вот теперь с нами везде на сервере будет COM-соединение. Получение данных пишем в модуле объекта (у внешних обработок нет модуля менеджера).
Этап 2. Создание объектов через конструктор "Новый".
Почти все объекты создаются без особенностей с помощью конструкции NewObject(). В скобках в кавычках указывается название объекта.
Примеры наиболее часто используемых конструкций без особенностей:
Запрос = ПодключениеКБазе.NewObject("Запрос");
МенеджерВременныхТаблиц = ПодключениеКБазе.NewObject("МенеджерВременныхТаблиц");
Массив = ПодключениеКБазе.NewObject("Массив");
СписокЗначений = ПодключениеКБазе.NewObject("СписокЗначений");
Структура = ПодключениеКБазе.NewObject("Структура");
Соответствие = ПодключениеКБазе.NewObject("Соответствие");
Таблица = ПодключениеКБазе.NewObject("ТаблицаЗначений");
Особенности возникают при создании полей в таблице и описании их типов. Если без использования соединения код создания полей выглядит так:
//Получение квалификаторов строки, числа
КвалификаторыЧисла15_2 = Новый КвалификаторыЧисла(15, 2);
КвалификаторыСтроки150 = Новый КвалификаторыСтроки(150);
//Через COM-соединение
КвалификаторыЧисла15_2 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 15, 2);
КвалификаторыСтроки150 = ПодключениеКБазе.NewObject("КвалификаторыСтроки", 150);
//Получение описания типов числа
ОписаниеТиповЧисла15_2 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла15_2);
//Через COM-соединение
ОписаниеТиповЧисла15_2 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла15_2);
//Получение описания типов строки
ОписаниеТиповСтроки150 = Новый ОписаниеТипов("Строка",,,, КвалификаторыСтроки150);
//Через COM-соединение
ОписаниеТиповСтроки150 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Строка", КвалификаторыСтроки150);
//Получение описания ссылочных типов
ОписаниеТиповПодразделения = Новый ОписаниеТипов("СправочникСсылка.ПодразделенияОрганизаций");
//Через COM-соединение
ОписаниеТиповПодразделения = ПодключениеКБазе.NewObject("ОписаниеТипов", "СправочникСсылка.ПодразделенияОрганизаций");
Как видно из приведённых примеров, все параметры указываются через запятую, после названия создаваемого объекта.
В обоих проектах, где мне довелось использовать COM-соединение, получалось много таблиц при выгрузке. Все поля таблиц делал типизированными.
Чтобы не писать много раз создание колонок для каждой таблицы, сделал 2 процедуры с шаблонами, в которых менял только название полей:
1. Функция "ПолучитьСтруктуруОписанияТипов" получает все необходимые мне типы значений, что сильно сокращает количества кода при создании полей. Ниже её код, в т.ч. для COM-соединения:
//Обычный код
Функция ПолучитьСтруктуруОписанияТипов()
КвалификаторыЧисла15_2 = Новый КвалификаторыЧисла(15, 2);
КвалификаторыЧисла15_3 = Новый КвалификаторыЧисла(15, 3);
КвалификаторыЧисла3_0 = Новый КвалификаторыЧисла(3, 0);
КвалификаторыЧисла6_0 = Новый КвалификаторыЧисла(6, 0);
КвалификаторыЧисла10_0 = Новый КвалификаторыЧисла(10, 0);
КвалификаторыЧисла10_2 = Новый КвалификаторыЧисла(10, 2);
КвалификаторыЧисла6_2 = Новый КвалификаторыЧисла(6, 2);
КвалификаторыЧисла8_2 = Новый КвалификаторыЧисла(8, 2);
КвалификаторыСтроки9 = Новый КвалификаторыСтроки(9);
КвалификаторыСтроки150 = Новый КвалификаторыСтроки(150);
КвалификаторыСтроки350 = Новый КвалификаторыСтроки(350);
ОписаниеТиповЧисла15_2 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла15_2);
ОписаниеТиповЧисла15_3 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла15_3);
ОписаниеТиповЧисла3_0 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла3_0);
ОписаниеТиповЧисла6_0 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла6_0);
ОписаниеТиповЧисла10_0 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла10_0);
ОписаниеТиповЧисла10_2 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла10_2);
ОписаниеТиповЧисла6_2 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла6_2);
ОписаниеТиповЧисла8_2 = Новый ОписаниеТипов("Число",,, КвалификаторыЧисла8_2);
ОписаниеТиповПодразделения = Новый ОписаниеТипов("СправочникСсылка.ПодразделенияОрганизаций");
ОписаниеТиповСотрудники = Новый ОписаниеТипов("СправочникСсылка.СотрудникиОрганизаций");
ОписаниеТиповФизическиеЛица = Новый ОписаниеТипов("СправочникСсылка.ФизическиеЛица");
ОписаниеТиповДолжности = Новый ОписаниеТипов("СправочникСсылка.ДолжностиОрганизаций");
ОписаниеТиповСтроки9 = Новый ОписаниеТипов("Строка",,,, КвалификаторыСтроки9);
ОписаниеТиповСтроки150 = Новый ОписаниеТипов("Строка",,,, КвалификаторыСтроки150);
ОписаниеТиповСтроки350 = Новый ОписаниеТипов("Строка",,,, КвалификаторыСтроки350);
ОписаниеТиповБулево = Новый ОписаниеТипов("Булево");
ОписаниеТиповДаты = Новый ОписаниеТипов("Дата");
СтруктураОписанияТипов = Новый Структура;
СтруктураОписанияТипов.Вставить("ОписаниеТиповПодразделения", ОписаниеТиповПодразделения);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСотрудники", ОписаниеТиповСотрудники);
СтруктураОписанияТипов.Вставить("ОписаниеТиповФизическиеЛица", ОписаниеТиповФизическиеЛица);
СтруктураОписанияТипов.Вставить("ОписаниеТиповДолжности", ОписаниеТиповДолжности);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла15_2", ОписаниеТиповЧисла15_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла15_3", ОписаниеТиповЧисла15_3);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла3_0", ОписаниеТиповЧисла3_0);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла6_0", ОписаниеТиповЧисла6_0);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла10_0", ОписаниеТиповЧисла10_0);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла10_2", ОписаниеТиповЧисла10_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла6_2", ОписаниеТиповЧисла6_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла8_2", ОписаниеТиповЧисла8_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСтроки9", ОписаниеТиповСтроки9);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСтроки150", ОписаниеТиповСтроки150);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСтроки350", ОписаниеТиповСтроки350);
СтруктураОписанияТипов.Вставить("ОписаниеТиповБулево", ОписаниеТиповБулево);
СтруктураОписанияТипов.Вставить("ОписаниеТиповДаты", ОписаниеТиповДаты);
Возврат СтруктураОписанияТипов;
КонецФункции // ПолучитьСтруктуруОписанияТипов()
//Для COM-соединения
Функция ПолучитьСтруктуруОписанияТипов()
КвалификаторыЧисла15_2 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 15, 2);
КвалификаторыЧисла15_3 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 15, 3);
КвалификаторыЧисла3_0 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 3, 0);
КвалификаторыЧисла6_0 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 6, 0);
КвалификаторыЧисла10_0 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 10, 0);
КвалификаторыЧисла10_2 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 10, 2);
КвалификаторыЧисла6_2 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 6, 2);
КвалификаторыЧисла8_2 = ПодключениеКБазе.NewObject("КвалификаторыЧисла", 8, 2);
КвалификаторыСтроки9 = ПодключениеКБазе.NewObject("КвалификаторыСтроки", 9);
КвалификаторыСтроки150 = ПодключениеКБазе.NewObject("КвалификаторыСтроки", 150);
КвалификаторыСтроки350 = ПодключениеКБазе.NewObject("КвалификаторыСтроки", 350);
ОписаниеТиповЧисла15_2 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла15_2);
ОписаниеТиповЧисла15_3 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла15_3);
ОписаниеТиповЧисла3_0 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла3_0);
ОписаниеТиповЧисла6_0 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла6_0);
ОписаниеТиповЧисла10_0 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла10_0);
ОписаниеТиповЧисла10_2 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла10_2);
ОписаниеТиповЧисла6_2 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла6_2);
ОписаниеТиповЧисла8_2 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Число", КвалификаторыЧисла8_2);
ОписаниеТиповПодразделения = ПодключениеКБазе.NewObject("ОписаниеТипов", "СправочникСсылка.ПодразделенияОрганизаций");
ОписаниеТиповСтроки9 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Строка", КвалификаторыСтроки9);
ОписаниеТиповСтроки150 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Строка", КвалификаторыСтроки150);
ОписаниеТиповСтроки350 = ПодключениеКБазе.NewObject("ОписаниеТипов", "Строка", КвалификаторыСтроки350);
ОписаниеТиповБулево = ПодключениеКБазе.NewObject("ОписаниеТипов", "Булево");
ОписаниеТиповДаты = ПодключениеКБазе.NewObject("ОписаниеТипов", "Дата");
СтруктураОписанияТипов = ПодключениеКБазе.NewObject("Структура");
СтруктураОписанияТипов.Вставить("ОписаниеТиповПодразделения", ОписаниеТиповПодразделения);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла15_2", ОписаниеТиповЧисла15_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла15_3", ОписаниеТиповЧисла15_3);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла3_0", ОписаниеТиповЧисла3_0);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла6_0", ОписаниеТиповЧисла6_0);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла10_0", ОписаниеТиповЧисла10_0);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла10_2", ОписаниеТиповЧисла10_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла6_2", ОписаниеТиповЧисла6_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповЧисла8_2", ОписаниеТиповЧисла8_2);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСтроки9", ОписаниеТиповСтроки9);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСтроки150", ОписаниеТиповСтроки150);
СтруктураОписанияТипов.Вставить("ОписаниеТиповСтроки350", ОписаниеТиповСтроки350);
СтруктураОписанияТипов.Вставить("ОписаниеТиповБулево", ОписаниеТиповБулево);
СтруктураОписанияТипов.Вставить("ОписаниеТиповДаты", ОписаниеТиповДаты);
Возврат СтруктураОписанияТипов;
КонецФункции // ПолучитьСтруктуруОписанияТипов()
2. Процедура "ДобавитьПоля". Благодаря наличию первой функции, код этой процедуры не меняется при использовании COM-соединения. Что довольно удобно.
Процедура ДобавитьПоля(ТаблицаСправочника)
ТаблицаСправочника.Колонки.Добавить("КолонкаПодразделение", СтруктураОписанияТипов.ОписаниеТиповПодразделения);
ТаблицаСправочника.Колонки.Добавить("КолонкаСотрудник", СтруктураОписанияТипов.ОписаниеТиповСотрудники);
ТаблицаСправочника.Колонки.Добавить("КолонкаФизическоеЛицо", СтруктураОписанияТипов.ОписаниеТиповФизическиеЛица);
ТаблицаСправочника.Колонки.Добавить("КолонкаДолжность", СтруктураОписанияТипов.ОписаниеТиповДолжности);
ТаблицаСправочника.Колонки.Добавить("КолонкаСтрока9", СтруктураОписанияТипов.ОписаниеТиповСтроки9);
ТаблицаСправочника.Колонки.Добавить("КолонкаСтрока150", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("КолонкаСтрока350", СтруктураОписанияТипов.ОписаниеТиповСтроки350);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло15_2", СтруктураОписанияТипов.ОписаниеТиповЧисла15_2);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло15_3", СтруктураОписанияТипов.ОписаниеТиповЧисла15_3);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло3_0", СтруктураОписанияТипов.ОписаниеТиповЧисла3_0);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло6_0", СтруктураОписанияТипов.ОписаниеТиповЧисла6_0);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло10_0", СтруктураОписанияТипов.ОписаниеТиповЧисла10_0);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло10_2", СтруктураОписанияТипов.ОписаниеТиповЧисла10_2);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло6_2", СтруктураОписанияТипов.ОписаниеТиповЧисла6_2);
ТаблицаСправочника.Колонки.Добавить("КолонкаЧисло8_2", СтруктураОписанияТипов.ОписаниеТиповЧисла8_2);
ТаблицаСправочника.Колонки.Добавить("КолонкаБулево", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("КолонкаДата", СтруктураОписанияТипов.ОписаниеТиповДаты);
КонецПроцедуры // ДобавитьПоля()
Благодаря этим двум методам, у меня "сборка" новой таблицы занимает буквально минуту. Копирую поля нужных мне типов и из запроса копирую в ДобавитьПоля сами названия полей. Пример созданной реальной таблицы для выгрузки документа "Прием на работу в организацию":
Процедура ДобавитьПоляПриемыНаРаботуРаботающие(ТаблицаСправочника)
ТаблицаСправочника.Колонки.Добавить("Организация", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("Сотрудник", СтруктураОписанияТипов.ОписаниеТиповСотрудники);
ТаблицаСправочника.Колонки.Добавить("ВидЗанятости", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ВидДоговора", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ДатаПриема", СтруктураОписанияТипов.ОписаниеТиповДаты);
ТаблицаСправочника.Колонки.Добавить("ФизическоеЛицо", СтруктураОписанияТипов.ОписаниеТиповФизическиеЛица);
ТаблицаСправочника.Колонки.Добавить("ФИО", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ДатаРождения", СтруктураОписанияТипов.ОписаниеТиповДаты);
ТаблицаСправочника.Колонки.Добавить("НомерСтроки", СтруктураОписанияТипов.ОписаниеТиповЧисла6_0);
ТаблицаСправочника.Колонки.Добавить("ЗанимаемыхСтавок", СтруктураОписанияТипов.ОписаниеТиповЧисла6_2);
ТаблицаСправочника.Колонки.Добавить("ИспытательныйСрок", СтруктураОписанияТипов.ОписаниеТиповЧисла6_0);
ТаблицаСправочника.Колонки.Добавить("ДатаУвольнения", СтруктураОписанияТипов.ОписаниеТиповДаты);
ТаблицаСправочника.Колонки.Добавить("ГрафикРаботы", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("УсловияПриемаНаРаботу", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("Сторно", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("ТрудоваяФункция", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ОтразитьВТрудовойКнижке", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("НомерПриказа", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ДатаПриказа", СтруктураОписанияТипов.ОписаниеТиповДаты);
ТаблицаСправочника.Колонки.Добавить("ПодразделениеНаименование", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ПодразделениеКод", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("Аванс", СтруктураОписанияТипов.ОписаниеТиповЧисла15_2);
ТаблицаСправочника.Колонки.Добавить("СпособРасчетаАванса", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ДолжностьНаименование", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ДолжностьКод", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
КонецПроцедуры // ДобавитьПоляПриемыНаРаботуРаботающие()
Этап 3. Чтение данных. Адаптация кода под работу через COM-соединение.
По большей части, адаптировать нужно моменты, описанные в первых двух этапах. НО! Есть и сюрпризы... Вот какие:
- Не работает получение данных из коллекции по индексу через квадратные скобки.
Стоит отметить, что получение значения реквизита через квадратные скобки срабатывает! Примеры с комментариями:
//Получение значение реквизита по имени работает
Для Сч = 1 По ТекСтрока.КоличествоУровней Цикл
ТекПодразделение = ТекСтрока["Уровень" + Сч + "Ссылка"];
КонецЦикла;
//По этой же теме
СтруктураТаблицКадровогоУчета[ИмяТаблицы]
//Получение данных из коллекции.
//Как не работает:
//#1
ДатаРегистрации = ДатыРегистрации[СтрокаФизЛицо.ФизическоеЛицо];
//Как работает:
//Выручает метод Получить()
ДатаРегистрации = ДатыРегистрации.Получить(Выборка.ФизическоеЛицо);
//#2
//Работа с результатом запроса. Мы привыкли так писать:
ВыборкаВычетыНаДетей = Результат[КолРез - 1].Выбрать();
//А работает только вот так:
ВыборкаВычетыНаДетей = Результат.Получить(КолРез - 1).Выбрать();
//#3
//К первой строке коллекции привыкли обращаться вот так:
НоваяСтрока.КодВычетаЛичный = СтрокиВычетыФизлиц[0].КодВычетаЛичный;
//Правильно через метод Получить()
НоваяСтрока.КодВычетаЛичный = СтрокиВычетыФизлиц.Получить(0).КодВычетаЛичный;
- Не все методы глобального контекста срабатывают. Примеры кода с комментариями:
//1. Проверка заполнения ссылочных типов. При этом, если значение примитивного типа и оно заполнено,
//в результате будет Истина. Но лучше и для них добавлять ПодключениеКБазе
//Не работает
ЗначениеЗаполнено(Выборка.ГоловнаяОрганизация)
//Так правильно:
ПодключениеКБазе.ЗначениеЗаполнено(Выборка.ГоловнаяОрганизация)
//Почему-то работает вот такой код (Выборка - это COM-объект!):
Если ПодключениеКБазе.ЗначениеЗаполнено(Выборка.ДополнительныйОтпуск) И ЗначениеЗаполнено(Выборка.ДатаНачалаДоп) И ЗначениеЗаполнено(Выборка.ДатаОкончанияДоп) И Выборка.ДатаНачалаДоп <= Выборка.ДатаОкончанияДоп Тогда
//...
КонецЕсли;
//2. Работа с примитивными типами в глобальном контексте сохраняется. Например, такой код сработает:
ИФНС = Лев(Выборка.КПП, 4);
//3. Заполнить значения свойств.
//Несмотря на то, что обе переменные содержат только реквизиты примитивных типов данных - не заполняет ничего!
//Не работает
ЗаполнитьЗначенияСвойств(НоваяСтрокаКонтактнойИнформации, ЗначенияПолей);
//Работает
ПодключениеКБазе.ЗаполнитьЗначенияСвойств(НоваяСтрокаКонтактнойИнформации, ЗначенияПолей);
//Почему-то работает вот такое (Выборка - это COM-объект!):
ЗаполнитьЗначенияСвойств(СтрокаКонтактнойИнформации, Выборка, "ФИО, ДатаРождения");
4. Не работают привычные нам ТипЗнч() и Тип(). Ведь все привыкли проверять тип переменных вот так
//Не работает
Если ТипЗнч(ВидКонтактнойИнформации) = Тип("СправочникСсылка.ВидыКонтактнойИнформации") Тогда
//...
КонецЕсли;
//Работает только так
Если ПодключениеКБазе.XMLТипЗнч(ВидКонтактнойИнформации).TypeName = "CatalogRef.ВидыКонтактнойИнформации" Тогда
//...
КонецЕсли;
- Обращение к предопределённым элементам также с особенностями. Не работает сравнение ссылок.
Просто получение предопределенного элемента, и добавление его в массив или список значений отлично работает:
//Предопределенные
СписокЗначений = ПодключениеКБазе.NewObject("СписокЗначений");
СписокЗначений.Добавить(ПодключениеКБазе.Справочники.ПоказателиСхемМотивации.ТарифнаяСтавкаДневная);
СписокЗначений.Добавить(ПодключениеКБазе.Справочники.ПоказателиСхемМотивации.ТарифнаяСтавкаМесячная);
СписокЗначений.Добавить(ПодключениеКБазе.Справочники.ПоказателиСхемМотивации.ТарифнаяСтавкаЧасовая);
//Обращение к другим метаданным также работает
РеглВалюта = ПодключениеКБазе.Константы.ВалютаРегламентированногоУчета.Получить();
СправочникМенеджер = ПодключениеКБазе.Справочники.ВидыКонтактнойИнформации;
//Сравнение значений ссылочных типов, в т.ч. с предопределенными не работает!
//#Для перечислений
//Не работающий вариант
Если ТипКонтактнойИнформации = Перечисления.ТипыКонтактнойИнформации.Адрес Тогда
//...
КонецЕсли;
//Работающий вариант
//Т.к. сравнение идёт в рамках одной базы, можем сравнить индекс перечисления
ТипАдрес = ПодключениеКБазе.Перечисления.ТипыКонтактнойИнформации.Адрес;
Если ПодключениеКБазе.Перечисления.ТипыКонтактнойИнформации.Индекс(ТипКонтактнойИнформации) = ПодключениеКБазе.Перечисления.ТипыКонтактнойИнформации.Индекс(ТипАдрес) Тогда
//...
КонецЕсли;
//#Для справочников
//Не работающий вариант
Если ВидКонтактнойИнформации = Справочники.ВидыКонтактнойИнформации.ФактАдресФизЛица Тогда
//...
КонецЕсли;
//Работающий вариант
Если ПодключениеКБазе.XMLСтрока(ВидКонтактнойИнформации) = ПодключениеКБазе.XMLString(ПодключениеКБазе.Справочники.ВидыКонтактнойИнформации.ФактАдресФизЛица) Тогда
//...
КонецЕсли;
- Вызов процедур и функций из общих модулей. Пример с комментариями:
//Данный код написан на стороне базы приёмника, т.е. через COM.
//Случайно обнаружил его в процессе написания статьи.
//Оказалось что в УПП и ЗУП 3.1 есть и общий модуль и функция с тем же названием.
//Т.к. параметр строковый, всё сработало
//Разложим строку в массив уровней
МассивУровней = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ПолноеНаименование, "/");
Для Сч = 0 По Уровень Цикл
НомерРеквизита = Сч + 1;
//Запишем каждый элемент в свой уровень
НовСтрокаПодразделения["Уровень" + НомерРеквизита] = МассивУровней[Сч];
КонецЦикла;
//Правильно вызывать так
МассивУровней = ПодключениеКБазе.СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ПолноеНаименование, "/");
При вызове методов из общих модулей следует помнить, что работаем через "Внешнее соединение". В настройках общего модуля должен стоять одноименный флажок.
В данном примере переменная "МассивУровней" это обычный "Массив", а не "COM-объект", поэтому квадратные скобки сработали.
Этот пример не стал менять на работу с COM-объектом, т.к. не имеет практического смысла. Результат функции правильный.
- Во многих случаях русскоязычный вариант не работает. Пока не попробуешь, не узнаешь в каких случаях нужен англоязычный вариант. Приведу примеры из моего опыта:
//ВыгрузитьКолонку() не сработало
СписокСотрудников = ТаблицаДанныхТабеля.UnloadColumn("Сотрудник");
//Процедура для выборки Сбросить()
ВыборкаПоПодразделениямУТ.Reset();
//Не сработало ТипЗнч()
ТипЗначенияПеречисления = ПодключениеКБазеЗУП.FromXMLType(ПодключениеКБазеЗУП.XMLTypeOf(ПодключениеКБазеЗУП.Перечисления.СостоянияСотрудника.ПустаяСсылка()));
Этап 4. Преобразование таблиц из COM-объекта в тип "ТаблицаЗначений".
Опишу принцип преобразования таблиц, который использую. Он состоит из 4-х шагов:
- Создаём новую структуру без использования COM-объекта. В этой структуре создаём те же самые таблицы, с теми же названиями.
- В цикле создаём таблицы значений через конструктор "Новый".
- Для текущей таблицы получаем соответствие колонок. Этот этап не обязательный. Если у Вас в источнике и приёмнике поля называются одинаково, то его пропускаем. Название колонок копируем из COM-объекта.
- Для каждой колонки теперь требуется определить какого она типа. В интернете на эту тему ничего не нашёл! Код написан методом проб и ошибок. Идея в следующем:
-- У описания типов есть массив определенных при создании колонки типов. Т.к. в моём случае тип всегда один, могу однозначно его получить.
-- Выше заранее создавал структуры с теми типами значений, которые использую при создании таблиц.
-- Для определения типа значения использую простой поиск в массиве. Во всём этом было крайне тяжело сопоставить типы значений, т.к. он везде COM-объект.
-- Для более точного определения типа использую квалификаторы строки и числа.
-- Новый тип колонки через условия также получаю из заранее подготовленной структуры.
- Создаю колонку с новым типом и полученным через соответствие наименованием. Таким образом, не нужно дублировать много процедур, которые использовались для создания таблиц через COM.
Код основной процедуры по преобразованию таблиц:
// Преобразует таблицы кадрового учета из COM-объекта в тип ТаблицаЗначений
Процедура ПреобразоватьТаблицыКадровогоУчета() Экспорт
//1. Создаем новую структуру
СтруктураТаблицКадровогоУчетаБезCOM = СоздатьСтруктуруТаблицБезCOM();
//2. Создаем в цикле таблицы
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчета Цикл
ТаблицаБезCOM = Новый ТаблицаЗначений;
//3. Получаем соответствие колонок
СоответствиеКолонок = ПолучениеСоответствиеКолонокПоТаблице(ТекТаблица.Ключ);
//4. В цикле по колонкам ...
Для Каждого ТекКолонка Из ТекТаблица.Значение.Колонки Цикл
НовыйТипКолонки = Неопределено;
//-- Определяем тип колонки. Длину строки и числа определяем по квалификаторам
ТипКолонки = ТекКолонка.ValueType.Types().Получить(0);
Если Не СтруктураОписанияТипов.ОписаниеТиповБулево.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Булево
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповБулево;
ИначеЕсли Не СтруктураОписанияТипов.ОписаниеТиповДаты.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Дата
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповДаты;
ИначеЕсли Не СтруктураОписанияТипов.ОписаниеТиповСтроки150.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Строка
Если ТекКолонка.ValueType.StringQualifiers.Length = 150 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки150;
ИначеЕсли ТекКолонка.ValueType.StringQualifiers.Length = 350 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки350;
ИначеЕсли ТекКолонка.ValueType.StringQualifiers.Length = 9 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки9;
КонецЕсли;
//Тип строки по умолчанию
Если НовыйТипКолонки = Неопределено Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки150;
КонецЕсли;
ИначеЕсли Не СтруктураОписанияТипов.ОписаниеТиповЧисла6_0.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Число
Если ТекКолонка.ValueType.NumberQualifiers.Digits = 15 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла15_2;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 3 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла15_3;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 3 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла3_0;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 6 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла6_0;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла6_2;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 10 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла10_0;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла10_2;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 8 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла8_2;
КонецЕсли;
КонецЕсли;
//Тип числа по умолчанию
Если НовыйТипКолонки = Неопределено Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла6_0;
КонецЕсли;
КонецЕсли;
//-- Получаем соответствие колонки
НовоеНаименованиеКолонки = СоответствиеКолонок.Получить(ТекКолонка.Name);
Если Не ЗначениеЗаполнено(НовоеНаименованиеКолонки) Тогда
Продолжить;
КонецЕсли;
//-- Создаем новую колонку
ТаблицаБезCOM.Колонки.Добавить(НовоеНаименованиеКолонки, НовыйТипКолонки);
КонецЦикла;
КонецЦикла;
КонецПроцедуры // ПреобразоватьТаблицыКадровогоУчета()
Код вспомогательных функций:
Функция СоздатьСтруктуруТаблицБезCOM()
СтруктураТаблиц = Новый Структура;
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчета Цикл
СтруктураТаблиц.Вставить(ТекТаблица.Ключ);
КонецЦикла;
Возврат СтруктураТаблиц;
КонецФункции // СоздатьСтруктуруТаблицБезCOM()
Функция ПолучениеСоответствиеКолонокПоТаблице(ИмяТаблицы)
Если ИмяТаблицы = "" Тогда
СоответствиеКолонок = ПолучитьСоответствиеКолонок();
КонецЕсли;
Возврат СоответствиеКолонок;
КонецФункции // ПолучениеСоответствиеКолонокПоТаблице()
Функция ПолучитьСоответствиеКолонок()
СоответствиеКолонок = Новый Соответствие;
СоответствиеКолонок.Вставить("ИсходноеПоле1", "НовоеПоле1");
СоответствиеКолонок.Вставить("ИсходноеПоле2", "НовоеПоле2");
СоответствиеКолонок.Вставить("ИсходноеПоле3", "НовоеПоле3");
СоответствиеКолонок.Вставить("ИсходноеПоле4", "НовоеПоле4");
СоответствиеКолонок.Вставить("ИсходноеПоле5", "НовоеПоле5");
СоответствиеКолонок.Вставить("ИсходноеПоле6", "НовоеПоле6");
СоответствиеКолонок.Вставить("ИсходноеПоле7", "НовоеПоле7");
СоответствиеКолонок.Вставить("ИсходноеПоле8", "НовоеПоле8");
СоответствиеКолонок.Вставить("ИсходноеПоле9", "НовоеПоле9");
СоответствиеКолонок.Вставить("ИсходноеПоле10", "НовоеПоле10");
Возврат СоответствиеКолонок;
КонецФункции // ПолучитьСоответствиеКолонок()
Теперь довольно важный момент! При выборе этой технологии нужно учесть, что это не самая быстрая технология обмена данными.
Однако! Эта технология используется до сих пор во всех тиражных решениях вендора.
Я лично её выбираю за стабильность. В моём опыте не было непонятных проблем/ошибок/сбоев.
Также ориентируюсь на то, что далеко не в каждой организации есть Web-сервер.
В крупных организациях, чтоб его "поднять" или даже просто подключить новый обмен данными, необходимо подготовить и согласовать с безопасниками уйму бумаг.
В публикациях ниже также есть примеры универсального кода:
Помощник заполнения графиков при вахтовом методе работы
Отладка временных таблиц и типа ТаблицаЗначений
Чтение данных из Excel. Шаблон кода
Предыдущие публикации:
Как читать чужой код? Часть 4. Программный интерфейс. Исправление чужих доработок
Я - ЗУПер! Часть 1. Компетенции сотрудников. (продолжение в апреле)
Ни в ЗУП ногой!? А мне нравится! Часть 1. Главные сложности решения, что отталкивает