При адаптации алгоритмов и внешних обработок с обычных форм на управляемые разработчики часто сталкиваются с ошибками вида Переменная не определена (Справочники) или Переменная не определена (Документы) при проверке модуля формы в контексте тонкого клиента. Разберем детально, почему возникает такое поведение платформы «1С:Предприятие 8.3», какие принципиальные архитектурные ограничения действуют на клиенте и как правильно переписать процедуру загрузки данных.
В обычных формах (толстый клиент) весь программный код по умолчанию выполнялся в едином контексте, где были доступны как элементы интерфейса, так и все объекты прикладного решения базы данных. В управляемом приложении действует строгое разделение логики на клиентскую и серверную части:
Справочники, Документы, РегистрыСведений, Константы, Перечисления и ПланыВидовХарактеристик существуют исключительно на стороне сервера. Попытка обратиться к ним в процедуре с директивой компиляции &НаКлиенте всегда приводит к ошибке компиляции.Объект: попытки вызвать Объект.Справочники... также ошибочны. На управляемой форме свойство Объект является реквизитом формы с типом ДанныеФормыСтруктура, а не ссылкой на метаданные или модулем прикладного объекта.XBase и коллекций: объект XBase, используемый для чтения DBF-файлов, а также универсальные коллекции ТаблицаЗначений и ДеревоЗначений не поддерживаются в тонком клиенте. Работа с ними должна выполняться на сервере.Проанализируем правильную архитектуру операции загрузки данных из внешнего файла:
XBase, выполняет поиск, создание элементов справочников, документов и запись наборов записей регистров.Разберем реализацию клиентской процедуры нажатия кнопки выполнения:
&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
// Проверяем заполнение реквизитов формы
Если Не ЗначениеЗаполнено(Объект.Организация) Тогда
Сообщить("Не выбрана организация!");
Возврат;
КонецЕсли;
Если Не ЗначениеЗаполнено(Объект.Дата) Тогда
Сообщить("Не выбрана дата!");
Возврат;
КонецЕсли;
Если ПустаяСтрока(Объект.ФайлЗагрузки) Тогда
Сообщить("Не выбран файл для загрузки!");
Возврат;
КонецЕсли;
// Помещаем локальный файл во временное хранилище для передачи на сервер
АдресФайлаВХранилище = "";
ПоместитьФайл(АдресФайлаВХранилище, Объект.ФайлЗагрузки, , Ложь, УникальныйИдентификатор);
Если ПустаяСтрока(АдресФайлаВХранилище) Тогда
Сообщить("Не удалось прочитать файл!");
Возврат;
КонецЕсли;
Сообщить("Загрузка начата: " + ТекущаяДата());
// Вызываем серверную обработку
ЗагрузитьДанныеНаСервере(АдресФайлаВХранилище);
Сообщить("Загрузка успешно завершена: " + ТекущаяДата());
КонецПроцедуры
Рассмотрим, как выглядит процедура выполнения на сервере. Обратите внимание: для вызова экспортных процедур модуля объекта внешней обработки (например, КВП_ЗаписатьОбъект) необходимо получить контекст объекта обработки через функцию РеквизитФормыВЗначение("Объект").
&НаСервере
Процедура ЗагрузитьДанныеНаСервере(АдресФайлаВХранилище)
// Получаем экземпляр объекта обработки для доступа к его методам
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
// Извлекаем двоичные данные из хранилища и сохраняем во временный файл на сервере
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайлаВХранилище);
ВременныйФайлСервера = ПолучитьИмяВременногоФайла("dbf");
ДвоичныеДанные.Записать(ВременныйФайлСервера);
ФайлДБФ = Новый XBase;
ФайлДБФ.Кодировка = КодировкаТекста.UTF8;
ФайлДБФ.ОткрытьФайл(ВременныйФайлСервера, , Истина);
Если Не ФайлДБФ.Открыта() Тогда
УдалитьФайлы(ВременныйФайлСервера);
ВызватьИсключение "Не удалось открыть временный файл DBF на сервере!";
КонецЕсли;
// На сервере мы можем свободно использовать ТаблицуЗначений
ТаблицаПлощадей = Новый ТаблицаЗначений;
ТаблицаПлощадей.Колонки.Добавить("Помещение", Новый ОписаниеТипов("СправочникСсылка.УПЖКХ_Помещения"));
ТаблицаПлощадей.Колонки.Добавить("ОбщаяПлощадь", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТаблицаДолейЛС = Новый ТаблицаЗначений;
ТаблицаДолейЛС.Колонки.Добавить("ЛицевойСчет", Новый ОписаниеТипов("СправочникСсылка.КВП_ЛицевыеСчета"));
ТаблицаДолейЛС.Колонки.Добавить("Помещение", Новый ОписаниеТипов("СправочникСсылка.УПЖКХ_Помещения"));
ТаблицаДолейЛС.Колонки.Добавить("Доля", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
Пока Не ФайлДБФ.ВКонце() Цикл
ЛицевойСчетСтрока = СокрЛП(ФайлДБФ.LS);
ФИО = СокрЛП(ФайлДБФ.FIO);
Корпус = СокрЛП(ФайлДБФ.KORP);
Квартира = СокрЛП(ФайлДБФ.KV);
ОбщаяПлощадь = ФайлДБФ.OB_PLOSH;
НаселенныйПункт = СокрЛП(ФайлДБФ.NP);
НаименованиеУлицы = СокрЛП(ФайлДБФ.NAME_UL);
НомерДома = СокрЛП(ФайлДБФ.DOM);
ФайлДБФ.Следующая();
Если ПустаяСтрока(ЛицевойСчетСтрока) Или ПустаяСтрока(ФИО) Тогда
Продолжить;
КонецЕсли;
// Работа со справочником Здания
ПредставлениеЗдания = НаселенныйПункт + ", " + НаименованиеУлицы + ", дом № " + НомерДома;
Здание = Справочники.КВП_Здания.НайтиПоНаименованию(ПредставлениеЗдания, Истина);
Если Здание.Пустая() Тогда
ЗданиеОбъект = Справочники.КВП_Здания.СоздатьЭлемент();
ЗданиеОбъект.Наименование = ПредставлениеЗдания;
ЗданиеОбъект.НомерДома = НомерДома;
ЗданиеОбъект.Записать();
Здание = ЗданиеОбъект.Ссылка;
КонецЕсли;
// Работа со справочником Помещения
Помещение = Справочники.УПЖКХ_Помещения.НайтиПоКоду(Квартира, , , Здание);
Если Помещение.Пустая() Тогда
ПомещениеОбъект = Справочники.УПЖКХ_Помещения.СоздатьЭлемент();
ПомещениеОбъект.Код = Квартира;
ПомещениеОбъект.Владелец = Здание;
ПомещениеОбъект.Наименование = "Кв. " + Квартира;
ПомещениеОбъект.Записать();
Помещение = ПомещениеОбъект.Ссылка;
КонецЕсли;
// Накопление данных по площадям
НоваяСтрока = ТаблицаПлощадей.Добавить();
НоваяСтрока.Помещение = Помещение;
НоваяСтрока.ОбщаяПлощадь = ОбщаяПлощадь;
КонецЦикла;
ФайлДБФ.ЗакрытьФайл();
УдалитьФайлы(ВременныйФайлСервера);
// Запись наборов записей в регистры
ТаблицаПлощадей.Свернуть("Помещение", "ОбщаяПлощадь");
Для Каждого Стр Из ТаблицаПлощадей Цикл
НаборЗаписей = РегистрыСведений.КВП_ПлощадьПомещения.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Стр.Помещение);
НаборЗаписей.Отбор.Период.Установить(Объект.Дата);
НовЗапись = НаборЗаписей.Добавить();
НовЗапись.Период = Объект.Дата;
НовЗапись.Объект = Стр.Помещение;
НовЗапись.Площадь = Стр.ОбщаяПлощадь;
НаборЗаписей.Записать();
КонецЦикла;
КонецПроцедуры
Справочники.НайтиПоНаименованию() и НайтиПоКоду() внутри итераций обхода большого файла создают множество одиночных запросов к СУБД. При больших объемах данных рекомендуется сначала загрузить строки файла во временную таблицу и найти ссылки одним пакетным запросом.НачатьТранзакцию() и ЗафиксироватьТранзакцию(), чтобы избежать блокировок и ускорить запись на диск.Попытка ... Исключение или после завершения чтения, чтобы не засорять дисковое пространство рабочего сервера.