При переходе с обычных форм на управляемые разработчики часто сталкиваются с ошибками при попытке сформировать файл DBF. Типичная ситуация: на клиенте создается объект XBase, затем вызывается серверная функция для получения строк документа, и разработчик пытается вернуть результат запроса или объект ТаблицаЗначений напрямую в клиентский контекст. В результате платформа выдает исключение: «Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. Ошибка преобразования данных XDTO...».
Разберем причины возникновения этой ошибки, выясним, почему не стоит передавать выборку через реквизиты формы, и рассмотрим архитектурно правильный шаблон выгрузки данных документа в формат DBF.
Посмотрим на частую ошибку в кодовой базе. Разработчик пытается выполнить запрос на сервере и вернуть таблицу значений клиенту:
&НаКлиенте
Процедура СоздатьФайл(Команда)
// ... создание XBase ...
Выборка = ДанныеДокумента(Объект.Реализация); // Ошибка вызова контекста!
// ... запись в файл ...
КонецПроцедуры
&НаСервереБезКонтекста
Функция ДанныеДокумента(Ссылка)
Запрос = Новый Запрос;
// ... текст запроса ...
Возврат Запрос.Выполнить().Выгрузить(); // Возвращается ТаблицаЗначений
КонецФункции
Выясним причину ошибки: тип данных ТаблицаЗначений не сериализуется для передачи на тонкий клиент. Тонкий клиент попросту не поддерживает работу с таблицами значений в оперативной памяти. Попытка обойти это через создание реквизита формы типа ТаблицаЗначений и метод ЗначениеВРеквизитФормы() формально решает проблему передачи, но является грубой архитектурной ошибкой. Загрузка сотен или тысяч строк в контекст формы резко раздувает ее размер, перегружает канал связи между клиентом и сервером и замедляет работу интерфейса.
Рассмотрим эталонную схему клиент-серверного взаимодействия для формирования файлов выгрузки:
&НаСервереБезКонтекста создается временный файл через ПолучитьИмяВременногоФайла().XBase, после чего файл обязательно закрывается методом ЗакрытьФайл().ДвоичныеДанные и помещается во временное хранилище через ПоместитьВоВременноеХранилище().Разберем по шагам реализацию этой схемы на примере выгрузки табличной части документа Реализация товаров и услуг.
Посмотрим на практический пример программного кода для модуля управляемой формы:
&НаКлиенте
Процедура ВыгрузитьВDBF(Команда)
Если Не ЗначениеЗаполнено(Объект.Ссылка) Тогда
Предупреждение("Сначала необходимо записать документ!");
Возврат;
КонецЕсли;
// Формируем файл на сервере и получаем адрес во временном хранилище
АдресФайла = СформироватьDBFНаСервере(Объект.Ссылка);
// Получаем файл из временного хранилища и сохраняем на клиенте
ИмяФайла = "Export_" + СокрЛП(Объект.Номер) + ".dbf";
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
Диалог.ПолноеИмяФайла = ИмяФайла;
Диалог.Фильтр = "Файлы dBase (*.dbf)|*.dbf";
Если Диалог.Выбрать() Тогда
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресФайла);
ДвоичныеДанныеФайла.Записать(Диалог.ПолноеИмяФайла);
ПоказатьОповещениеПользователя("Выгрузка завершена", , "Файл сохранен: " + Диалог.ПолноеИмяФайла);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СформироватьDBFНаСервере(СсылкаНаДокумент)
// 1. Получаем путь к временному файлу на стороне сервера
ВременныйПуть = ПолучитьИмяВременногоФайла("dbf");
// 2. Инициализируем XBase и настраиваем поля таблицы
ФайлDBF = Новый XBase;
ФайлDBF.Кодировка = КодировкаXBase.OEM; // Либо КодировкаXBase.ANSI
// Внимание: имя поля в DBF не должно превышать 10 символов
ФайлDBF.Поля.Добавить("NOMER", "N", 5, 0);
ФайлDBF.Поля.Добавить("NAIMEN", "S", 80, 0);
ФайлDBF.Поля.Добавить("KOLVO", "N", 15, 3);
ФайлDBF.Поля.Добавить("CENA", "N", 15, 2);
ФайлDBF.Поля.Добавить("SUMMA", "N", 15, 2);
ФайлDBF.СоздатьФайл(ВременныйПуть);
// 3. Выполняем выборку данных документа
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТабЧасть.НомерСтроки КАК НомерСтроки,
| ТабЧасть.Номенклатура.Наименование КАК НоменклатураНаименование,
| ТабЧасть.Количество КАК Количество,
| ТабЧасть.Цена КАК Цена,
| ТабЧасть.Сумма КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ТабЧасть
|ГДЕ
| ТабЧасть.Ссылка = &Ссылка
|УПОРЯДОЧИТЬ ПО
| НомерСтроки";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Выборка = Запрос.Выполнить().Выбрать();
// 4. Заполняем таблицу DBF строками
Пока Выборка.Следующий() Цикл
ФайлDBF.Добавить();
ФайлDBF.NOMER = Выборка.НомерСтроки;
ФайлDBF.NAIMEN = Строка(Выборка.НоменклатураНаименование);
ФайлDBF.KOLVO = Выборка.Количество;
ФайлDBF.CENA = Выборка.Цена;
ФайлDBF.SUMMA = Выборка.Сумма;
ФайлDBF.Записать();
КонецЦикла;
// 5. Обязательно закрываем файл для снятия блокировки ОС
ФайлDBF.ЗакрытьФайл();
// 6. Помещаем файл во временное хранилище и удаляем файл на диске сервера
ФайлВПамяти = Новый ДвоичныеДанные(ВременныйПуть);
АдресВХранилище = ПоместитьВоВременноеХранилище(ФайлВПамяти);
УдалитьФайлы(ВременныйПуть);
Возврат АдресВХранилище;
КонецФункции
Рассмотрим подробнее важные технические нюансы, несоблюдение которых приводит к ошибкам выполнения:
ЗакрытьФайл(): пока файл не закрыт явно, дескриптор файла удерживается платформой 1С. Попытка прочитать файл через Новый ДвоичныеДанные(ВременныйПуть) вызовет исключение совместного доступа к файлу."НОМЕНКЛАТУРА" или имя длиннее 10 знаков приведет к ошибке при создании структуры файла."S" требуют явного строкового значения. Передача ссылочного типа (например, СправочникСсылка.Номенклатура) вызовет ошибку заполнения. В запросе следует сразу обращаться к реквизитам через точку (Номенклатура.Наименование) или использовать функцию Строка().XBase не поддерживает кодировку UTF-8. Доступны только два режима: КодировкаXBase.OEM (кодовая страница CP866, стандарт для DOS-систем) и КодировкаXBase.ANSI (Windows-1251)."D:\dBase.dbf") внутри серверных процедур. Сервер 1С выполняется под отдельным системным пользователем, который может не иметь прав на запись в корень диска, либо сервер может функционировать под управлением Linux, где структура путей принципиально иная. Всегда используйте метод ПолучитьИмяВременногоФайла().