Rash thoughts about .NET, C#, F# and Dynamics NAV.


"Every solution will only lead to new problems."

Thursday, 30. March 2006


Navision: Universeller Dataport

Filed under: Navision — Steffen Forkmann at 15:39 Uhr

Aufgrund eines Artikels vom “Nawischn-Blog” möchte ich hier mal einen Weg aufzeigen, wie man sich einen universellen Dataport für Microsoft Navision bauen kann, mit dem man ein ganzen Satz an Tabellen aus CSV-Tabellen einlesen kann. Interessant ist das vor allem wenn man eine Datenübernahme machen will und im ersten Schritt erstmal alle Daten in Zwischentabellen ins Navision ziehen will.

Um den automatischen Import bewerkstelligen zu können müssen die Zieltabellen angelegt werden. Dafür braucht man jedoch Informationen über die Struktur der Daten – also welches Feld kommt mit welchem Datentyp an welcher Position in welcher Importdatei. Das ist zugleich auch der schwierigste Teil.

Glücklicherweise kann man sowas auch teilweise automatisch machen – zum Beispiel für Exceltabellen oder dBase-Datenbanken: Mit einem PlugIn für den Total Commander kann man nämlich Excel-Dateien bzw. eine ganze dBase-Datenbank mit einer Schema.ini exportieren. Bei einzelnen Dateien kann man das natürlich auch schnell per Hand anlegen.

Ab nun läuft alles automatisch. Als erstes müssen die Zieltabellen angelegt werden. Dazu habe ich eine Template-Tabelle angelegt die nur ein Feld für die laufende Datensatznummer enthält, die Zieltabellen werden dann aus dieser Tabelle kopiert:

PROCEDURE CreateTable@1000000007( p_NewObjectID@1000000002 : Integer; p_NewObjectName@1000000003 : Text[250]); VAR l_Object@1000000004 : Record 2000000001; l_NewObject@1000000005 : Record 2000000001; BEGIN // Copy Migration Template Table l_Object.GET(l_Object.Type::Table,'', DATABASE::"Migration Template"); l_Object.CALCFIELDS("BLOB Reference"); l_NewObject.INIT; l_NewObject.COPY(l_Object); l_NewObject.ID := p_NewObjectID; l_NewObject.Name := p_NewObjectName; l_NewObject.INSERT(TRUE); END;

Der Umweg mit der Template-Tabelle kommt dadurch zustande, dass man beim Insert in die Objekt-Tabelle schon einen Primärschlüßel haben muss und den kann man nur anlegen wenn man ein Feld anlegt. Felder können jedoch nur in in existierende Tabellen eingefügt werden. Diesen Konflikt konnte ich bisher nur über den Objekt-Designer auflösen. Als nächstes müssen die Felder aus der Schema.ini in die Zieltabellen eingefügt werden:

PROCEDURE AddFieldToTable@1000000009( p_TableNo@1000000001 : Integer; p_FieldID@1000000003 : Integer; p_FieldName@1000000002 : Text[250]; p_Type@1000000004 : Text[30]; p_Width@1000000005 : Integer); VAR l_Field@1000000000 : Record 2000000041; BEGIN l_Field.INIT; l_Field.TableNo := p_TableNo; l_Field."No." := p_FieldID; l_Field.FieldName:= p_FieldName; CASE p_Type OF 'Text': BEGIN l_Field.Type := l_Field.Type::Text; IF p_Width < 250 THEN l_Field.Len := p_Width ELSE l_Field.Len := 250; END; 'Date': l_Field.Type := l_Field.Type::Date; 'Double': l_Field.Type := l_Field.Type::Decimal; 'Long': l_Field.Type := l_Field.Type::Integer; 'Memo': BEGIN l_Field.Type := l_Field.Type::Text; l_Field.Len := 250; END; ELSE ERROR(Text003,p_Type); END; l_Field.Enabled := TRUE; l_Field.SQLDataType := l_Field.SQLDataType::Varchar; l_Field.INSERT(TRUE); END;

Nachdem nun alle Zieltabellen fertig sind, kann man nun durch die einzelnen CSV-Dateien gehen und die Daten in die entsprechenden Felder transportieren. Dazu durchläuft man die CSV-Tabellen zeilenweise und holt sich zum Beispiel mit folgenden Funktionen die einzelnen Feldwerte (per Index) aus dem Zeilentext:

