使用 ExpandoObject 生成动态对象

· 2 分钟阅读

我一直需要将具有自己定义的列的 Excel 文件转换为具有动态列的新文件,并且我对如何在不出现严重性能问题的情况下正确执行此操作感到有点困惑。

我提出了一个教程,您可以在此处找到它,该教程使用.NET ExpandoObject,它几乎可以让您创建一个对象并添加动态成员。

扩展对象

微软的定义是:

表示一个对象,其成员可以在运行时动态添加和删除。

它有一些注释:

ExpandoObject 类使您能够在运行时添加和删除其实例的成员,以及设置和获取这些成员的值。此类支持动态绑定,这使您能够使用标准语法(如sampleObject.sampleMember),而不是更复杂的语法(如sampleObject.GetAttribute(“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 对象,我们创建一个 ColumnExcelItem 表,该表将用于生成 ExcelItem

数据库结构

我们将此定义保存在数据库中,如下所示:

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 表中,仅此而已。

案例

在本例中,我们只使用了一个案例,但假设您希望为某些用户提供不同的报告。假设 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 角色生成它,它将具有 NameAgeSurname

结论

这是学习 dynamic 在 .NET 中如何工作的一种很酷的方法,当您需要为不同的用户提供不同的角色或模板,并且对投入时间硬编码内容不感兴趣时,这确实是一个很好的解决方案。