В MS Dynamics AX на различных проектах часто приходится заниматься импортом данных. В случае если нам нужно импортировать справочники, то в MS DAX 12 есть свой внутренний инструмент загрузки данных DMF (Data migration framework), где легко настраиваются шаблоны импорта данных и производится сам импорт. В случае если импорт данных несет периодический характер, например, импорт строк журнала ценовых соглашений или строк заказа на продажу, то реализация импорта выполняется по каким-то собственным стандартам загрузки данных либо в произвольном виде. В этих случаях как правило чаще всего используются следующие типы файлов для импорта данных:

— excel файлы,

— текстовые файлы,

— dbf файлы.

Чтение excel файлов и текстовых файлов более распространенно и не вызывает трудностей у разработчиков, а вот с чтением dbf файлов приходится поломать голову, поэтому в этой статье приведу пример чтения dbf файла.

Для того что бы импортировать данные из dbf файла нужно сначала его прочитать т.е. открыть. Для этого я создал класс DBFReader в котором создал статичный метод read:

public static System.Data.DataTable read(Filename _fileName)
{
    System.Data.DataTable resultDT = new System.Data.DataTable();
    System.IO.FileStream fileStream;
    System.Data.DataColumnCollection columnsColl;
    System.Data.DataRowCollection rowColl;
    System.Data.DataRow row;
    System.Text.Encoding encoding;
    System.String string, string2;
    System.Globalization.DateTimeFormatInfo dateTimeFormat;
    System.Globalization.NumberFormatInfo numberFormat;
    System.Globalization.CultureInfo cultureInfo;
    System.DBNull dbNull;
    System.Byte[] buffer1 = new System.Byte[4]();
    container conBuffer1;
    System.Int32 num;
    System.Byte[] buffer2 = new System.Byte[2]();
    container conBuffer2;
    System.String[] strArray1;
    System.String[] strArray2;
    System.Byte[] numArray1;
    System.Byte[] numArray2;
    System.Byte[] numArray3;
    System.Byte[] numArray4;
    int length1, length2 = 0;
    int i, j, numRows;
    int index1, index2, index3;
    str char, stringXPP;
        
    new InteropPermission(InteropKind::ClrInterop).assert();
    fileStream = new System.IO.FileStream(_fileName, System.IO.FileMode::Open);
        
    fileStream.set_Position(4);
    fileStream.Read(buffer1, 0, buffer1.get_Length());
    conBuffer1 = DBFReader::convertArray(buffer1);
    num = conPeek(conBuffer1, 1) +
            conPeek(conBuffer1, 2) * 256 +
            conPeek(conBuffer1, 3) * 65536 +
            conPeek(conBuffer1, 4) * 16777216;
        
    fileStream.set_Position(8);
    fileStream.Read(buffer2, 0, buffer2.get_Length());
    conBuffer2 = DBFReader::convertArray(buffer2);
    length1 = (conPeek(conBuffer2, 1) + conPeek(conBuffer2, 2) * 256 - 1) / 32 - 1;
    numArray1 = new System.Byte[length1]();
    numArray2 = new System.Byte[length1]();
    numArray3 = new System.Byte[32 * length1]();
    fileStream.set_Position(32);
    fileStream.Read(numArray3, 0, numArray3.get_Length());
    strArray1 = new System.String[length1]();
    strArray2 = new System.String[length1]();
        
    encoding = System.Text.Encoding::get_Default();
    for (i = 0; i < length1; ++i)
    {
        string = encoding.GetString(numArray3, i * 32, 10);
        string = string.TrimEnd(new System.Char[1]());
        strArray1.set_Item(i, string);
        string2 = System.String::Concat(System.Convert::ToChar(numArray3.get_Item(i * 32 + 11)));
        strArray2.set_Item(i, string2);
        numArray1.set_Item(i, numArray3.get_Item(i * 32 + 16));
        numArray2.set_Item(i, numArray3.get_Item(i * 32 + 17));
        j = numArray1.get_Item(i);
        length2 += j;
        columnsColl = resultDT.get_Columns();
        char = strArray2.get_Item(i);
        switch (char)
        {
            case 'L':
                columnsColl.Add(strArray1.get_Item(i), System.Type::GetType("System.Boolean"));
                break;
            case 'D':
                columnsColl.Add(strArray1.get_Item(i), System.Type::GetType("System.DateTime"));
                break;
            case 'N':
                j = numArray2.get_Item(i);
                if (j == 0)
                {
                    columnsColl.Add(strArray1.get_Item(i), System.Type::GetType("System.Int64"));
                    break;
                }
                columnsColl.Add(strArray1.get_Item(i), System.Type::GetType("System.Decimal"));
                break;
            case 'F':
                columnsColl.Add(strArray1.get_Item(i), System.Type::GetType("System.Double"));
                break;
            default:
                columnsColl.Add(strArray1.get_Item(i), System.Type::GetType("System.String"));
                break;
        }
    }
    fileStream.ReadByte();
    cultureInfo = new System.Globalization.CultureInfo("en-US", false);
    dateTimeFormat = cultureInfo.get_DateTimeFormat();
    cultureInfo = new System.Globalization.CultureInfo("en-US", false);
    numberFormat = cultureInfo.get_NumberFormat();
    numArray4 = new System.Byte[length2]();
    resultDT.BeginLoadData();
        
    encoding = System.Text.Encoding::GetEncoding(866);
    numRows = num;
    for (index1 = 0; index1 < numRows; ++index1)
    {
        fileStream.ReadByte();
        fileStream.Read(numArray4, 0, numArray4.get_Length());
        row = resultDT.NewRow();
        index2 = 0;
        for (index3 = 0; index3 < length1; ++index3)
        {
            string = encoding.GetString(numArray4, index2, numArray1.get_Item(index3));
            string = string.TrimEnd(new System.Char[1]());
            j = numArray1.get_Item(index3);
            index2 += j;
            stringXPP = string;
            if (stringXPP != '')
            {
                char = strArray2.get_Item(index3);
                switch (char)
                {
                    case 'L':
                        row.set_Item(index3, stringXPP == 'T' ? true : false);
                        continue;
                    case 'D':
                        row.set_Item(index3, System.DateTime::ParseExact(string, 'yyyyMMdd', dateTimeFormat));
                        continue;
                    case 'N':
                        j = numArray2.get_Item(index3);
                        if (j != 0)
                            row.set_Item(index3, System.Decimal::Parse(string, numberFormat));
                        else
                            row.set_Item(index3, System.Int64::Parse(string, numberFormat));
                        continue;
                    case 'F':
                        row.set_Item(index3, System.Double::Parse(string, numberFormat));
                        continue;
                    default:
                        row.set_Item(index3, string);
                        continue;
                }
            }
            else
                row.set_Item(index3, '');
        }
        rowColl = resultDT.get_Rows();
        rowColl.Add(row);
    }
    resultDT.EndLoadData();
    fileStream.Close();
        
    return resultDT;
}

