Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

arkadiusz.wasniewski

DBF po ludzku

Jakiś czas temu Sławek pisał o dostępie poprzez sterowniki ODBC do tabel w formacie Excel. W podobny sposób można również próbować przetwarzać pliki DBF. Ale można też prościej... bardziej po ludzku...

Wersji formatu DBF jest oczywiście wiele, ale my zajmiemy się wersją 3, bardzo popularną zwłaszcza w środowisku MS-DOS. Niezbędne będzie przy tym skorzystanie z przestrzeni nazw InteropServices umożliwiającej dostęp do kodu niezarządzanego.

Każdy plik DBF składa się z nagłówka

    [StructLayout(LayoutKind.Sequential)]

    struct Dbf3Header

    {

        public const byte ReservedSize = 20;

 

        public byte Dbf;

        public byte Year;

        public byte Mounth;

        public byte Day;

        public int RecordCount;

        public ushort HeaderSize;

        public ushort RecordSize;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=Dbf3Header.ReservedSize)]

        public byte[] Reserved;

    }

który zawiera podstawowe informacje o przechowywanych wewnątrz danych. Nazwy pól są na tyle dosłowne, iż nie będziemy się na ich temat rozwodzić. Każde pole, które będzie występować w pliku, opisane jest w następujący sposób:

    [StructLayout(LayoutKind.Sequential)]

    struct Dbf3Field

    {

        public const byte FieldNameSize = 11;

        public const byte ReservedSize = 14;

 

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=Dbf3Field.FieldNameSize)]

        public byte[] FieldName;

        public byte FieldType;

        public int Offset;

        public byte Length;

        public byte Precision;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=Dbf3Field.ReservedSize)]

        public byte[] Reserved;

    }

Pole FieldType zawiera rodzaj pola i może przyjmować wartość (w wersji tej dla uproszczenia pominiemy pola typu memo):

  • 'C' dla pola znakowego;
  • 'D' dla pola data;
  • 'L' dla pola logicznego;
  • 'N' dla pola numerycznego.

Aby móc wykonywać operacje na pliku DBF musimy go wpierw otworzyć:

        private void OpenDbf()

        {

            header = MarshalHelper.FromStream<Dbf3Header>(this.stream);

            int fieldCount =

                (header.HeaderSize - Marshal.SizeOf(header)) /

                Marshal.SizeOf(typeof(Dbf3Field));

 

            fields = new Dictionary<string, Dbf3Field>(fieldCount);

            int offset = 1;

            for (int i = 0; i < fieldCount; i++)

            {

                Dbf3Field field = MarshalHelper.FromStream<Dbf3Field>(

                    this.stream);

                field.Offset = offset;

                offset = offset + field.Length;

                fields.Add(MarshalHelper.ByteArrayToString(

                    field.FieldName).ToUpper(), field);

            }

 

            buffer = new byte[header.RecordSize];

        }

lub utworzyć:

        private void CreateDbf()

        {

            header.Dbf = 0x03;

            header.RecordCount = 0;

            header.RecordSize = 0;

            header.HeaderSize = (ushort)(Marshal.SizeOf(header) + 1);

 

            MarshalHelper.ToStream<Dbf3Header>(stream, header);

            this.WriteToStreamEndOfHeader();

 

            fields = new Dictionary<string, Dbf3Field>();

        }

Jeśli plik został przez nas utworzony to powinniśmy dodać do niego definicje pól, które będą wewnątrz składowane (w poniższym przykładzie dla uproszczenia pominięto wszystkie pola poza rodzajem data):

        public void NewField(string fieldName, Dbf3FieldType fieldType,

            byte length, byte precision)

        {

            Dbf3Field field = new Dbf3Field();

 

            field.FieldName = MarshalHelper.StringToByteArray(

                fieldName.ToUpper(), Dbf3Field.FieldNameSize);

            switch (fieldType)

            {

                ...

                case Dbf3FieldType.Date:

                    field.FieldType = Convert.ToByte('D');

                    field.Length = 8;

                    field.Precision = 0;

                    break;

                ...

            }

 

            if (header.RecordSize == 0)

                header.RecordSize = 1;

            field.Offset = header.RecordSize;

            header.HeaderSize += (ushort)Marshal.SizeOf(typeof(Dbf3Field));

            header.RecordSize += field.Length;

 

            fields.Add(fieldName.ToUpper(), field);

        }

Mając przygotowany plik DBF możemy dodawać do niego nowe wiersze. Wpierw przygotowujemy bufor dla danych:

        public void NewRecord()

        {

            buffer = new byte[header.RecordSize];

        }

