Generieren und kompilieren Sie die Namen-zu-Index-Übersetzung/-Zuordnung für eine schnellere Wiederverwendbarkeit
-
11-12-2019 - |
Frage
Angenommen, ich erhalte Daten von einem Dienst (den ich nicht kontrollieren kann) als:
public class Data
{
// an array of column names
public string[] ColumnNames { get; set; }
// an array of rows that contain arrays of strings as column values
public string[][] Rows { get; get; }
}
und auf der mittleren Ebene möchte ich dies einem zuordnen/übersetzen IEnumerable<Entity>
wo Spaltennamen drin sind Data
Vielleicht in meinem als Eigenschaften dargestellt Entity
Klasse.Ich sagte Mai weil ich möglicherweise nicht alle vom Dienst zurückgegebenen Daten benötige, sondern nur einige davon.
Transformation
Dies ist eine Abstraktion eines Algorithmus, der die Übersetzung durchführen würde:
- Erstelle ein
IDictionary<string, int>
vonColumnNames
So kann ich einzelne Spaltennamen problemlos Array-Indizes in einzelnen Zeilen zuordnen. - Benutze Reflexion, um meine zu untersuchen
Entity
Eigenschaftennamen, damit ich sie mit Spaltennamen abgleichen kann - iterieren
Data.Rows
und erschaffe meinEntity
Objekte und füllen Sie Eigenschaften gemäß der in Nr. 1 durchgeführten Zuordnung.Wahrscheinlich mit Reflexion undSetValue
auf Eigenschaften, um sie festzulegen.
Optimierung
Der obere Algorithmus würde natürlich funktionieren, aber ich denke, dass er aufgrund der Verwendung von Reflektion etwas Caching und möglicherweise eine spontane Kompilierung durchführen sollte, was die Dinge erheblich beschleunigen könnte.
Wenn die Schritte 1 und 2 abgeschlossen sind, könnten wir tatsächlich eine Methode generieren, die ein Array von Zeichenfolgen verwendet, meine Entitäten mithilfe von Indizes direkt instanziiert und sie kompiliert und zwischenspeichern für eine spätere Wiederverwendung.
Normalerweise erhalte ich eine Ergebnisseite, sodass nachfolgende Anfragen dieselbe kompilierte Methode wiederverwenden würden.
Zusätzlicher Fakt
Dies ist für die Frage (und Antworten) nicht zwingend erforderlich, aber ich habe auch zwei Attribute erstellt, die bei der Zuordnung von Spalten zu Eigenschaften helfen, wenn diese in ihren Namen nicht übereinstimmen.Ich habe das Offensichtlichste geschaffen MapNameAttribute
(das benötigt eine Zeichenfolge und aktiviert optional auch die Groß-/Kleinschreibung) und IgnoreMappingAttribute
für Immobilien auf meinem Entity
das sollte keinen Daten zugeordnet werden.Diese Attribute werden jedoch in Schritt 2 des oberen Algorithmus gelesen, sodass Eigenschaftsnamen gemäß diesen deklarativen Metadaten gesammelt und umbenannt werden, sodass sie mit den Spaltennamen übereinstimmen.
Frage
Was ist der beste und einfachste Weg, eine solche Methode zu generieren und zu kompilieren?Lambda-Ausdrücke? CSharpCodeProvider
Klasse?
Haben Sie vielleicht ein Beispiel für generierten und kompilierten Code, der etwas Ähnliches bewirkt?Ich vermute, dass Zuordnungen ein ziemlich häufiges Szenario sind.
Notiz:In der Zwischenzeit werde ich PetaPoco (und vielleicht auch Massive) untersuchen, weil beide afaik, im laufenden Betrieb, genau zu Kartierungszwecken kompilieren und zwischenspeichern.
Lösung
Anregung: Erhalten Sie FastMember von NuGet
Dann verwenden Sie einfach:
var accessor = TypeAccessor.Create(typeof(Entity));
Dann einfach in deiner Schleife, wenn du das gefunden hast memberName
Und newValue
für die aktuelle Iteration:
accessor[obj, memberName] = newValue;
Dies soll das tun, was Sie verlangen;Intern verwaltet es eine Reihe von Typen, sofern diese zuvor gesehen wurden.Wenn ein neuer Typ angezeigt wird, wird eine neue Unterklasse erstellt TypeAccessor
spontan (via TypeBuilder
) und speichert es zwischen.Jedes einzigartig TypeAccessor
kennt die Eigenschaften für diesen Typ und grundsätzlich verhält sich einfach wie ein:
switch(memberName) {
case "Foo": obj.Foo = (int)newValue;
case "Bar": obj.Bar = (string)newValue;
// etc
}
Da dies zwischengespeichert wird, zahlen Sie nur etwaige Kosten (und nicht wirklich einen groß Kosten) das erste Mal, dass es Ihren Typ sieht;die restliche Zeit ist es kostenlos.Weil es nutzt ILGenerator
direkt, es vermeidet auch jede unnötige Abstraktion, zum Beispiel via Expression
oder CodeDom, also ist es so schnell wie möglich.
(Ich sollte das auch klarstellen dynamic
Typen, d.h.Typen, die implementieren IDynamicMetaObjectProvider
, es kann eine einzelne Instanz verwenden, um jedes Objekt zu unterstützen).
Zusätzlich:
Was du könnte tun ist:Nimm das Vorhandene FastMember
Code eingeben und zur Verarbeitung bearbeiten MapNameAttribute
Und IgnoreMappingAttribute
während WriteGetter
Und WriteSetter
;Dann passiert der ganze Voodoo auf deinem Daten Namen, statt der Mitglied Namen.
Dies würde bedeuten, die Zeilen zu ändern:
il.Emit(OpCodes.Ldstr, prop.Name);
Und
il.Emit(OpCodes.Ldstr, field.Name);
sowohl WriteGetter
Und WriteSetter
, und tun a continue
zu Beginn des foreach
Schleifen, wenn es ignoriert werden soll.