Работа с файлами формата DBF (dBase) в платформе 1С:Предприятие 8 осуществляется с помощью специализированного объекта XBase. Несмотря на то что этот формат считается устаревшим, он до сих пор активно используется для интеграции со сторонними информационными системами, банками и биллинговыми сервисами. При реализации выгрузки данных в DBF начинающие разработчики часто сталкиваются с трудностями, связанными с логикой обхода коллекций и контекстом выполнения кода (клиент-серверное взаимодействие).
В этой статье мы подробно разберем, почему возникает ошибка «Не обнаружено поле объекта...» при попытке заполнить DBF-файл, проанализируем архитектурные недостатки работы с дисковыми файлами на клиенте и напишем правильный, оптимизированный код для управляемого приложения.
Давайте выясним причину основной проблемы, с которой столкнулся автор темы. В своем исходном коде он пытается перенести данные из экранной таблицы (реквизита формы) в новый файл DBF. Для этого он использует цикл, но допускает критическую синтаксическую ошибку.
Рассмотрим фрагмент ошибочного кода, который привел к сбою:
Для каждого СтрТаб из Реквизит1 цикл
НовыйДБФ.Добавить;
НовыйДБФ.Packid = Реквизит1.Packid; // Ошибка здесь!
НовыйДБФ.Dt = Реквизит1.Dt; // Ошибка здесь!
// ...
КонецЦикла;
В данном цикле переменная СтрТаб является итератором, который на каждом шаге принимает значение конкретной строки таблицы Реквизит1. Однако внутри тела цикла разработчик обращается не к этой строке (СтрТаб), а напрямую к самой таблице (Реквизит1).
Поскольку Реквизит1 является объектом типа ДанныеФормыКоллекция (или ТаблицаЗначений), у него нет свойств с именами колонок (таких как Packid, Dt и т.д.). Эти свойства есть только у конкретной строки. Платформа вполне закономерно выбрасывает исключение: «Не обнаружено поле объекта Реквизит1.Packid».
Для исправления этой ошибки достаточно заменить обращение к таблице на обращение к текущей строке цикла:
Для каждого СтрТаб из Реквизит1 Цикл
НовыйДБФ.Добавить();
НовыйДБФ.Packid = СтрТаб.Packid; // Правильное обращение к строке
НовыйДБФ.Dt = СтрТаб.Dt; // Правильное обращение к строке
// ...
НовыйДБФ.Записать();
КонецЦикла;
Проанализируем первый листинг автора более детально. В нем скрывается еще несколько потенциальных проблем, которые могут привести к неверному заполнению данных или ошибкам времени выполнения.
1. Ошибка в функции СтрЗаменить
В коде заполнения строк присутствует следующее выражение:
НоваяСтрока.Dtpay = СокрЛП(СтрЗаменить(ФайлДБФ.Dtpay,""," "));
Функция СтрЗаменить принимает параметры в строгом порядке: СтрЗаменить(<Строка>, <ПодстрокаПоиска>, <ПодстрокаЗамены>). Передача пустой строки "" во второй параметр является некорректной, так как платформа не может искать "ничто". Очевидно, автор перепутал параметры местами и хотел удалить лишние пробелы (заменив пробел на пустую строку), как это сделано в соседних строках. Правильный вариант:
НоваяСтрока.Dtpay = СокрЛП(СтрЗаменить(ФайлДБФ.Dtpay, " ", ""));
2. Проблема типов данных при записи в DBF
При создании полей нового DBF-файла автор жестко типизирует колонки:
НовыйДБФ.Поля.Добавить("Sum","N",8,2); // Числовой тип
НовыйДБФ.Поля.Добавить("Dtpay","D",8); // Тип "Дата"
Однако при считывании данных из исходного файла все значения предварительно очищаются от пробелов и помещаются в таблицу Реквизит1 в виде строк. Если попытаться записать текстовое значение (например, пустую строку или некорректно отформатированную строку даты) напрямую в числовое или датовое поле объекта XBase, это приведет либо к ошибке приведения типов, либо к записи пустых значений (нулей). Перед записью в DBF данные необходимо явно преобразовывать к нужным типам с помощью функций Число() и Дата().
В современных конфигурациях 1С, работающих на управляемых формах, выполнять операции чтения/записи файлов непосредственно в процедурах с директивой компиляции &НаКлиенте крайне не рекомендуется по следующим причинам:
XBase физически не поддерживается в веб-клиенте (при запуске системы через браузер). Попытка инициализировать конструктор Новый XBase вызовет системную ошибку.Как сделать правильно? Нам необходимо разделить логику на клиентскую и серверную части. На клиенте пользователь выбирает файл и передает его на сервер. На сервере происходит вся тяжелая работа: чтение, обработка данных, запись в новый файл. Затем готовый файл передается обратно на клиент.
Рассмотрим по шагам, как переписать процедуру импорта и экспорта DBF-файла с соблюдением стандартов разработки 1С.
Для того чтобы сервер мог прочитать локальный файл клиента, его необходимо поместить во временное хранилище. На клиенте мы используем асинхронные методы работы с файлами.
&НаКлиенте
Процедура ИмпортИЭкспортФайла(Команда)
// Описываем параметры выбора файла
ПараметрыВыбора = Новый ПараметрыДиалогаВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ПараметрыВыбора.Фильтр = "Файлы DBF (*.dbf)|*.dbf";
ПараметрыВыбора.МножественныйВыбор = Ложь;
ПараметрыВыбора.Заголовок = "Выберите исходный файл биллинга";
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
// Начинаем выбор файла
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , , ПараметрыВыбора, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораФайла(РезультатПомещения, ДополнительныеПараметры) Экспорт
Если РезультатПомещения = Неопределено ИЛИ НЕ РезультатПомещения.ИмяФайлаПомещено Тогда
Возврат; // Пользователь отменил выбор
КонецЕсли;
// Получаем имя выбранного файла без пути
ФайлИнфо = Новый Файл(РезультатПомещения.ИмяФайла);
ИмяФайла = ФайлИнфо.Имя;
// Передаем адрес во временном хранилище на сервер для обработки
АдресРезультата = ОбработатьФайлНаСервере(РезультатПомещения.Адрес, ИмяФайла);
// Получаем обработанный файл обратно на компьютер пользователя
Если ЗначениеЗаполнено(АдресРезультата) Тогда
ПараметрыПолучения = Новый ПараметрыПолученияФайлаССервера;
НачатьПолучениеФайлаССервера( , АдресРезультата, "C:\НЕРАЗНЕСЕНО\" + ИмяФайла, ПараметрыПолучения);
КонецЕсли;
КонецПроцедуры
Теперь напишем серверную функцию, которая извлечет файл из временного хранилища, запишет его во временный каталог сервера, прочитает с помощью XBase, заполнит таблицу значений, создаст новый файл с правильной структурой и вернет адрес готового файла обратно.
&НаСервереБезКонтекста
Функция ОбработатьФайлНаСервере(АдресВХранилище, ИмяФайла)
// 1. Извлекаем бинарные данные из временного хранилища
ДанныеФайла = ПолучитьИзВременногоХранилища(АдресВХранилище);
// 2. Создаем временные файлы на стороне сервера
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("dbf");
ДанныеФайла.Записать(ИмяВременногоФайла);
// 3. Открываем исходный DBF на чтение
ФайлДБФ = Новый XBase;
ФайлДБФ.ОткрытьФайл(ИмяВременногоФайла, , Истина); // Открываем только для чтения
Если НЕ ФайлДБФ.Открыт() Тогда
ВызватьИсключение "Не удалось открыть исходный DBF-файл на сервере!";
КонецЕсли;
// Создаем временную Таблицу Значений для хранения результатов
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Packid", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));
ТЗ.Колонки.Добавить("Dt", Новый ОписаниеТипов("Дата"));
ТЗ.Колонки.Добавить("Tp", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(1)));
ТЗ.Колонки.Добавить("Account", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(9, 0)));
ТЗ.Колонки.Добавить("Sum", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(8, 2)));
ТЗ.Колонки.Добавить("Usr", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));
// Читаем данные из файла во временную таблицу значений
ФайлДБФ.Первая();
Пока НЕ ФайлДБФ.ВКонце() Цикл
// Пример фильтрации данных (например, отсекаем по коду участка)
// Если Лев(СокрЛП(ФайлДБФ.Account), 3) = "123" Тогда
НоваяСтрока = ТЗ.Добавить();
// Очищаем строки от пробелов и приводим к нужным типам данных
НоваяСтрока.Packid = Число(СтрЗаменить(Строка(ФайлДБФ.Packid), " ", ""));
НоваяСтрока.Dt = Дата(ФайлДБФ.Dt);
НоваяСтрока.Tp = СокрЛП(ФайлДБФ.Tp);
НоваяСтрока.Account = Число(СтрЗаменить(Строка(ФайлДБФ.Account), " ", ""));
НоваяСтрока.Sum = Число(СтрЗаменить(Строка(ФайлДБФ.Sum), " ", ""));
НоваяСтрока.Usr = Число(СтрЗаменить(Строка(ФайлДБФ.Usr), " ", ""));
ФайлДБФ.Следующая();
КонецЦикла;
ФайлДБФ.ЗакрытьФайл();
УдалитьФайлы(ИмяВременногоФайла); // Очищаем временный файл источника
// 4. Создаем новый результирующий DBF-файл
ИмяФайлаРезультата = ПолучитьИмяВременногоФайла("dbf");
НовыйДБФ = Новый XBase;
// Важно: всегда явно указывайте кодировку для корректной записи кириллицы!
НовыйДБФ.Кодировка = КодировкаXBase.ANSI;
// Описываем структуру нового файла.
// Обратите внимание на длину имен полей - она не должна превышать 10 символов!
НовыйДБФ.Поля.Добавить("Packid", "N", 10);
НовыйДБФ.Поля.Добавить("Dt", "D", 8);
НовыйДБФ.Поля.Добавить("Tp", "S", 1);
НовыйДБФ.Поля.Добавить("Account", "N", 9);
НовыйДБФ.Поля.Добавить("Sum", "N", 8, 2);
НовыйДБФ.Поля.Добавить("Usr", "N", 10);
// Создаем физический файл на диске сервера
НовыйДБФ.СоздатьФайл(ИмяФайлаРезультата);
НовыйДБФ.Записать();
// 5. Заполняем новый файл данными из нашей таблицы значений
Для каждого СтрТаб Из ТЗ Цикл
НовыйДБФ.Добавить();
// Присваиваем значения полям новой строки
НовыйДБФ.Packid = СтрТаб.Packid;
НовыйДБФ.Dt = СтрТаб.Dt;
НовыйДБФ.Tp = СтрТаб.Tp;
НовыйДБФ.Account = СтрТаб.Account;
НовыйДБФ.Sum = СтрТаб.Sum;
НовыйДБФ.Usr = СтрТаб.Usr;
НовыйДБФ.Записать();
КонецЦикла;
НовыйДБФ.ЗакрытьФайл();
// Помещаем готовый файл во временное хранилище и возвращаем его адрес клиенту
ДвоичныеДанныеРезультата = Новый ДвоичныеДанные(ИмяФайлаРезультата);
АдресРезультата = ПоместитьВВременноеХранилище(ДвоичныеДанныеРезультата);
// Очищаем временные файлы на сервере
УдалитьФайлы(ИмяФайлаРезультата);
Возврат АдресРезультата;
КонецФункции
Мы спроектировали надежную архитектуру взаимодействия с внешними файлами DBF. Давайте резюмируем ключевые правила, которые уберегут вас от подобных ошибок в будущем:
Для каждого Итератор Из Коллекция всегда обращайтесь к свойствам через переменную итератора (в нашем примере это была СтрТаб), а не через саму исходную коллекцию.СокрЛП() и СтрЗаменить(Строка, " ", ""). Избегайте передачи пустого символа в качестве искомого значения в СтрЗаменить.&НаСервере или &НаСервереБезКонтекста. На клиенте оставляйте исключительно интерфейсные операции.Кодировка (например, КодировкаXBase.ANSI), чтобы избежать проблем с чтением кириллических символов в других программах.Применение данных рекомендаций позволит вам создавать стабильный и быстрый код для интеграции 1С с любыми внешними базами данных формата DBF.