Почему появляется пустая строка в DBF-файле при работе с XBase в 1С 7.7 и как это исправить?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Работа с внешними файлами DBF через встроенный объект XBase в платформе 1С:Предприятие 7.7 — классическая задача, с которой часто сталкиваются разработчики при интеграции систем. Однако этот механизм таит в себе неочевидные нюансы. Один из наиболее распространенных сбоев — внезапное появление пустой строки в файле DBF, после чего запись любых последующих данных полностью прекращается, хотя в отладчике все свойства объекта выглядят заполненными.

В этой статье мы подробно разберем, почему возникает пустая запись, как работают индексы CDX в связке с таблицами DBF, почему нельзя использовать метод Сжать() внутри транзакций или циклов, и как правильно организовать отказоустойчивую запись данных.

Выясняем причину: Механизм появления пустой строки

Давайте проанализируем, что происходит на уровне СУБД (движка Codebase), когда мы выполняем стандартный цикл добавления записи. Типичный проблемный код выглядит следующим образом:


Xb = СоздатьОбъект("XBase"); 
Xb.ОткрытьФайл(Путь + "File.dbf", Путь + "File.cdx", 0); 
NAME = Данные.NAME; 
ID = Данные.ID; 

Xb.ТекущийИндекс("IDXID");      
Xb.Ключ.ID = ID;
Если ЗначениеЗаполнено(Данные.NOM) = 1 Тогда
    Если Xb.НайтиПоКлючу(0) = 0 Тогда
        Xb.Добавить();
        Xb.ID = ID;
        Xb.NAME = NAME;
    КонецЕсли;
    Xb.IDPOSTNOM = Строка(Данные.NOM);
Иначе
    Если Xb.НайтиПоКлючу(0) = 1 Тогда
        Xb.Удалить();
    КонецЕсли;
    Xb.Сжать();
КонецЕсли;
Xb.Записать();   
Xb.ЗакрытьФайл();

Разберем по шагам внутреннюю логику работы этого алгоритма:

  1. Метод Добавить() физически выделяет под запись новое место (строку) в файле DBF. На этом этапе все поля новой записи инициализируются значениями по умолчанию: пустые строки, нули и пустые даты.
  2. Затем разработчик заполняет свойства объекта Xb.ID, Xb.NAME и другие. Эти данные пока находятся в оперативной памяти (в буфере записи).
  3. При вызове метода Записать() платформа пытается зафиксировать данные из буфера на диск и одновременно обновить все связанные индексы в файле CDX.
  4. Если при обновлении любого из индексов (даже не того, который выбран методом ТекущийИндекс()) происходит ошибка — например, нарушается уникальность поля — процесс записи аварийно прерывается на уровне базы данных.

В результате этого прерывания заполненный буфер сбрасывается, но физически созданная методом Добавить() строка в DBF-файле уже создана и остается пустой! Более того, индексный файл CDX рассинхронизируется с таблицей DBF, из-за чего последующие операции поиска и добавления начинают работать некорректно или полностью блокируются.

Анализируем влияние уникальных индексов

Обратите внимание на создание индексов. Допустим, при создании файла был определен индекс с признаком уникальности:

БД.ДобавитьИндекс("IDXIDPOSTNOM", "IDPOSTNOM", 1, 0, "");

Третий параметр, равный 1, указывает системе, что значение поля IDPOSTNOM должно быть строго уникальным во всей таблице.

Частая ошибка разработчиков заключается в предположении, что если перед записью установлен другой текущий индекс (например, Xb.ТекущийИндекс("IDXID")), то проверки по остальным индексам выполняться не будут. Это не так. При вызове Записать() СУБД обновляет все зарегистрированные в файле CDX индексы. Если вы попытаетесь записать дублирующееся значение в поле IDPOSTNOM, система вернет ошибку уникальности, запись прервется, и в файле появится пустая строка.

Решение проблемы на уровне структуры файлов: если поле не обязано быть уникальным, измените признак уникальности на 0 при создании индекса:

БД.ДобавитьИндекс("IDXIDPOSTNOM", "IDPOSTNOM", 0, 0, "");

Грубейшая ошибка: Метод Сжать() внутри цикла

Посмотрим на ветку Иначе в исходном коде:


