Почему не заполняется файл DBF в 1С и как исправить ошибку «Поле объекта не обнаружено»?

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

Работа с файлами формата 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С, работающих на управляемых формах, выполнять операции чтения/записи файлов непосредственно в процедурах с директивой компиляции &НаКлиенте крайне не рекомендуется по следующим причинам:

  1. Ограничение веб-клиента: Объект XBase физически не поддерживается в веб-клиенте (при запуске системы через браузер). Попытка инициализировать конструктор Новый XBase вызовет системную ошибку.
  2. Производительность: Чтение и обработка больших объемов данных на клиенте нагружают рабочую станцию пользователя и вызывают "зависание" интерфейса. Такие ресурсоемкие задачи должны выполняться на сервере.
  3. Права доступа к файловой системе: Прямая запись на диски (особенно сетевые ресурсы или папки операционной системы) может быть заблокирована локальной политикой безопасности на стороне клиента.

Как сделать правильно? Нам необходимо разделить логику на клиентскую и серверную части. На клиенте пользователь выбирает файл и передает его на сервер. На сервере происходит вся тяжелая работа: чтение, обработка данных, запись в новый файл. Затем готовый файл передается обратно на клиент.

Реализуем правильное решение по шагам

Рассмотрим по шагам, как переписать процедуру импорта и экспорта DBF-файла с соблюдением стандартов разработки 1С.

Шаг 1. Передача исходного файла на сервер

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


&НаКлиенте
Процедура ИмпортИЭкспортФайла(Команда)
    
    // Описываем параметры выбора файла
    ПараметрыВыбора = Новый ПараметрыДиалогаВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    ПараметрыВыбора.Фильтр = "Файлы DBF (*.dbf)|*.dbf";
    ПараметрыВыбора.МножественныйВыбор = Ложь;
    ПараметрыВыбора.Заголовок = "Выберите исходный файл биллинга";
    
    Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    
    // Начинаем выбор файла
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
    НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , , ПараметрыВыбора, УникальныйИдентификатор);
    
КонецПроцедуры

&НаКлиенте
Процедура ПослеВыбораФайла(РезультатПомещения, ДополнительныеПараметры) Экспорт
    
    Если РезультатПомещения = Неопределено ИЛИ НЕ РезультатПомещения.ИмяФайлаПомещено Тогда
        Возврат; // Пользователь отменил выбор
    КонецЕсли;
    
    // Получаем имя выбранного файла без пути
    ФайлИнфо = Новый Файл(РезультатПомещения.ИмяФайла);
    ИмяФайла = ФайлИнфо.Имя;
    
    // Передаем адрес во временном хранилище на сервер для обработки
    АдресРезультата = ОбработатьФайлНаСервере(РезультатПомещения.Адрес, ИмяФайла);
    
    // Получаем обработанный файл обратно на компьютер пользователя
    Если ЗначениеЗаполнено(АдресРезультата) Тогда
        ПараметрыПолучения = Новый ПараметрыПолученияФайлаССервера;
        НачатьПолучениеФайлаССервера( , АдресРезультата, "C:\НЕРАЗНЕСЕНО\" + ИмяФайла, ПараметрыПолучения);
    КонецЕсли;
    
КонецПроцедуры

Шаг 2. Обработка файла и генерация нового DBF на сервере

Теперь напишем серверную функцию, которая извлечет файл из временного хранилища, запишет его во временный каталог сервера, прочитает с помощью 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. Давайте резюмируем ключевые правила, которые уберегут вас от подобных ошибок в будущем:

Применение данных рекомендаций позволит вам создавать стабильный и быстрый код для интеграции 1С с любыми внешними базами данных формата DBF.

← На главную