ExpandoObject를 사용한 동적 개체 생성
자체적으로 정의된 열이 있는 Excel 파일을 동적 열이 있는 새 파일로 변환해야 했는데 심각한 성능 문제 없이 이를 올바르게 수행하는 방법이 다소 혼란스러웠습니다.
저는 .NET ExpandoObject를 사용하는 여기를 찾을 수 있는 튜토리얼을 찾았습니다. 이를 통해 객체를 생성하고 동적 구성원을 추가할 수 있습니다.
ExpandoObject
Microsoft 정의는 다음과 같습니다.
런타임 시 동적으로 멤버를 추가하고 제거할 수 있는 개체를 나타냅니다.
그리고 여기에는 몇 가지 설명이 있습니다.
ExpandoObject 클래스를 사용하면 런타임에 해당 인스턴스의 멤버를 추가 및 삭제할 수 있으며 이러한 멤버의 값을 설정하고 가져올 수도 있습니다. 이 클래스는 동적 바인딩을 지원하므로 SampleObject.GetAttribute(“sampleMember”)와 같은 더 복잡한 구문 대신 SampleObject.sampleMember와 같은 표준 구문을 사용할 수 있습니다.
현재 동작
List<T>를 전달하여 이 경우에는 ExcelItem를 전달하여 Reflection를 사용하여 xlsx 파일을 생성하는 ExcelExportService가 있습니다.
지금까지 우리의 코드는 다음과 같습니다:
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 객체를 사용하는 것입니다. 왜냐하면 열 목록을 생성하는 데 사용할 객체의 속성을 설정하려고 하기 때문입니다.
1톤과 같은 많은 속성을 가진 객체가 있고 변경할 때마다 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 역할을 가진 사용자가 있는 경우 파일에는 Id, Name, Surname, Age, CreatedAt, UpdatedAt 열이 포함됩니다.
Users 역할을 사용하여 생성하는 경우 Name, Age를 갖게 됩니다. Surname.
결론
이는 .NET에서 dynamic가 어떻게 작동하는지 배울 수 있는 멋진 방법이며, 다양한 사용자를 위해 다양한 역할이나 템플릿이 필요하고 하드코딩에 시간을 투자하는 데 별로 관심이 없을 때 정말 좋은 솔루션입니다.