PROCEDURE GetStringWithDelimiter@1000000011( p_Text@1000000000 : Text[1024]; p_ID@1000000001 : Integer; p_Delimiter@1000000002 : Char) Result : Text[1024]; VAR l_DelimiterCount@1000000003 : Integer; i@1000000004 : Integer; l_StringOpen@1000000005 : Boolean; BEGIN l_DelimiterCount := 1; FOR i := 1 TO STRLEN(p_Text) DO BEGIN IF (p_Text[i] = '"') AND (NOT l_StringOpen) THEN l_StringOpen := TRUE; IF (p_Text[i] = '"') AND l_StringOpen AND ((STRLEN(p_Text) = i) OR (p_Text[i+1] = p_Delimiter)) THEN l_StringOpen := FALSE; IF (p_Text[i] = p_Delimiter) AND NOT l_StringOpen THEN BEGIN l_DelimiterCount := l_DelimiterCount + 1; IF l_DelimiterCount > p_ID THEN EXIT(Result); END ELSE IF (l_DelimiterCount = p_ID) AND (p_Text[i] '"') THEN Result := Result + COPYSTR(p_Text,i,1); END; END; PROCEDURE GetString@1000000010( p_Text@1000000000 : Text[1024]; p_ID@1000000001 : Integer; p_Delimiter@1000000002 : Char) Result : Text[1024]; VAR l_DelimiterCount@1000000003 : Integer; i@1000000004 : Integer; l_StringOpen@1000000005 : Boolean; BEGIN l_DelimiterCount := 1; FOR i := 1 TO STRLEN(p_Text) DO BEGIN IF (p_Text[i] = p_Delimiter) THEN BEGIN l_DelimiterCount := l_DelimiterCount + 1; IF l_DelimiterCount > p_ID THEN EXIT(Result); END ELSE IF (l_DelimiterCount = p_ID) AND (p_Text[i] '"') THEN Result := Result + COPYSTR(p_Text,i,1); END; END;

4 Comments »

  1. Danke Steffen. Diese Herangehensweise ist wirklich vielversprechend. Ich werde das auf jeden Fall mal ausprobieren.

    Schönes Wochenende
    Jörg

    Comment by J√∂rg — Friday, 31. March 2006 um 20:20 Uhr

  2. *ähm* … hast du mir noch ein Beispiel für diese Schema.ini. Ich kenne den TotalCommander, aber diese Möglichkeit ist mir noch unbekannt. Wie funzt das?

    Comment by J√∂rg — Friday, 31. March 2006 um 20:22 Uhr

  3. Für den TotalCommander gibt es PlugIns (Lister) die allerlei Datenformate anzeigen können. Zum Beispiel welche für Excel oder dBase. Die exportieren ale nach gleichem Schema in CSV-Dateien:

    [Zugänge_xls_Tabelle1.CSV]
    ColNameHeader=True
    CharacterSet=1252
    Format=CSVDelimited
    DecimalSymbol=.
    MaxScanRows=64
    Col1=ERNr Text Width 255
    Col2=KontoNrFIBU Text Width 255
    Col3=KontoNrABU Text Width 255
    Col4=LosNr Text Width 255
    Col5=Bezeichnung Text Width 255
    Col6=F6 Text Width 255
    Col7=BelegDatum DateTime
    Col8=AIBWGNR Text Width 255
    Col9=WGNR Text Width 255
    Col10=NDJahrRND Double
    Col11=KST Double
    Col12=AHKZugangWJ2005 Double
    Col13=Anzahl Double
    Col14=SchachtbisSchacht Double
    Col15=LängeinMetern Double
    Col16=Querschnitt Double
    Col17=Baustoff Double
    Col18=WGNRAIB Text Width 255
    Col19=WGNR1 Text Width 255
    Col20=Fertigstellung DateTime

    [Zugänge_xls_Tabelle2.CSV]
    ColNameHeader=True
    CharacterSet=1252
    Format=CSVDelimited
    DecimalSymbol=.
    MaxScanRows=64
    Col1=ERNr Text Width 255
    Col2=KontoNrFIBU Text Width 255
    Col3=KontoNrABU Text Width 255
    Col4=LosNr Text Width 255
    Col5=Bezeichnung Text Width 255
    Col6=F6 Text Width 255
    Col7=BelegDatum DateTime
    Col8=AIBWGNR Text Width 255
    Col9=WGNR Text Width 255
    Col10=NDJahrRND Double
    Col11=KST Double
    Col12=AHKZugangWJ2005 Double
    Col13=Anzahl Double

    Comment by Steffen Forkmann — Monday, 3. April 2006 um 8:43 Uhr

  4. Achso eine Sache noch:
    Du musst aufpassen, wenn du immer Text Width 255 nimmst kommst du sehr schnell die maximale Navision-Record-Größe. Also immer versuchen die Feldbreiten so klein wie möglich zu halten

    Comment by Steffen Forkmann — Monday, 3. April 2006 um 9:06 Uhr

RSS feed for comments on this post. | TrackBack URI

Leave a comment

XHTML ( You can use these tags): <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> .