Почему справочники из расширения не определяются через ТипВсеСсылки() и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с расширениями конфигурации в системе 1С:Предприятие разработчики часто сталкиваются с неочевидным поведением платформы, особенно при попытке использовать обобщенные типы ссылок или методы, предназначенные для работы со всеми ссылочными типами, такие как ТипВсеСсылки(). В частности, речь идет о ситуации, когда справочники, созданные в расширении, по каким-то причинам не определяются через метод Справочники.ТипВсеСсылки().СодержитТип() или не могут быть присвоены реквизиту с типом ЛюбаяСсылка. Разберем эту проблему подробнее, выясним ее причины и рассмотрим способы решения в зависимости от версии платформы.

Суть проблемы: Ссылки расширений не включаются в обобщенные типы

Основная сложность, с которой сталкиваются разработчики, заключается в том, что до версии платформы 1С:Предприятие 8.3.20 типы метаданных, определенные в расширениях конфигурации, не включались в состав "обобщенных" типов данных основной конфигурации. Это означает, что для платформы 1С:Предприятие до версии 8.3.20:

  1. Метод Справочники.ТипВсеСсылки().Типы() (а также аналогичные методы для других объектов, таких как документы, перечисления и т.д.) возвращал только те типы, которые были определены непосредственно в основной конфигурации, полностью игнорируя типы из расширений.
  2. При попытке присвоить ссылке на объект расширения переменной или реквизиту с типом ЛюбаяСсылка (или СправочникСсылка, ДокументСсылка и т.п.) значение такой переменной или реквизита могло становиться Неопределено или приводить к нежелательному поведению.
  3. Программный код, который полагался на эти обобщенные типы для универсальной обработки ссылок, не мог корректно работать с объектами, созданными в расширениях.

Например, разработчик ожидает, что следующий код вернет Истина, но на платформах до 8.3.20 он выдает Ложь, если СсылкаСправочникРасширения является ссылкой на справочник из расширения:


// Предположим, СсылкаСправочникРасширения - это ссылка на справочник, созданный в расширении
Сообщить(Справочники.ТипВсеСсылки.СодержитТип(ТипЗнч(СсылкаСправочникРасширения))); // >>> Ложь

При этом проверка принадлежности метаданных самой ссылки к коллекции справочников (или других объектов) через метаданные основной конфигурации покажет Истина, что только усугубляет путаницу:


Сообщить(Метаданные.Справочники.Содержит(СсылкаСправочникРасширения.Метаданные)); // >>> Истина

Такое поведение подтверждает, что платформа "знает" о существовании объекта расширения как метаданного, но не включает его в обобщенные типы ссылок на программном уровне до версии 8.3.20. Этот аспект также означал, что стандартные команды БСП (Библиотека стандартных подсистем), которые используют обобщенные типы, могли не находить объекты расширений, что усложняло их интеграцию.

Официальное решение: Обновление платформы до версии 8.3.20 и выше

Фирма "1С" признала это ограничение и внесла существенные изменения в работу платформы, начиная с версии 8.3.20. Это нововведение значительно упростило взаимодействие с объектами расширений и устранило необходимость в сложных обходных путях для большинства сценариев.

  1. Расширение обобщенных типов: В версии 8.3.20 и выше обобщенные типы данных, такие как ЛюбаяСсылка, СправочникСсылка, ДокументСсылка и другие, теперь включают в себя ссылки на объекты, созданные в расширении. Это означает, что код, использующий эти типы, будет корректно обрабатывать ссылки на объекты расширений без дополнительной настройки.
  2. Корректная работа методов ТипВсеСсылки(): Методы, возвращающие все типы метаданных одного вида (например, Справочники.ТипВсеСсылки.Типы(), Документы.ТипВсеСсылки.Типы() и т.д.), теперь будут возвращать типы как из основной конфигурации, так и из всех активных расширений.
  3. Упрощение разработки: Эти изменения значительно повышают гибкость работы с метаданными и упрощают встраивание расширений в стандартные библиотеки и универсальные механизмы конфигурации, где ранее требовались специфические обходы. Это позволяет разработчикам использовать стандартные команды БСП и другие универсальные подсистемы для работы с объектами расширений без дополнительного кодирования.

Для того чтобы воспользоваться этими возможностями, необходимо выполнить следующие шаги:

  1. Обновить платформу 1С:Предприятие до версии 8.3.20 или более новой.
  2. Установить режим совместимости как для основной конфигурации, так и для всех используемых расширений, до версии платформы 8.3.20 или выше (например, 8.3.20, 8.3.21 и т.д.). Важно убедиться, что режим совместимости соответствует или превышает версию, в которой было введено данное изменение.