Если Xb.НайтиПоКлючу(0) = 1 Тогда
    Xb.Удалить();
КонецЕсли;
Xb.Сжать();

Метод Сжать() (в англоязычной версии — Pack) выполняет физическое удаление записей, помеченных на удаление с помощью метода Удалить(). Для этого движок XBase вынужден перестроить всю таблицу: создать временный файл, скопировать туда только актуальные записи, удалить старый файл, переименовать новый и полностью перестроить все связанные индексы.

Вызов Сжать() внутри итерационного цикла обработки записей приводит к критическим последствиям:

Как сделать правильно: помечайте записи на удаление методом Удалить() внутри цикла обработки, а метод Сжать() вызывайте строго один раз в самом конце работы алгоритма, перед закрытием файла.


// ... Код цикла, где выполняются операции удаления Xb.Удалить() ...

// Выносим сжатие за пределы основного цикла обработки:
Если Xb.Модифицирован() = 1 Тогда
    Xb.Записать();
КонецЕсли;

Xb.Сжать(); // Физически удаляем помеченные записи один раз для всего файла
Xb.ЗакрытьФайл();

Диагностика ошибок через КодОшибки() и транзакционный подход

Объект XBase в 1С 7.7 при возникновении ошибок записи часто ведет себя «молча» — программа не падает с критической ошибкой выполнения, а просто молча портит файл DBF. Чтобы избежать этого, необходимо обязательно анализировать состояние выполнения после вызова Записать() с помощью метода КодОшибки().

Рассмотрим пример правильного написания отказоустойчивого кода записи данных с обработкой ошибок:


Xb.Добавить();
Xb.ID = ID;
Xb.NAME = NAME;
Xb.IDPOSTNOM = Строка(Данные.NOM);

Xb.Записать();

// Проверяем, успешно ли прошла запись в DBF/CDX
Если Xb.КодОшибки() < 0 Тогда
    КодОш = Xb.КодОшибки();
    // Отменяем транзакцию записи в буфере, чтобы не плодить пустые строки
    Xb.Отменить(); 
    
    Если КодОш = -340 Тогда
        Сообщить("Ошибка: Нарушение уникальности индекса! Запись отклонена.", "!");
    Иначе
        Сообщить("Системная ошибка работы с DBF. Код ошибки: " + КодОш, "!");
    КонецЕсли;
КонецЕсли;

Метод Отменить() в случае сбоя очищает буфер записи и предотвращает появление той самой "пустой строки" в таблице, сохраняя целостность структуры данных.

Как правильно работать с уникальными индексами

Если бизнес-логика вашей задачи жестко требует уникальности поля IDPOSTNOM, то простое отключение уникальности в индексе — это плохой обходной путь, который приведет к дублированию данных. В таком случае необходимо проверять уникальность значения программно перед выполнением добавления.

Посмотрим на пример реализации такой проверки:


// Нам нужно добавить новую запись, но поле IDPOSTNOM должно быть уникальным
НовоеЗначениеIDPOSTNOM = Строка(Данные.NOM);

// 1. Переключаемся на индекс по проверяемому полю
Xb.ТекущийИндекс("IDXIDPOSTNOM");
Xb.Ключ.IDPOSTNOM = НовоеЗначениеIDPOSTNOM;

// 2. Ищем, нет ли уже такого значения в файле
Если Xb.НайтиПоКлючу(0) = 1 Тогда
    // Запись с таким ключом уже существует! Предотвращаем дублирование
    Сообщить("Запись со значением IDPOSTNOM = " + НовоеЗначениеIDPOSTNOM + " уже существует! Пропускаем.", "W");
Иначе
    // Записи нет, можем безопасно добавлять
    Xb.ТекущийИндекс("IDXID"); // Возвращаем рабочий индекс поиска по ID
    Xb.Добавить();
    Xb.ID = ID;
    Xb.NAME = NAME;
    Xb.IDPOSTNOM = НовоеЗначениеIDPOSTNOM;
    Xb.Записать();
КонецЕсли;

При таком подходе вы гарантируете целостность базы данных и полностью исключаете возникновение скрытых ошибок СУБД Codebase, обеспечивая стабильную и быструю работу вашей обработки обмена данными.

← На главную