a po wypełnieniu zapisujemy na dysk:

        public void Write()

        {

            stream.Position = header.HeaderSize +

                header.RecordSize * header.RecordCount;

            header.RecordCount++;

            stream.Write(buffer, 0, buffer.Length);

        }

Wszystkie dane w plikach DBF są składowane w postaci łańcuchów. Oznacza to, iż tak naprawdę potrzebujemy jednej głównej metody do zapisu danych:

        public void SetString(string fieldName, string value)

        {

            Dbf3Field field = this.GetField(fieldName);

            string fieldValue;

            if (value == null)

                value = string.Empty;

            if (field.FieldType == Convert.ToByte('C'))

                fieldValue = value.PadRight(field.Length);

            else

                fieldValue = value.PadLeft(field.Length);

            byte[] data = Encoding.Default.GetBytes(fieldValue);

            Buffer.BlockCopy(data, 0, buffer, field.Offset, field.Length);

        }

Reszta metod umożliwiających zapisywanie innych typów danych wywołuje wewnętrznie tę metodę wykonując rzutowanie na typ łańcuchowy np.:

        public void SetInt32(string fieldName, int value)

        {

            this.SetString(fieldName, Convert.ToString(

                value, dbfCultureInfo));

        }

Przy konwersji musimy zwrócić uwagę na fakt, iż dane w pliku DBF są składowane według amerykańskich ustawień regionalnych. To samo dotyczy danych odczytywanych:

        public int GetInt32(string fieldName)

        {

            return Convert.ToInt32(this.GetString(fieldName),

                dbfCultureInfo);

        }

Operując na plikach DBF należy również pamiętać, iż po nagłówku należy w strumieniu umieścić znacznik końca nagłówka 0x0D, a po zapisaniu wszystkich danych na dysk znacznik końca danych 0x1A.

Powyższe przykłady zawierają między innymi wywołania metod pomocniczej klasy MarshalHelper. Klasa ta definiuje metody ułatwiające zamianę kodu zarządzanego na niezarządzany i odwrotnie. Korzysta ona przede wszystkim z Marshal.StructureToPtr oraz z Marshal.PtrToStructure.

Dzięki powyższemu kodowi możemy uniezależnić się od sterowników i ustawień systemowych co niejednokrotnie bardzo się przydaje, jeśli oczywiście ktoś z tego typu rozwiązań musi korzystać.

Opublikowane 12 czerwca 2007 15:02 przez arkadiusz.wasniewski
Filed under: , ,

Komentarze:

 

mgrzeg said:

Jak przyjdzie mi kiedys pozmagac sie z dbfami to wiem gdzie zaczac ;)

A nie myslales o zrobieniu czegos z Dbf3Field - znaczy sie zamiast pola FieldType zrobic mala hierarchie tych pol? W ten sposob odpadlo by ci sprawdzanie w roznych miejscach tego FieldType, a moglbys dalej pracowac na abstrakcyjnej klasie Dbf3Field i wszystko 'samo by sie robilo' :)

A tak poza tym - gdzie kod do sprucia, he?

czerwca 12, 2007 17:34
 

arkadiusz.wasniewski said:

Kodu całości nie będzie niestety. To wszystko co mogę pokazać. Jest to bowiem rozwiązanie stworzone na potrzeby wewnętrzne w miejscu, w którym obecnie tworzę i tylko tyle mogę udostępnić :-(

Arek

czerwca 13, 2007 09:38
 

Wojciech Gebczyk said:

Niezly pomysl na optymalizacje :-) Przypominaja mi sie czasy Pascala i czytanie reordow z pliku... :-)

czerwca 13, 2007 09:42
 

dario-g said:

Przypominają mi się stare dobre ;) czasy :))

czerwca 13, 2007 22:24
 

Marek Zgadzaj said:

Dziękuję za ciekawy artykuł.

Ponieważ przez wiele lat pisałem programy w Clipperze i CA-Visual-Objects i częściowo Vulcan.NET, stosuję inne rozwiązanie, moim zdaniem łatwiejsze (dla mnie) ponieważ pracuję z tymi programami na co dzień.

Firma GrafX opracowała nowy program Vulcan.NET, który jest kontynuacją VO na platformie .NET. W związku z tym, Vulcan przetwarza DBF'y w sposób naturalny (natywny).

Przetwarzanie DBF w C# ( i innym języku .NET) bez bezpośredniego używania ani uczenia się tych ww. programów, można wykonać w taki sposób:

1. Pobrać i zainstalować Vulcan.NET wersję TRIAL ze strony producenta:

http://www.govulcan.net/portal/. Ponieważ i dokumentacja Vulcana jest bardzo kiepska i nie zawiera opisu klas dostępu do DBF, dlatego trzeba wykonać krok 2.