После этих действий стандартный код, проверяющий тип ссылки, будет работать корректно:


// В платформе 8.3.20 и выше (при соответствующем режиме совместимости)
// этот код теперь вернет Истина
Сообщить(Справочники.ТипВсеСсылки.СодержитТип(ТипЗнч(СсылкаСправочникРасширения))); // >>> Истина

Это является рекомендуемым и наиболее правильным решением для обеспечения полной совместимости и использования всех возможностей платформы. Повсеместное использование этого подхода позволит значительно снизить сложность кода и повысить его читаемость и поддерживаемость.

Обходные решения для старых версий платформы (до 8.3.20)

Если обновление платформы до 8.3.20 и выше по каким-либо причинам невозможно или нежелательно, нам потребуются специальные обходные пути для идентификации и работы с типами из расширений. Эти решения основаны на ручном получении метаданных всех активных расширений и последующей проверке принадлежности типов к этим метаданным. Рассмотрим два основных способа.

Способ 1: Получение метаданных конкретного расширения и проверка принадлежности

Этот подход предполагает, что мы знаем имя расширения, с которым работаем, и хотим проверить, принадлежит ли определенная ссылка объекту этого расширения. Мы можем получить метаданные конкретного расширения и затем использовать их для проверки.

Шаг 1: Получаем метаданные целевого расширения.

Для начала нам понадобится функция, которая будет получать объект метаданных конкретного расширения по его имени. Рассмотрим пример такой функции, которую можно разместить, например, в общем модуле:


Функция ПолучитьМетаданныеТекущегоРасширения(ИмяРасширения) Экспорт
    // Получаем список всех активных расширений конфигурации, установленных в системе.
    МассивРасширений = РасширенияКонфигурации.Получить();
    
    // Перебираем каждое найденное расширение в списке.
    Для Каждого Расширение Из МассивРасширений Цикл
        // Для каждого расширения получаем его данные, которые можно преобразовать в объект метаданных.
        рМетаданные = Новый ОбъектМетаданныхКонфигурация(Расширение.ПолучитьДанные());
        
        // Сравниваем имя текущего расширения (полученное из его метаданных) с искомым именем.
        Если рМетаданные.Имя = ИмяРасширения Тогда
            // Если расширение с указанным именем найдено, возвращаем его метаданные.
            Возврат рМетаданные;
        КонецЕсли;
    КонецЦикла;
    
    // Если после перебора всех расширений расширение с указанным именем не было найдено,
    // возвращаем Неопределено, чтобы сигнализировать об отсутствии.
    Возврат Неопределено;
КонецФункции // ПолучитьМетаданныеТекущегоРасширения

Эта функция позволяет нам получить объект ОбъектМетаданныхКонфигурация для конкретного расширения, указанного по имени. Например, если наше расширение называется "ОбменПоПравиламКД2", мы будем передавать это имя в функцию. Эта функция является ключевым элементом для последующих проверок, так как она предоставляет доступ к структуре метаданных конкретного расширения.

Шаг 2: Проверяем, является ли тип справочником из расширения.

Имея метаданные расширения, мы можем написать функцию, которая проверяет, относится ли переданный тип (или тип значения ссылки) к справочнику, определенному в этом расширении. Для этого нам нужно найти метаданные типа внутри коллекции метаданных расширения, а затем убедиться, что они содержатся в коллекции Справочники этого расширения.


// Предполагается, что где-то доступна функция ПолучитьМетаданныеТекущегоРасширения,
// возможно, в общем модуле, чтобы ее можно было вызывать по имени модуля.
// Например, если функция ПолучитьМетаданныеТекущегоРасширения находится в модуле "РасшКД2_ПовтИсп",
// то вызов будет РасшКД2_ПовтИсп.ПолучитьМетаданныеТекущегоРасширения.
Функция ЭтоСправочникРасширения(ПроверяемыйТип, ИмяРасширения) Экспорт
    // Получаем метаданные целевого расширения с помощью ранее определенной функции.
    МетаданныеРасширения = ПолучитьМетаданныеТекущегоРасширения(ИмяРасширения); // Замените на фактический вызов, если функция в другом модуле
    
    // Если расширение не найдено, или его метаданные не получены (вернулось Неопределено),
    // то переданный тип точно не является справочником из этого расширения.
    Если МетаданныеРасширения = Неопределено Тогда
        Возврат Ложь;
    КонецЕсли;
    
    // Находим метаданные переданного типа в контексте метаданных расширения.
    // Метод НайтиПоТипу возвращает объект метаданных, соответствующий типу, или Неопределено,
    // если такой тип в расширении не определен.
    МетаданныеТипа = МетаданныеРасширения.НайтиПоТипу(ПроверяемыйТип);
    
    // Проверяем два условия:
    // 1. Что метаданные типа были успешно найдены в расширении (не Неопределено).
    // 2. Что найденные метаданные типа являются справочником в коллекции справочников этого расширения.
    Возврат МетаданныеТипа <> Неопределено И МетаданныеРасширения.Справочники.Содержит(МетаданныеТипа);
