Dynamische Objektgenerierung mit ExpandoObject
Ich musste eine Excel-Datei mit eigenen definierten Spalten in eine neue mit dynamischen Spalten konvertieren und war etwas verwirrt, wie ich das ohne ernsthafte Leistungsprobleme richtig machen sollte.
Ich bin auf ein Tutorial gestoßen, das Sie [hier](https://www.oreilly.com/content/building-c-objects-dynamically/ finden können und das .NET ExpandoObject verwendet, mit dem Sie praktisch ein Objekt erstellen und dynamische Mitglieder hinzufügen können.
ExpandoObject
Die Microsoft-Definition lautet:
Stellt ein Objekt dar, dessen Mitglieder zur Laufzeit dynamisch hinzugefügt und entfernt werden können.
Und es gibt ein paar Anmerkungen:
Mit der ExpandoObject-Klasse können Sie Mitglieder ihrer Instanzen zur Laufzeit hinzufügen und löschen sowie Werte dieser Mitglieder festlegen und abrufen. Diese Klasse unterstützt die dynamische Bindung, wodurch Sie Standardsyntax wie „sampleObject.sampleMember“ anstelle einer komplexeren Syntax wie „sampleObject.GetAttribute(“sampleMember”) verwenden können.
Aktuelles Verhalten
Wir haben ein ExcelExportService, das durch Übergabe eines List<T>, in diesem Fall ExcelItem, Reflection verwendet, um eine xlsx-Datei zu erstellen.
Bisher sieht unser Code so aus:
class ExcelItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Age { get; set; }
public string CreatedAt { get; set; }
public string UpdatedAt { get; set; }
}
ExcelItem ist das Objekt mit allen Eigenschaften, die zum Generieren der Excel-Datei verwendet werden.
public static byte[] GetExcelBytes() {
List<ExcelItem> excelItems = new List<ExcelItem>();
ExcelExportService exportService = new ExcelExportService();
foreach (var items in itemsToExcel)
{
excelItems.Add(new ExcelItem()
{
Id = item.Id,
Name = item.Name,
Surname = item.Surname,
Age = item.Age,
CreatedAt = item.CreatedAt
UpdatedAt = item.UpdatedAt
});
}
return exportService.ExportToExcel(excelItems);
}
Dieser Ansatz funktioniert perfekt, die Datei xlsx wird ohne Probleme generiert, wobei jede Spalte jede Eigenschaft ist. Sie würden diese Methode GetExcelBytes beispielsweise wie folgt verwenden, um sie in einer Datei zu speichern:
File.WriteAllBytes("path-to-somewhere/my-file.xlsx", GetExcelBytes());
Das Problem
Da wir genau wissen, dass wir alle Eigenschaften des ExcelItem-Objekts verwenden, möchte ich eigentlich nicht alle davon verwenden, sondern nur vielleicht 2 oder 3 davon.
Eine Voraussetzung ist auch, dass ich den Code nicht ändern möchte. Alles sollte vom administrator erledigt werden, der entscheidet, welche Spalten angezeigt werden sollen, und er kann die Eigenschaften im Code kennen oder auch nicht.
TLDR: Alles sollte dynamisch sein, wir haben ein Objekt mit Eigenschaften und wir müssen sicherstellen, dass die generierte Datei N Eigenschaften dieses Objekts enthält, aber offensichtlich nicht fest codiert ist.
Dynamische Spalten
Die Änderung würde darin bestehen, ein dynamic-Objekt zu verwenden, da wir festlegen möchten, welche Eigenschaften des Objekts zum Generieren der Spaltenliste verwendet werden.
Nehmen wir an, wir haben ein Objekt mit vielen Eigenschaften, zum Beispiel einer Tonne davon, und wir möchten das ExcelItem-Objekt nicht jedes Mal ändern, wenn wir eine Änderung vornehmen. Wir erstellen eine ColumnExcelItem-Tabelle, die zum Generieren dieses ExcelItem verwendet wird.
Datenbankstruktur
Wir speichern diese Definition in unserer Datenbank in etwa so:
Id PropertyName
---------------------
1 Name
2 Surname
3 Age
Beachten Sie, dass wir weniger Werte haben als die vorhandenen Eigenschaften des Objekts ExcelItem, da es 6 Eigenschaften hat und nur 3 in der Datenbank aufgeführt ist.
Beachten Sie außerdem, dass PropertyName mit dem Eigenschaftsnamen des Objekts übereinstimmen muss, das ich für die dynamische Zuweisung verwende.
Das Objekt bauen
Nachdem wir nun die Datenbanktabelle haben, müssen wir das Repository erstellen, um diese zu erhalten und sie im Code verwenden zu können.Ich werde für diesen Teil kein Tutorial erstellen, sondern direkt zum Anfang der neuen Funktion GetExcelBytes() springen.
public static byte[] GetExcelBytes(List<string> columns) {
List<ExcelItem> excelItems = new List<ExcelItem>();
List<dynamic> objectsToExcel = new List<dynamic>();
ExcelExportService exportService = new ExcelExportService();
foreach (var item in itemsToExcel)
{
dynamic newObject = new ExpandoObject();
foreach (var col in columns)
{
this.AddProperty(newObject, col, product.GetType().GetProperty(col).GetValue(item, null));
}
objectsToExcel.Add(newObject);
}
return exportService.ExportToExcel(objectsToExcel);
}
Jetzt erstellen wir ein Objekt mit einigen Eigenschaften von ExcelItem, aber völlig dynamisch. Anstatt alle 6 Eigenschaften zu verwenden, verwenden wir nur 3 davon, diejenige, die wir nur senden möchten.
Nehmen wir nun an, dass dieses ExcelItem-Objekt 200 Eigenschaften hat, verrückt, aber es könnte passieren.
Das Einzige, was ich tun muss, ist, die Eigenschaften, die ich in der Excel-Datei rendern möchte, in die Tabelle ColumnExcelItem einzufügen, und das war’s.
Fälle
In diesem Fall haben wir nur einen einzigen Fall verwendet, aber nehmen wir an, Sie möchten für einige Benutzer unterschiedliche Berichte haben. Nehmen wir an, dass die Rolle admin alle Eigenschaften erhalten soll, dann würden Sie so etwas wie diese Struktur in der Datenbank erstellen
ReportingTemplates
Id PropertyName
---------------------
1 Admin
2 Users
ReportingTemplateItems
Id TemplateId PropertyName
---------------------
1 1 Id
2 1 Name
3 1 Surname
4 1 Age
5 1 CreatedAt
6 1 UpdatedAt
7 2 Name
8 2 Surname
9 2 Age
Wenn Sie dann die Vorlage abrufen, können Sie verschiedene Spalten haben, alle dynamisch und ohne Codebezug.
Wenn wir einen Benutzer mit der Rolle Admins haben, enthält die Datei die Spalten Id, Name, Surname, Age, CreatedAt, UpdatedAt.
Wenn Sie es mit der Rolle Users generieren, hat es Name, Age. Surname.
Fazit
Dies ist eine coole Art zu lernen, wie dynamic in .NET funktioniert, und es ist eine wirklich gute Lösung, wenn Sie unterschiedliche Rollen oder Vorlagen für verschiedene Benutzer benötigen und nicht wirklich daran interessiert sind, Zeit in die Hardcodierung zu investieren.