إنشاء كائن ديناميكي باستخدام ExpandoObject
لقد كنت في حاجة إلى تحويل ملفات 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، ولكنه ديناميكي تمامًا. بدلاً من استخدام جميع الخصائص الستة، نستخدم فقط ثلاثة منها، وهي الخاصية التي نريد إرسالها فقط.
الآن لنفترض أن هذا الكائن 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.
الخلاصة
هذه طريقة رائعة لتعلم كيفية عمل dynamic في .NET، وهي حل جيد حقًا عندما تحتاج إلى أدوار أو قوالب مختلفة لمستخدمين مختلفين، ولا تهتم حقًا باستثمار الوقت في أشياء البرمجة الثابتة.