При интеграции баз данных через OLE/COM или при обычной выгрузке реквизитов контрагентов разработчики часто сталкиваются с ситуацией, когда данные адресов и телефонов не считываются из регистра сведений КонтактнаяИнформация, хотя визуально в карточке контрагента они заполнены. Разберем типичные ошибки и рассмотрим правильные варианты получения контактных данных.
Проанализируем исходный фрагмент кода, в котором возникает ошибка ненахождения записей:
НаборЗаписей = РегистрыСведений.КонтактнаяИнформация.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Кл);
НаборЗаписей.Отбор.Тип.Установить(Перечисления.ТипыКонтактнойИнформации.Адрес);
НаборЗаписей.Отбор.Вид.Установить(Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента);
НаборЗаписей.Прочитать();
Для каждого Запись Из НаборЗаписей Цикл
олеКл.ЮридическийАдрес = Запись.Представление;
КонецЦикла;
Выясним причину такого поведения платформы. В приведенном примере переменная Кл формируется следующим образом:
Выборка = Справочники.Контрагенты.Выбрать();
Пока Выборка.Следующий() Цикл
Кл = Выборка.ПолучитьОбъект();
Здесь кроются ключевые архитектурные ошибки:
ПолучитьОбъект() возвращает значение типа СправочникОбъект.Контрагенты. Измерение Объект регистра сведений КонтактнаяИнформация имеет ссылочный тип (СправочникСсылка.Контрагенты или составной тип ссылок). При передаче в отбор типа «Объект» платформа выполняет строгое сравнение типов и не находит совпадений в таблице базы данных. В отбор необходимо передавать исключительно ссылку: Кл.Ссылка или сразу Выборка.Ссылка.Выборка.ПолучитьОбъект() считывает из базы данных все реквизиты, табличные части и блокирует память. Если объект не планируется модифицировать и записывать обратно в базу, использование ПолучитьОбъект() категорически не рекомендуется.СоздатьНаборЗаписей() ориентирован на пакетную запись изменений. Для точечного чтения единичной записи он слишком тяжеловесен.Рассмотрим исправление исходного кода с помощью объекта МенеджерЗаписи. Этот объект предназначен для адресации конкретной записи регистра по значениям всех его измерений. Обратите внимание на корректное написание имени метода — СоздатьМенеджерЗаписи (с буквой «д»).
Запись = РегистрыСведений.КонтактнаяИнформация.СоздатьМенеджерЗаписи();
Запись.Объект = Выборка.Ссылка; // Передаем ссылку, а не объект!
Запись.Тип = Перечисления.ТипыКонтактнойИнформации.Адрес;
Запись.Вид = Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента;
Запись.Прочитать();
Если Запись.Выбран() Тогда
олеКл.ЮридическийАдрес = Запись.Представление;
КонецЕсли;
Метод Выбран() позволяет проверить, существует ли в информационной базе запись с указанными ключевыми полями.
Разберем еще более компактный и быстрый вариант для непериодического регистра сведений — вызов метода Получить(). Этот способ не создает промежуточных объектов в памяти:
Отбор = Новый Структура;
Отбор.Вставить("Объект", Выборка.Ссылка);
Отбор.Вставить("Тип", Перечисления.ТипыКонтактнойИнформации.Адрес);
Отбор.Вставить("Вид", Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента);
ДанныеКИ = РегистрыСведений.КонтактнаяИнформация.Получить(Отбор);
ЮрАдрес = ДанныеКИ.Представление;
Метод Получить() возвращает структуру со значениями ресурсов. Если запись с таким набором измерений отсутствует, поля структуры будут содержать значения по умолчанию (для строковых полей — пустую строку).
Проанализируем производительность при COM/OLE обмене. Выполнение чтения данных для каждого контрагента в цикле порождает проблему N+1 обращений к базе. При работе через COM-соединение каждый вызов через межпроцессный интерфейс приводит к колоссальным потерям времени.
Наиболее правильным подходом является выборка всех необходимых данных контрагентов вместе с их адресами и телефонами единым запросом через левое соединение:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Контрагент,
| Контрагенты.Наименование КАК Наименование,
| ЕСТЬNULL(КИ_ЮрАдрес.Представление, """") КАК ЮридическийАдрес,
| ЕСТЬNULL(КИ_Телефон.Представление, """") КАК Телефон
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КИ_ЮрАдрес
| ПО КИ_ЮрАдрес.Объект = Контрагенты.Ссылка
| И КИ_ЮрАдрес.Вид = &ВидЮрАдрес
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КИ_Телефон
| ПО КИ_Телефон.Объект = Контрагенты.Ссылка
| И КИ_Телефон.Вид = &ВидТелефон
|ГДЕ
| НЕ Контрагенты.ЭтоГруппа";
Запрос.УстановитьПараметр("ВидЮрАдрес", Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента);
Запрос.УстановитьПараметр("ВидТелефон", Справочники.ВидыКонтактнойИнформации.ТелефонКонтрагента);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Передаем данные в приемник без лишних обращений к СУБД
олеКл = олеБаза.Справочники.Контрагенты.СоздатьЭлемент();
олеКл.Наименование = Выборка.Наименование;
олеКл.ЮридическийАдрес = Выборка.ЮридическийАдрес;
// ... дальнейшая обработка
КонецЦикла;
При работе с контактной информацией обязательно учитывайте версию конфигурации:
Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента действительно существует в метаданных как предопределенный элемент. Если справочник заполнялся пользователями вручную, получение по имени предопределенного вернет Неопределено или вызовет исключение. В таком случае поиск вида следует выполнять по наименованию или уникальному идентификатору.Контрагенты.КонтактнаяИнформация). Для работы с ней используются типовые функции общего модуля УправлениеКонтактнойИнформацией.