Для того чтобы байт массивы конвертировать в тип контейнер добавил метод convertArray:

public static container convertArray(System.Byte[] _mas)
{
    container resCon;
    int i, num, lengthMas = _mas.get_Length();
        
    for (i = 1; i <= lengthMas; i++)
    {
        num    = _mas.get_Item(i - 1);
        resCon += [num];
    }
        
    return resCon;
}

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

public class DBFReaderList
{
    System.Data.DataTable dataTable;
    System.Data.DataRowCollection rowCollection;
    System.Data.DataRow row;
    int num;
}
      
public System.Data.DataRow current()
{
    if (row)
        return row;
    else
        return null;
}
      
public anytype currentItemColumn(str _nameCol)
{
    System.Object obj;
        
    if (_nameCol && row)
    {
        obj = row.get_Item(_nameCol);
        return DBFReaderList::convertValueToXppType(obj);
    }
    else
        return null;
}
      
public boolean moveNext()
{
    int countLines = rowCollection.get_Count();
        
    if (num >= 0 && rowCollection && countLines > num)
    {
        row = rowCollection.get_Item(num);
        num++;
        return true;
    }
        
    return false;
}
      
public void new(System.Data.DataTable _dataTable)
{
    dataTable = _dataTable;
    if (dataTable)
        rowCollection = dataTable.get_Rows();
    num = 0;
}
      
public void reset()
{
    num = 0;
}
      
public static DBFReaderList construct(System.Data.DataTable _dataTable)
{
    return new DBFReaderList(_dataTable);
}
      
private static anytype convertValueToXppType(System.Object _value)
{
    System.Boolean boolValue;
    boolean boolValueXpp;
    System.DateTime dateValue;
    TransDate dateValueXpp;
    System.Int64 intValue;
    int64 intValueXpp;
    System.Decimal decimalValue;
    System.Double doubleValue;
    real realValueXpp;
    System.String stringValue;
    str stringValueXpp;
    System.Type typeValue = _value.GetType();
    str stringType = typeValue.ToString();
        
    switch (stringType)
    {
        case 'System.Boolean':
            boolValue = System.Convert::ToBoolean(_value);
            boolValueXpp = boolValue;
            return boolValueXpp;
        case 'System.DateTime':
            dateValue = System.Convert::ToDateTime(_value);
            dateValueXpp = dateValue;
            return dateValueXpp;
        case 'System.Int64':
            intValue = System.Convert::ToInt64(_value);
            intValueXpp = intValue;
            return intValueXpp;
        case 'System.Decimal':
            decimalValue = System.Convert::ToDecimal(_value);
            realValueXpp = decimalValue;
            return realValueXpp;
        case 'System.Double':
            doubleValue = System.Convert::ToDouble(_value);
            realValueXpp = doubleValue;
            return realValueXpp;
        default:
            stringValue = System.Convert::ToString(_value);
            stringValueXpp = stringValue;
            return stringValueXpp;
    }
}

Ну и в конечном итоге пример как работать с данными классами:

DBFReaderList readerList;
str valueSearch;

try { readerList = DBFReaderList::construct(DBFReader::read(fileName)); }
catch { throw error('Неудалось открыть файл. Файл поврежден или не существует'); }

try
{
    while (readerList.moveNext())
    {
        valueSearch = readerList.currentItemColumn(‘columnName’);
        info(valueSearch);
    }
}
catch
{
    error('Ошибка чтения файла');
}

Сначала мы открываем файл с помощью класса DBFReader, после чего с помощью класса DBFReaderList производится перебор строк через цикл while, где получаем значение конкретной ячейки из файла. Таким вот образом происходит чтение dbf файла, как оказывается ничего сложного.