КонецФункции // ЭтоСправочникРасширения

Использование этой функции позволяет нам точно определить, принадлежит ли переданный тип справочнику из конкретного расширения. Например, если у нас есть СсылкаСправочникРасширения и мы хотим проверить, является ли она справочником из расширения "МоеРасширение", мы можем вызвать:


// Пример использования функции ЭтоСправочникРасширения
Если ЭтоСправочникРасширения(ТипЗнч(СсылкаСправочникРасширения), "МоеРасширение") Тогда
    // Это справочник из расширения "МоеРасширение", можно выполнять специфическую логику.
    Сообщить("Ссылка относится к справочнику из расширения 'МоеРасширение'");
Иначе
    Сообщить("Ссылка НЕ относится к справочнику из расширения 'МоеРасширение'");
КонецЕсли;

Этот подход полезен, когда мы хотим обрабатывать объекты из конкретного, заранее известного расширения, например, для работы с планами обмена или другими подсистемами, специфичными для данного расширения.

Способ 2: Универсальная проверка ссылочных типов из любого расширения

В некоторых случаях нам может потребоваться более универсальное решение, которое проверяет, является ли значение ссылочным типом (справочник, документ, перечисление и т.д.), определенным в любом активном расширении. Это часто необходимо для общих механизмов обмена данными, универсальных обработок (например, универсального обмена данными XML из КД 2.1), или других подсистем, которые должны учитывать все возможные ссылочные объекты, включая те, что добавлены через расширения.

Шаг 1: Инициализируем кэш метаданных всех расширений.

Для оптимизации производительности, вместо многократного получения метаданных всех расширений при каждом вызове, мы можем кэшировать их в переменной при первом обращении. Это поможет избежать повторных ресурсоемких операций и улучшит скорость работы. Для этого мы можем использовать глобальную переменную модуля или статическое свойство.


Перем МетаданныеРасширений; // Объявляем переменную для кэширования метаданных расширений

// ... в теле функции или в блоке инициализации
Если МетаданныеРасширений = Неопределено Тогда
    // Если кэш пуст, заполняем его.
    МетаданныеРасширений = Новый Массив; // Создаем новый массив для хранения метаданных.
    Для Каждого Расширение Из РасширенияКонфигурации.Получить() Цикл
        // Для каждого активного расширения получаем его данные и добавляем в массив.
        МетаданныеРасширений.Добавить(Новый ОбъектМетаданныхКонфигурация(Расширение.ПолучитьДанные()));
    КонецЦикла;
КонецЕсли;

Этот блок кода гарантирует, что список метаданных всех расширений будет получен только один раз (при первом вызове функции) и сохранен для дальнейшего использования, что существенно повышает производительность при многократных проверках.

Шаг 2: Реализуем функцию для проверки ссылочного типа расширения.

На основе кэшированных метаданных мы можем создать универсальную функцию ЭтоЗначениеСсылочногоТипаРасширения(). Эта функция будет принимать любое значение и определять, является ли оно ссылкой на объект, определенный в каком-либо из активных расширений. Эта функция охватывает различные ссылочные типы, такие как справочники, документы, перечисления и другие.


