CONNECTED Conference 2023 - Aufzeichnungen jetzt hier verfügbar +++                     

Suche

über alle News und Events

 

Alle News

 

Für Entwickler, Architekten, Projektleiter und...

Weiterlesen

In der Welt der Softwareentwicklung ist die...

Weiterlesen

QUIBIQ spendet für den guten Zweck – und für...

Weiterlesen

Eine bestimmte Antwort auf einen HTTP Request zu...

Weiterlesen

In einer Welt, die von stetigem Wandel geprägt...

Weiterlesen

In einem unserer Kundenprojekte, war das Ziel eine...

Weiterlesen

QUIBIQ Hamburg wird mit dem Hamburger...

Weiterlesen

Zwei Tage lang wurde vom 14.-15.11 wieder das...

Weiterlesen

Was ist ein Excel-Plugin – und wann ist es...

Weiterlesen

Wir expandieren, bringen Kunden und Talente besser...

Weiterlesen

How to: CSV-Verarbeitung - Teil 2

In einem vorherigen Artikel wurden Gründe abgewogen, die für eine Verarbeitung von FlatFiles mit BizTalk durch eine eigene C#-Komponente, statt dem FlatFile-Schema-Wizard, sprechen. Im Folgenden werden wir diese Komponente analysieren: Der FlatFile Parser, die Herausforderungen und eine genauere Beleuchtung der Performance.

Das Ziel

Ein eigens geschriebener Code ermöglicht maximale Flexibilität und Anpassbarkeit. Um die Vorzüge davon auszureizen, liegt ein besonderer Fokus auf der Wiederverwendbarkeit des Parsers. Letztendlich wollen wir für unterschiedliche FlatFiles nicht die Implementierung des Parsers verändern, sondern ihm lediglich eine gemappte Klasse zur Verfügung stellen.

Die Werkzeuge

FlatFiles weichen untereinander enorm ab. Sie unterscheiden sich beispielsweise im Aufbau oder nutzen unterschiedliche Separatoren. Um den Parser über diese variablen Ausprägungen zu informieren, werden sie in der Mapping-Klasse als Attribute dargestellt. Die Attribute ermöglichen uns Metainformationen mit einem C# Element, wie Klassen und Properties, zu verbinden.


In der obigen Darstellung sieht man eine beispielhafte Mapping-Klasse. Die Klasse stellt das FlatFile-Dokument dar, eine Property ist ein Datenfeld und alle Properties im Verbund sind ein Datensatz. Dateiweite Metainformationen, wie das Dateiformat oder Delimiter, sind als Klassen-Attribute beigefügt. Feldweite Metainformationen, wie die Reihenfolge, sind als Property-Attribute angemerkt. Zusätzlich können wahlweise Plausi-Prüfungen deklariert werden, wie ein Mindestwert oder erlaubte Werte, beigefügt werden. Um das Setzen der Attribute benutzerfreundlicher zu gestalten, befinden sich Pflichtangaben im Konstruktor und optionale Attribut-Parameter werden darauffolgend aufgeführt. Für Wahloptionen, wie für das Attribut fileFormat, ist es sinnhaft einen Enumerator zu erstellen.

Klassenauflösung über System.Reflection

Der Parser lernt die Mapping-Klasse erst zur Laufzeit kennen. Sie wird ihm als generischen Typ bei seiner Instanziierung übergeben.

Über den Namespace System.Reflection erhält er die Fähigkeit, die Klasseninformationen der zu konvertierenden Mapping-Klasse aufzulösen. Zu diesen Informationen gehören unter anderem die einzelnen Properties, deren Set-Methoden und vor alledem dessen Attribute. Das Jonglieren mit System.Reflection bietet ein dynamisches und flexibles verhalten, jedoch sind die damit verbundenen Operationen langsam und ineffizient. Als Musterfall nutzen wir eine CSV mit 10 Feldern und 5 Millionen Einträgen. Insgesamt wird die Set-Methode also 10 mal aufgelöst, jedoch 50 Millionen mal aufgerufen.

1) Direktes Setzen der Property

Der schnellste Ansatz ist das direkte Setzen der Property. Hierfür müsste der Parser und die gemappte Klasse hart verdrahtet sein um den set-Accessor anzusteuern. Wir verlieren den dynamischen Ansatz.

