I have two classes that look a bit like this:
public class Order
{
public string Reference { get; set; }
//some more string, int, date fields excluded for brevity
public HashSet<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public int LineNum { get; set; }
public int Qty { get; set; }
public string ItemName { get; set; }
}
I have been asked to export the a list of orders into an Excel spreadsheet for people to view, at the click of a button in a Windows Application. I have decided that I should do this by creating a datatable with columns names of (in the example above) Reference
,LineNum
,Qty
,ItemName
, which I can store in memory and display when requested.
To do this, I decided to write a method to add column names for all the simple properties of a class, and to iterate into the type of item in all collection properties to do the same for each item in that collection, repeating the parent properties for each row. Thus the output will look something like this:
Reference LineNum Qty ItemName //In this case, there were two Orders, the first
0001 1 12 Foo //with order.OrderLines.Count == 2, the second
0001 2 23 Bar //with order.OrderLines.Count == 1.
0002 1 34 Foo
I have (relying heavily on other StackOverflow answers!) written this code:
public static class Class_Shredder
{
public static DataTable Flatten<T>(List<T> masterClass)
{
var dt = AddColumnsForProperties(masterClass[0]);
// some more code here
return dt;
}
public static DataTable AddColumnsForProperties<T>(T classObject)
{
DataTable dt = new DataTable(typeof(T).Name);
AddColumnsForProperties(classObject, dt);
return dt;
}
public static DataTable AddColumnsForProperties<T>(T classObject, DataTable dt)
{
foreach (var property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.PropertyType.IsImplementationOfGenericType(typeof(ICollection<>)))
{
//if we have a collection property, we list the properties of one object in the collection, not the name of the collection itself
//PROBLEM HERE
AddColumnsForProperties(property.PropertyType.GetGenericArguments().Single(), dt);
}
else
{
dt.Columns.Add(property.Name);
}
}
return dt;
}
}
NB. the GetGenericArguments() and IsImplementationOfGenericType are listed at the bottom of this question.
When I call the Flatten
method on a 'List', it starts off by working well, returning the properties of the order, recognising when it hits a collection. The problem is that when it reaches the HashSet<OrderLine>
property, it doesn't seem to call AddColumnsForProperties
with an OrderLine - the properties it finds are MemberType
, DeclaringType
and more... I think that means it has received an object
, but I'm not sure...
So... what I would like to know is how do I call the method in the line marked with ERROR HERE
above with an object of type T
, given that it is dealing with a HashSet<T>
(or other collection) property type?
Answers that helped me get this far:
getting item type for a generic list
check whether T inherits or implements an interface
How to know if a property is a collection
Exporting an EF Model to Excel (C# Corner)
For Reference:
public static bool IsGenericTypeOf(this Type type, Type genericTypeDefinition)=> type.IsGenericType && type.GetGenericTypeDefinition() == genericTypeDefinition;
public static bool IsImplementationOfGenericType(this Type type, Type genericTypeDefinition)
{
if (!genericTypeDefinition.IsGenericTypeDefinition)
return false;
// looking for generic interface implementations
if (genericTypeDefinition.IsInterface)
{
foreach (Type i in type.GetInterfaces())
{
if (i.Name == genericTypeDefinition.Name && i.IsGenericTypeOf(genericTypeDefinition))
return true;
}
return false;
}
// looking for generic [base] types
for (Type t = type; type != null; type = type.BaseType)
{
if (t.Name == genericTypeDefinition.Name && t.IsGenericTypeOf(genericTypeDefinition))
return true;
}
return false;
}
Aucun commentaire:
Enregistrer un commentaire