Почему метод ПолучитьОбъект() выдает ошибку на тонком клиенте и как это исправить?

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

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

Разбор ситуации: почему контекст имеет значение

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

Однако, когда пользователь меняет значение в поле на форме, срабатывает клиентское событие (например, ПриИзменении). Это событие выполняется в контексте Тонкого клиента. Тонкий клиент можно сравнить с интернет-браузером: его задача — отображать интерфейс и взаимодействовать с пользователем, но у него нет прямого доступа к базе данных. На клиенте «живут» только ссылки, но не сами объекты (СправочникОбъект, ДокументОбъект). Попытка вызвать метод ПолучитьОбъект() на клиенте всегда приведет к ошибке, так как этого метода в клиентском приложении просто не существует.

Особенности компиляции общих модулей

Рассмотрим важный аспект работы общих модулей, у которых в свойствах одновременно установлены флаги Клиент (управляемое приложение) и Сервер. Проанализируем, что происходит «под капотом» платформы:

  1. Система компилирует два (или три, если включено «Внешнее соединение») независимых программных блока с одинаковым именем.
  2. Если вызов идет из клиентской процедуры формы, управление передается в «клиентскую копию» общего модуля.
  3. Если вызов идет из серверной процедуры, используется «серверная копия».

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

Решение проблемы: использование флага «Вызов сервера»

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

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

Посмотрим на пример правильной организации кода в серверном модуле:


// В общем модуле "МойСерверныйМодуль" (флаги: Сервер, Вызов сервера)
Функция ЭтоПомеченныйНаУдалениеОбъект(СсылкаНаОбъект) Экспорт
    // На сервере метод ПолучитьОбъект() доступен
    ОбъектБД = СсылкаНаОбъект.ПолучитьОбъект();
    Возврат ОбъектБД.ПометкаУдаления;
КонецФункции

Теперь эту функцию можно вызвать из клиентского события формы документа:


&НаКлиенте
Процедура ВидВремениПриИзменении(Элемент)
    // Вызов сервера через общий модуль
    Если МойСерверныйМодуль.ЭтоПомеченныйНаУдалениеОбъект(Объект.ВидВремени) Тогда
        Сообщить("Внимание: выбран удаленный элемент!");
    КонецЕсли;
КонецПроцедуры

Инструкции препроцессора в общих модулях

Разберем еще один способ управления кодом в универсальных модулях (где стоят галочки и Клиент, и Сервер). Вместо директив &НаСервере, которые используются только в формах и командах, в общих модулях применяются инструкции препроцессора. Это позволяет изолировать платформенно-зависимый код:


Процедура УниверсальнаяПроцедура() Экспорт
    #Если Сервер Тогда
        // Этот код скомпилируется только для сервера
        // Здесь можно ПолучитьОбъект()
    #КонецЕсли
    
    #Если ТонкийКлиент Тогда
        // Этот код только для клиента
        // Здесь можно только Сообщить() или работать с формой
    #КонецЕсли
КонецПроцедуры

Однако помните: использование #Если Сервер внутри модуля, вызванного с клиента, не превратит клиентский вызов в серверный. Код внутри просто будет проигнорирован. Для перехода с клиента на сервер нужен либо модуль с Вызовом сервера, либо вызов серверного метода самой формы.

Оптимизация: почему ПолучитьОбъект() — это «тяжелая» операция

Проанализируем производительность. Метод ПолучитьОбъект() — это крайне ресурсоемкая операция. Когда вы его вызываете, 1С считывает из базы данных абсолютно все данные объекта: все реквизиты, все табличные части, все дополнительные сведения. Если вам нужно просто проверить одно свойство (например, является ли вид времени «Явкой»), использовать ПолучитьОбъект() неэффективно.

Рассмотрим альтернативы:

  1. Обращение через точку от ссылки на сервере: Выражение Ссылка.Наименование на сервере работает быстрее, так как 1С применяет механизм кэширования объектов.
  2. Запрос: Это самый быстрый способ получения данных. Если нужно получить значения нескольких реквизитов, всегда лучше написать небольшой запрос.

Пример оптимизированного кода на сервере:


Функция ПолучитьДанныеВидаВремени(Ссылка) Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ВидыИспользованияРабочегоВремени.УдалитьВидВремени КАК Вид
        |ИЗ
        |   Справочник.ВидыИспользованияРабочегоВремени КАК ВидыИспользованияРабочегоВремени
        |ГДЕ
        |   ВидыИспользованияРабочегоВремени.Ссылка = &Ссылка";
    
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Вид;
    КонецЕсли;
    Возврат Неопределено;
КонецФункции

Передача данных между контекстами (Маршаллинг)

Выясним, что происходит при вызове сервера. Данные передаются по сети в сериализованном виде. Это накладывает ограничения на типы данных:

Если вам нужно изменить данные документа на форме на основании данных из базы, алгоритм должен быть таким: 1. Вызываем серверный метод формы. 2. На сервере используем РеквизитФормыВЗначение("Объект"), чтобы получить полноценный объект документа. 3. Производим манипуляции с объектом. 4. Выполняем ЗначениеВРеквизитФормы(Объект, "Объект"), чтобы вернуть изменения на форму.

Проблема «N+1» и вызовы в цикле

Посмотрим на ситуацию со стороны сетевого трафика. Каждый вызов серверного общего модуля с клиента — это отдельный сетевой пакет. Если вы поместите вызов серверной функции (даже очень быстрой) внутрь цикла, обходящего табличную часть из 500 строк, пользователь заметит существенные задержки (так называемые «фризы» интерфейса).

Решение: Вместо того чтобы вызывать сервер 500 раз для каждой строки, передайте на сервер массив всех нужных ссылок одним разом. Обработайте их там одним запросом и верните на клиент массив структур или соответствие с результатами. Это сократит количество сетевых транзакций до одной, что кардинально ускорит работу программы.

Использование модулей повторного использования

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

Таким образом, решение проблемы ПолучитьОбъект() на тонком клиенте кроется не просто в «добавлении галочки», а в правильном распределении логики между интерфейсным слоем и слоем работы с данными, соблюдая правила контекстного перехода и заботясь о производительности системы.

← На главную