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