ExpandoObject を使用した動的オブジェクト生成

· 2分で読める

独自に定義された列を含む Excel ファイルを、動的列を含む新しいファイルに変換する必要がありましたが、重大なパフォーマンスの問題を発生させずに適切に変換する方法について少し混乱しました。

私は、.NET ExpandoObject を使用するチュートリアルを思いつきました。ここ では、オブジェクトを作成して動的メンバーを追加できます。

ExpandoObject

Microsoft の定義は次のとおりです。

実行時にメンバーを動的に追加および削除できるオブジェクトを表します。

そして、いくつかのコメントがあります。

ExpandoObject クラスを使用すると、実行時にインスタンスのメンバーを追加および削除したり、これらのメンバーの値を設定および取得したりすることができます。このクラスは動的バインディングをサポートしているため、sampleObject.GetAttribute(“sampleMember”) のような複雑な構文の代わりに、sampleObject.sampleMember のような標準構文を使用できます。

現在の動作

ExcelExportService がありますが、これは List<T> (この場合は ExcelItem) を渡すことによって、 Reflection を使用して xlsx ファイルを作成します。

これまでのところ、コードは次のようになります。

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 は、Excel ファイルの生成に使用されるすべてのプロパティを含むオブジェクトです。

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);
}

このアプローチは完全に機能し、xlsx ファイルは各列が各プロパティとして問題なく生成されます。たとえば、ファイルに保存するには、この GetExcelBytes メソッドを次のように使用します。

File.WriteAllBytes("path-to-somewhere/my-file.xlsx", GetExcelBytes());

問題

ここで行っていることは ExcelItem オブジェクトのすべてのプロパティを使用することであるため、実際に必要なのはすべてを使用するのではなく、そのうちの 2 ~ 3 個だけを使用することだけです。

また、コードを変更したくないという要件もあります。すべては administrator によって行われ、どの列を表示するかを決定するのですが、彼はコード内のプロパティを知っているかどうかはわかりません。

TLDR: すべては動的である必要があります。プロパティを持つオブジェクトがあり、生成されたファイルにはそのオブジェクトの N プロパティがあることを確認する必要がありますが、明らかにハードコーディングされていません。

動的列

列のリストを生成するためにオブジェクトのどのプロパティを使用するかを設定したいため、変更は dynamic オブジェクトを使用することになります。

たとえば、大量 のような多くのプロパティを持つオブジェクトがあり、変更を加えるたびに ExcelItem オブジェクトを変更するのはあまり望ましくなく、その ExcelItem の生成に使用される ColumnExcelItem テーブルを作成します。

データベース構造

この定義を次のようにしてデータベースに保存します。

Id    PropertyName
---------------------
1     Name
2     Surname
3     Age

ExcelItem オブジェクトの既存のプロパティよりも値が少なく、6 プロパティがあり、データベースには 3 のみがリストされていることに注意してください。

PropertyName は、動的割り当てに使用するオブジェクトのプロパティ名と一致する必要があることにも注意してください。

オブジェクトの構築

データベース テーブルを作成したので、それを取得してコード内で使用できるようにするためのリポジトリを構築する必要があります。この部分のチュートリアルは作成しません。新しい GetExcelBytes() 関数の最初のところでスキップします。

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);
}

ここで、ExcelItem のいくつかのプロパティを持つオブジェクトを作成しますが、完全に動的です。 6 つのプロパティをすべて使用するのではなく、送信したいプロパティのうち 3 つだけを使用します。

ここで、この ExcelItem オブジェクトに 200 個のプロパティがあると仮定しましょう。クレイジーですが、それは起こり得ることです。

私がしなければならない唯一のことは、Excel ファイルにレンダリングしたいプロパティを ColumnExcelItem テーブルに挿入することだけです。

この例では 1 つのケースを使用しましたが、一部のユーザーに対して異なるレポートを作成したいとします。 admin ロールがすべてのプロパティを取得する必要があるとします。その場合、データベースに次のような構造を作成します。

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

次に、テンプレートを取得することで、コードに関係なく、すべて動的にさまざまな列を作成できます。

ロール Admins を持つユーザーがいる場合、ファイルには列 IdNameSurnameAgeCreatedAtUpdatedAt の列が含まれます。

Users ロールを使用して生成した場合、NameAge が含まれます。 Surname

結論

これは、.NET で dynamic がどのように機能するかを学ぶための優れた方法であり、ユーザーごとに異なるロールやテンプレートが必要で、ハードコーディングに時間を費やすことにあまり興味がない場合に、非常に優れたソリューションです。