v8: Охота на Com-ов Ключевые слова: COM, OLE, значение перечисления, подключить удаленную базу.
Часть первая. Жажда скорости.Глава первая. Широкозакрытые глаза.
Чтение. Начнем с очевидных вещей. Первым шагом для существенного повышения скорости работы чтения через Внешнее Соединение считаю полный отказ от использования выборок/ссылок внешней базы.
Про конструкции вида
Выборка=V8.Справочники.Склады.Выбрать();
Пока Выборка.Следующий() Цикл
КонецЦикла;
советую вспоминать только в страшных ночных кошмарах. Ибо при обращении к любому реквизиту, не принадлежащему к основным данным объекта произойдет чтение всех реквизитов объекта. Если мы попытаемся получить реквизит основных данных - то произойдет чтение только основных данных, что тоже не хорошо. Использование же запроса позволит читать только то, что вам действительно нужно. Скажем так:
Запрос=V8.NewObject("Запрос");
Запрос.Текст=
"ВЫБРАТЬ
| Склады.Код,
| Склады.Наименование,
| Склады.ВидСклада.Порядок
|ИЗ
| Справочник.Склады КАК Склады";
Выборка=Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КонецЦикла;
позволит вам прочесть только то, что нужно... Также хочу вам сказать, что просто забудьте о таких запросах, как:
Запрос=Бухгалтерия.NewObject("Запрос");
Запрос.Текст=
"ВЫБРАТЬ
| Склады.Ссылка, //Вот здесь оно, это поле
| Склады.Код,
| Склады.Наименование,
| Склады.ВидСклада.Порядок
|ИЗ
| Справочник.Склады КАК Склады";
Выборка=Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КонецЦикла;
ибо ссылка на объект внешней базу в локальной базе БЕСПОЛЕЗНА (в большинстве случаев). Наличие этой ссылки может приводить к конструкции вида:
Пока Выборка.Следующий() Цикл
НашОбъект=ПоискПоКоду(Выборка.Ссылка.Код);
КонецЦикла;
которая циклично читает объект, а значит сводит на нет весь смысл использование запроса. Поверьте моему опыту, вы этого и не заметите, пока вам бухгалтер не скажет, что - же так долго выгрузка ползет...
Также не рекомендую использовать конструкцию вида:
Пока Выборка.Следующий() Цикл
Сообщить("Производится выгрузка склада "+V8.String(Выборка.Ссылка));
КонецЦикла;
И хотя данный код меннее критичен (считываются только основные данные объекта), лучше использовать функцию ПРЕДСТАВЛЕНИЕ()/ПРЕДСТАВЛЕНИЕССЫЛКИ() в запросе, или на крайняк Наименование вытащите. Есть и еще одна причина, о которой расскажу чуть позже.
Хочу отметить, что указанные особенности справедливы не только для Внешнего Соединения, но и для локальных баз. Не пренебрегайте ими. Дабы не быть голословным, приведу тесты на скорость/выносливость/быстроту в таблице1.
Время выборки 10000 элементов в секундах | Тип соединения | Выборка | Запрос | ComConnector | 0,583412 | 0,192428 | Application | 1,178783 | 0,782243 |
Запись. Ну что тут сказать.. Тут нечего сказать. Запросы не используются для записи. А то ежели дала бы 1С одинесникам INSERT, DELETE, UPDATE, CREATE TABLE, то про "Доступно и Всерьез" можно было бы только мечтать, глядя, как одинесники UPDATE-тят виртуальные таблицы.... Единственное, что могу посоветовать - это использование транзакций. Не могу теоритически обосновать, почему так, это просто эмпирика. Конструкция вида:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
СправочникМенеджер=Бухгалтерия.Справочники.Склады;
Бухгалтерия.НачатьТранзакцию();
Для i=1 по 1000 Цикл
НовыйОбъект=СправочникМенеджер.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=НовыйВидСклада;
НовыйОбъект.Записать();
КонецЦикла;
Бухгалтерия.ЗафиксироватьТранзакцию();
будет производительней, чем
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
СправочникМенеджер=Бухгалтерия.Справочники.Склады;
Для i=1 по 1000 Цикл
НовыйОбъект=СправочникМенеджер.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=НовыйВидСклада;
НовыйОбъект.Записать();
КонецЦикла;
Смотрим таблицу, короче
Время записи 1000 элементов в секундах | Тип соединения | Простая | Транзакция | ComConnector | 2,594816 | 1,558324 | Application | 8,482852 | 7,455433 |
Глава два. Для сильных духом мужчин.
Речь пойдет про методику оптимизации работы с объектами удаленной базы. Отмечу, что работа с методами/свойствами Com объектов в 1С осуществляется через позднее связывание и требует значительных затрат. Поэтому, чем меньше вы делаете вызовов методов/свойств Com объекта - тем более быстро все работает. Например, конструкция вида:
Для i=1 по 1000 Цикл
НовыйОбъект=Бухгалтерия.Справочники.Склады.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
НовыйОбъект.Записать();
КонецЦикла;
гораздо менее оптимальна, чем конструкция вида:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
СправочникМенеджер=Бухгалтерия.Справочники.Склады;
Для i=1 по 1000 Цикл
НовыйОбъект=СправочникМенеджер.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=НовыйВидСклада;
НовыйОбъект.Записать();
КонецЦикла;
ибо во втором случае используется гораздо меньше вызовов свойств Com-объектов. Ну и наконец решение для фанатов - основной код должен быть внутри конфигурации внешней базы, скажем в процедурах общего модуля, и вызываться из локальной базы в передачей параметров, например, как:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
Для i=1 по 1000 Цикл
Бухгалтерия.СоздатьСклад(Строка(Новый УникальныйИдентификатор()),НовыйВидСклада);
КонецЦикла;
где
Функция СоздатьСклад(Наименование,ВидСклада) Экспорт
НовыйОбъект=Справочники.Склады.СоздатьЭлемент();
НовыйОбъект.Наименование=Наименование;
НовыйОбъект.ВидСклада=ВидСклада;
НовыйОбъект.Записать();
КонецФункции
это функция общего модуля внешней базы...
Однако этот прием затруднительно использовать и отлаживать, кроме того он почти не имеет смысла для COMConnector-а.
Сводный отчет о производительности представлен в таблице 3.
Время записи 1000 элементов в секундах | Тип соединения | Простая | Предопредел.СправочникМенеджер | Внутренняя | ComConnector | 5,12778 | 2,49483 | 2,29044 | Application | 57,52807 | 8,337808 | 2,395056 |
Часть два. Шозанах???
Глава один. Все то, о чем вы так долго мечтали, но не решались спросить.
Конечно перечисление. Как же его получить? Просто, как две кнопочки в Дельфишнике на форму набросать... Конструкцией вида:
СоответствиеВидаСклада=Новый Соответствие;
СоответствиеВидаСклада.Вставить(0,Перечисления.ВидыСкладов.Оптовый);
СоответствиеВидаСклада.Вставить(1,Перечисления.ВидыСкладов.Розничный);
СоответствиеВидаСклада.Вставить(2,Перечисления.ВидыСкладов.НеавтоматизированнаяТорговаяТочка);
Запрос=Бухгалтерия.NewObject("Запрос");
Запрос.Текст=
"ВЫБРАТЬ
| Склады.Ссылка,
| Склады.Наименование,
| Склады.ВидСклада.Порядок как ПорядокВидаСклада
|ИЗ
| Справочник.Склады КАК Склады";
Выборка=Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НовыйОбъект=Справочники.Склады.СоздатьЭлемент();
НовыйОбъект.Наименование=Выборка.Наименование;
НовыйОбъект.ВидСклада=СоответствиеВидаСклада.Получить(Выборка.ПорядокВидаСклада);
НовыйОбъект.Записать();
КонецЦикла;
получаем следующее:
Мы выбираем из удаленной базы НЕ ССЫЛКУ на элемент перечисления, которая возвратится как COM-объект, а его ПОРЯДКОВЫЙ НОМЕР в конфигурации, который также позволяет однозначно идентифицировать значение перечисления. Ну а конструкция
СоответствиеВидаСклада=Новый Соответствие;
СоответствиеВидаСклада.Вставить(0,Перечисления.ВидыСкладов.Оптовый);
СоответствиеВидаСклада.Вставить(1,Перечисления.ВидыСкладов.Розничный);
СоответствиеВидаСклада.Вставить(2,Перечисления.ВидыСкладов.НеавтоматизированнаяТорговаяТочка);
устанавливает соответстивие между порядковым номером перечисления в удаленной базе и его значением в локальной базе. Сделана эта конструкция для удобства кода... :-)
Ах да, еще же есть Метаданные() у COM-объекта Перечисления. Так вот, извращенцы идут лесом, ибо смотрим главу "Для сильных духом мужчин". Даже писать про Метаданные() не буду...
Глава два. Если что-то хочется, но нельзя, то немного можно.. Но не со мной.
Вот есть у вас задача сделать обмен через COM. И будут там обмениваться ну скажем... Поступления. Что я делаю в первую очередь? А в первую очередь я в модуле обработки прописываю функции
ПолучитьНоменклатуру()
ПолучитьСклад()
ПолучитьКонтрагента()
вот так.
Разберем теперь код следующего вида:
Перем СоответствиеВидовСкладов Экспорт;
Перем ЗапросВнешнейБазы Экспорт;
Перем ВнешняяБаза Экспорт;
Процедура Подключиться() Экспорт
ВнешняяБаза=Новый COMОбъект("V81.COMConnector");
Попытка
ВнешняяБаза=ВнешняяБаза.Connect(База.СтрокаПодключения);
Исключение
Предупреждение("Ошибка открытия базы!!!");
Сообщить(ОписаниеОшибки());
ВнешняяБаза=Неопределено;
Возврат;
КонецПопытки;
ЗапросВнешнейБазы=ВнешняяБаза.NewObject("Запрос");
СоответствиеВидовСкладов=Новый Соответствие;
СоответствиеВидовСкладов.Вставить(0,Перечисления.ВидыСкладов.Оптовый);
СоответствиеВидовСкладов.Вставить(1,Перечисления.ВидыСкладов.Розничный);
СоответствиеВидовСкладов.Вставить(2,Перечисления.ВидыСкладов.НеавтоматизированнаяТорговаяТочка);
КонецПроцедуры
Функция ПолучитьСклад(КодВнешнейБазы,ПолучатьГруппу=Ложь)Экспорт
Если (КодВнешнейБазы=Неопределено)ИЛИ(КодВнешнейБазы=NULL)ИЛИ(КодВнешнейБазы="") Тогда
Возврат Справочники.Склады.ПустаяСсылка();
КонецЕсли;
Склад=Справочники.Склады.НайтиПоРеквизиту("КодВнешнейБазы",КодВнешнейБазы);
Если Не Склад.Пустая() Тогда
Возврат Склад; //Он создан уже раньше, вернем ссылку на него
Иначе
Если ПолучатьГруппу Тогда
ЗапросВнешнейБазы.Текст=
"ВЫБРАТЬ
| Склады.Наименование,
| Склады.Родитель.Код
|ИЗ
| Справочник.Склады КАК Склады
|ГДЕ
| Склады.Код = &Код";
ЗапросВнешнейБазы.УстановитьПараметр("Код",КодВнешнейБазы);
Выборка=ЗапросВнешнейБазы.Выполнить().Выбрать();
Выборка.Следующий();
Склад=Справочники.Склады.СоздатьГруппу();
Склад.Наименование=Выборка.Наименование;
Склад.КодВнешнейБазы=КодВнешнейБазы;
Склад.Родитель=ПолучитьСклад(Выборка.РодительКод,Истина);
Склад.Записать();
Иначе
ЗапросВнешнейБазы.Текст=
"ВЫБРАТЬ
| Склады.Наименование,
| Склады.ВидСклада.Порядок,
| Склады.Родитель.Код
|ИЗ
| Справочник.Склады КАК Склады
|ГДЕ
| Склады.Код = &Код";
ЗапросВнешнейБазы.УстановитьПараметр("Код",КодВнешнейБазы);
Выборка=ЗапросВнешнейБазы.Выполнить().Выбрать();
Выборка.Следующий();
Склад=Справочники.Склады.СоздатьЭлемент();
Склад.Наименование=Выборка.Наименование;
Склад.ВидСклада=СоответствиеВидовСкладов.Получить(Выборка.ВидСкладаПорядок);
Склад.КодВнешнейБазы=КодВнешнейБазы;
Склад.Комментарий="Создан "+ТекущаяДата()+" во время выгрузки";
Склад.Родитель=ПолучитьСклад(Выборка.РодительКод,Истина);
Склад.Записать();
КонецЕсли;
Возврат Склад.Ссылка;
КонецЕсли;
КонецФункции
Первым делом мы подключаемся к удаленной базе и сразу создаем объект запроса в удаленной базе (“Для сильных духом”). Затем создаем соответствие для перечисления по порядковому номеру. Все это храним в экспортных переменных модуля объекта.
Допускаем, что синхронизация осуществляется по коду объекта удаленной базы, который хранится в реквизите объекта локальной базы (не забывайте про длину реквизита, иначе будут дубли!).
Ну а функция ПолучитьСклад() – примитивна, разбирайтесь товарищи сами.
Плюсы:
1) Быстродействие
2) Формирование полной иерархии
3) Возможность быстро получить элемент, например, при перегрузке документа.
Дальше будет статья про Сомов, позднее вязание, и как это все варится.. Никакой практики, так, теория и мои мысли.. Ждем-с.
P.S.
Тут еще графики были, но их не разместишь.
P.S.S. Тесты делал правильно, базу очищал, перезапускал.. База файловая.
P.S.S.S. Кто может научить форматировать таблицы на этом тарабарском HTML - напишите..
|