Z tej instalacji wykorzystujemy tylko niektóre DLL do włączenia do C# + ewentualnie Help.

2. Pobrać i zainstalować CA-Visual Object TRIAL, wersja nie ma znaczenia:

http://www.cavo.com/. Pod tym adresem są także podręczniki w PDF ale najważniejsze informacje są w pliku pomocy CHM, jest tam składnia wszyskich klas dostępu do DBF.

3. W programie C# dodać odniesienia (referencje) do odpowiednich klas Vulcana, i napisać kod do przetwarzania DBF. Przykład (fragmenty)

using VulcanVORDDClasses; // <--- tutaj są klasy do przetwarzania DBF

using VulcanVOSystemClasses;

using VulcanRTFuncs;

using Vulcan;

using Vulcan.RDD;

(...)

// Otwarcie DBF

string cDBF = "SPOLKI.DBF";

// DBServer{<oFileSpec> | <cFileName>, [<lShareMode>], [<lReadOnlyMode>], [<cDriver>], [<acRDDs>]}

DbServer oDBServer = new DbServer( cDBF, true, false, "DBFCDX");

if (!(bool)oDBServer.Used)

{

   MessageBox.Show("Tabela bazy Parter DOS jest niedostępna"

             + "\nNazwa: " + cFullDBF, "Błąd otwarcia tabeli Parter DOS");

   return false;

}

// Tutaj możemy rozpocząć przetwarzanie (lub najpierw otworzyć indeksy), np.

oDBServer.SetIndex( "SPOLKI.CDX" ); // otwarcie pliku indeksowego

oDBServer.SetOrder( "KLIENCI" ); // ustawienie kolejności, np wg nazwisk

oDBServer.GoTop(); // ustawieni na 1-szy rekord

while (!(bool)oDBServer.EoF) // Przetwarzanie do końca tabeli

{

  if (!(bool)oDBServer.Deleted) // Wykluczenie rekordów oznaczonych do skasowania

  {

/// Tutaj przetwrzanie rekordu, np. pobranmie pola "NAZWISKO"

string nazwisko = (string)oDBServer.FielGet( "NAZWISKO");

       //itd.

   }

  oDBServer.Skip() // pobranie następnego rekordu

}

oDBServer:Close() // zamknięcie DBF i CDX

--------------------------------

Jest jeszcze jeden produkt .NET obsługujący DBF'y, o nazwie CULE. Kiedyś nawet brałem udział w testowaniu wersji przedprodukcyjnej. Na stronie producenta jest wersja FREE, nie badałem tego, ale można sprawdzić czy klasy nadadzą się do włączenia do C# i czy opis jest dokładniejszy.

Używam bibliotek Vulcana w aplikacjach C# do importowania i przetwarzania danych z DBF do MS SQL.

Gdyby ktoś z kolegów potrzebował informacji z tej dziedziny - pomogę.

Linki:

-----

1. Strona CA-VO http://www.cavo.com/

2. Strona producenta GrafX: http://www.grafxsoft.com/

3. Strona Vulcan.NET: http://www.govulcan.net/portal/

4. Strona Cule: http://www.softwareperspectives.com/CULEPlace/

Marek

stycznia 8, 2008 14:20
 

Triale, triale said:

Znacznie prosciej pobrac FREE xbapi.pl ze strony www.otc.pl. Dziala z cdx-ami i jest w miare szybka.

stycznia 14, 2008 05:25
 

Triale, triale said:

Sorry za literowke, chodzilo o xbapipl.

stycznia 14, 2008 08:14
 

kosiek said:

Czy możesz udostępnić klasę MarshalHelper? Nie wiem, jak ją zaimplementować, żeby działało poprawnie.

maja 14, 2008 12:52
 

arkadiusz.wasniewski said:

We notce dotyczącej iterator&#243;w czy też kiedy opisywałem operacje na plikach DBF posługiwałem się

czerwca 4, 2008 14:02
 

arkadiusz.wasniewski said:

Klasa MarshalHelper dostępna jest (wraz z opisem) we wpisie z czerwca 2008 roku: http://zine.net.pl/blogs/arkadiusz_wasniewski/archive/2008/06/04/marshalhelper-zarz-dza-niezarz-dzanym.aspx

września 21, 2008 18:21
 

arkadiusz.wasniewski said:

Niekt&#243;rzy pewnie pamiętają m&#243;j wpis dotyczący plik&#243;w DBF . Nagł&#243;wek pliku DBF zawiera

września 25, 2008 15:04
 

Mietek said:

Tylko technologia vulcan.net umożliwia aktyną pracę na otwartych plikach typu NTX

października 9, 2010 12:41
Komentarze anonimowe wyłączone
W oparciu o Community Server (Personal Edition), Telligent Systems