2) Ansteuern der PropertyInfo

Das Auflösen der PropertyInfo über System.Reflection und setzen der Properties über die SetValue-Methode ist ein enormer Zeitaufwand. Das muss besser gehen.

3) Ansteuern der MethodInfo

Das Objekt PropertyInfo enthält die MethodInfo der Set-Methode. Die MethodInfo kann durch .Invoke aufgerufen werden. Ein besseres Ergebnis, aber nicht zufriedenstellend. Das Problem ist, dass bei Methodenaufruf der MethodInfo sowie der PropertyInfo wieder und wieder die Route der System.Reflection genutzt wird.

Ein Ansatz ist die Nutzung von Delegaten. An Delegaten können Methoden gekapselt werden. Der Delegat ähnelt dabei einem Methoden-Pointer, der auf die Ziel-Methode verweist und dabei mehrfach ausgeführt werden kann ohne System.Reflection erneut anzusteuern. Eine Set-Methode ist eine anonyme Methode ohne Rückgabewert und benötigt daher den Action Delegaten. Die Eingangsparameter sind zum einen die Klasseninstanz (T) und der geparste Feldwert als object, da der Parser den tatsächlichen Werttyp erst zur Laufzeit kennen kann. Unser theoretisch und dynamisch einsetzbarer Delegat Action<T, object> funktioniert in der Praxis jedoch nicht. Die Set-Methode benötigt einen stark-typisierten Eingabeparameter, wie einen string oder int, das genutzte object ist jedoch schwach-typisiert. Das Resultat ist eine Exception.

Des Pudels Kern wäre den Parameter object zur Laufzeit zu bestimmen. Geht das?

Lösung 1: System.Linq.Expressions oder das Nutzen des Expression Trees
Der Namespace System.Linq.Expressions bietet die benötigte Funktionalität. Durch den Methodenaufruf Expression.Convert() wird der deklarierte Parameter object durch den Typ der Ziel-Property ersetzt. Das heißt für uns, dass deklarativ unsere Action<T, object> schwach-typisiert ist, doch object im Hintergrund den Typ der Property einnimmt. Im Anschluss lässt sich der Expression Tree nun mit unserem neuen Parameter und Methodenkörper zusammenbauen und kompilieren.

Unser gemessenes Ergebnis durch diese Anpassung:

 

Lösung 2: schwach-typisierten Delegaten an stark-typisierten Delegaten binden
Eine weitere Lösung, ist das binden eines schwach-typisierten Delegaten an einen stark-typisierten Delegaten. Übersetzt: ich binde beispielsweise die Action<T, object> an eine stark typisierte Action<T, string>.

Um in diesem Fall eine dynamische Auflösung zu erreichen, muss die aufrufende Methode die Parameter für TTarget (Klasseninstanz der Mapping-Klasse) und TParam (Typ der Property) übergeben. Um das zu erreichen, konvertiert der Caller die Methode per .MakeGenericMethod()-Aufruf zu einer generischen Methode, denn nur so können die Ziel-Parameter dynamisch injiziert werden.

Fazit

Der endgültige Vergleich zeigt keinen nennenswerten Unterschied zwischen den beiden Lösungsansätzen, jedoch eine enorme Leistungssteigerung zu den ungekapselten Set-Aufrufen.

Das war ein Ausflug in der Performance und Nutzung des Parsers. Der kommende Teil 3 wird sich mit dem Parsen von hierarchischen FlatFile-Strukturen befassen.
Dieser Beitrag kommt von Philipp, QUIBIQ Stuttgart.

Ihre Kontaktmöglichkeiten

Sie haben eine konkrete Frage an uns


 

Bleiben Sie immer auf dem Laufenden


 

Mit meinem "Ja" erkläre ich mich mit der Verarbeitung meiner Daten zur Zusendung von Informationen einverstanden. Ich weiß, dass ich diese Erklärung jederzeit durch einfache Mitteilung widerrufen kann. Bei einem Nein an dieser Stelle erhalte ich zukünftig keine Informationen mehr.

© QUIBIQ GmbH · Impressum · Datenschutz