Функция ЭтоЗначениеСсылочногоТипаРасширения(Значение) Экспорт
    Тип = ТипЗнч(Значение);
    
    // Если значение Неопределено, оно не может быть ссылочным типом расширения.
    Если Тип = Тип("Неопределено") Тогда
        Возврат Ложь;
    КонецЕсли;
    
    // Инициализируем кэш метаданных расширений при первом вызове функции.
    Если МетаданныеРасширений = Неопределено Тогда
        МетаданныеРасширений = Новый Массив;
        Для Каждого Расширение Из РасширенияКонфигурации.Получить() Цикл
            МетаданныеРасширений.Добавить(Новый ОбъектМетаданныхКонфигурация(Расширение.ПолучитьДанные()));
        КонецЦикла;
    КонецЕсли;
    
    // Перебираем метаданные каждого расширения, чтобы найти, в каком из них определен наш тип.
    Для Каждого МетаданныеРасширения Из МетаданныеРасширений Цикл
        // Пытаемся найти метаданные переданного типа в текущем расширении.
        // Если тип не определен в данном расширении, МетаданныеТипа будет Неопределено.
        МетаданныеТипа = МетаданныеРасширения.НайтиПоТипу(Тип);
        
        // Если метаданные типа найдены в этом расширении, то проверяем,
        // относится ли он к одному из ссылочных типов, которые могут быть в расширении.
        Если МетаданныеТипа <> Неопределено И (
            МетаданныеРасширения.Справочники.Содержит(МетаданныеТипа) // Проверка на справочник
            ИЛИ МетаданныеРасширения.Документы.Содержит(МетаданныеТипа) // Проверка на документ
            ИЛИ МетаданныеРасширения.Перечисления.Содержит(МетаданныеТипа) // Проверка на перечисление
            ИЛИ МетаданныеРасширения.ПланыВидовХарактеристик.Содержит(МетаданныеТипа) // Проверка на ПВХ
            ИЛИ МетаданныеРасширения.ПланыСчетов.Содержит(МетаданныеТипа) // Проверка на план счетов
            ИЛИ МетаданныеРасширения.ПланыВидовРасчета.Содержит(МетаданныеТипа) // Проверка на ПВР
            ИЛИ МетаданныеРасширения.БизнесПроцессы.Содержит(МетаданныеТипа) // Проверка на бизнес-процесс
            // Отдельная проверка для точек маршрута бизнес-процессов, так как у них свой ТипВсеСсылки
            ИЛИ МетаданныеРасширения.БизнесПроцессы.ТипВсеСсылкиТочекМаршрутаБизнесПроцессов.СодержитТип(Тип) 
            ИЛИ МетаданныеРасширения.Задачи.Содержит(МетаданныеТипа) // Проверка на задачу
            ИЛИ МетаданныеРасширения.ПланыОбмена.Содержит(МетаданныеТипа) // Проверка на план обмена
        ) Тогда
            // Если тип относится к ссылочному типу расширения (хотя бы одному из перечисленных),
            // немедленно возвращаем Истина, так как мы нашли подтверждение.
            Возврат Истина;
        КонецЕсли;
    КонецЦикла;
    
    // Если после перебора всех расширений и всех проверок тип не был определен как ссылочный тип расширения,
    // возвращаем Ложь.
    Возврат Ложь;
КонецФункции

Эта функция представляет собой мощный инструмент для работы с объектами расширений в старых версиях платформы. Она охватывает широкий спектр ссылочных типов и проверяет их принадлежность к любому из активных расширений. Такое решение может быть интегрировано в универсальные обработки, механизмы синхронизации данных (например, для корректного определения УникальныйИдентификатор при выгрузке из КД 2.1), или другие подсистемы, требующие знания о типе объекта, чтобы избежать ситуаций, когда УникальныйИдентификатор для объектов расширения оказывается пустым.

Важные аспекты при работе с расширениями

  1. Режим совместимости: Всегда внимательно отслеживайте и управляйте режимом совместимости как для основной конфигурации, так и для всех используемых расширений. Несоответствие режимов совместимости может привести к некорректной работе расширений или их автоматическому отключению. Для использования последних возможностей платформы (например, корректной работы ТипВсеСсылки() с расширениями), необходимо установить соответствующий режим совместимости (8.3.20 или выше).
  2. Целевое назначение расширений: Помните, что расширения конфигурации предназначены для доработки прикладных решений без изменения основной конфигурации и снятия ее с поддержки. Стремитесь использовать их для добавления новой функциональности или изменения существующей без прямого редактирования объектов основной конфигурации, что является золотым правилом расширяемости.
  3. Производительность обходных путей: Методы ручного перебора метаданных расширений (как в обходных решениях для старых версий) могут быть достаточно ресурсоемкими, особенно если их вызывать часто в циклах или при обработке большого объема данных. Кэширование списка метаданных расширений (как показано в "Способе 2") является критически важной хорошей практикой для минимизации накладных расходов и поддержания приемлемой производительности системы.
  4. Версионирование расширений: При разработке расширений и использовании обходных путей, важно также учитывать версионирование платформы. Код, написанный для одной версии платформы (например, до 8.3.20), может перестать быть актуальным или оптимальным после обновления до более новой версии, где проблема уже решена.

В заключение, хотя платформы 1С:Предприятие до версии 8.3.20 имели ограничения по работе с обобщенными типами ссылок из расширений, современные версии платформы (8.3.20 и выше) устранили эту проблему, предоставив более удобный и интегрированный механизм. Для тех, кто вынужден работать на старых версиях, существуют проверенные обходные пути, позволяющие реализовать необходимую логику, хотя и с большими усилиями и использованием более сложного кода. Выбор решения зависит от конкретных требований проекта, версии платформы и стратегии ее обновления.

← На главную