0

I have an IEnumerable of Clients and the fields in the class Client are:

    public string Name { get; set; }
    public int Age { get; set; }
    public Town Hometown { get; set; }

And the fields in the class Hometown are:

    public string TownName { get; set; }
    public double Population { get; set; }
    public double Mortality{ get; set; }

The goal is to generate a generic method that can receive as input an IEnumerable and generate a DataTable with the primitive types of the particular class. In this example, it would be the following columns: Name, Age, TownName, Population and Morality.

I tried the below code:

    public DataTable TransformIEnumerableToDataTable<T>(IEnumerable<T> IEnumerableTable)
    {
        var props = typeof(T).GetProperties();

        foreach (PropertyInfo prop in props)
        {
            if (!prop.PropertyType.IsPrimitive || prop.PropertyType != typeof(Decimal) || prop.PropertyType != typeof(String))
            {
                // Here I can't move forward, I can't pass such argumento to the function typeof, so I don't know what do should be done
                var prop2 = typeof(prop.PropertyType);
            }
        }

        var dt = new DataTable();
        dt.Columns.AddRange(
          props.Select(p => new DataColumn(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType)).ToArray()
        );

        IEnumerableTable.ToList().ForEach(
          i => dt.Rows.Add(props.Select(p => p.GetValue(i, null)).ToArray())
        );

        return dt;
    }

The output, without the foreach statement, gives me a DataTable with the following columns: Name, Age and Hometown. However, I expect the output to have the primitive data types of hometown and not the hometown object itself.

asa
  • 675
  • 2
  • 7
  • 17
  • 1
    To get the primitive types inside the `Hometown` object instance, you'll have to learn all about [reflection](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection). – Jim Mischel Jun 28 '19 at 23:48
  • `Age` as a property is a bad idea - you never know when the value is correct or when it needs to be updated. – Ňɏssa Pøngjǣrdenlarp Jun 29 '19 at 03:34
  • So you're trying to reflect down the object model? How deep do you go? How do you stop? Why not just pass in a query that flattens the result? – Enigmativity Jun 29 '19 at 06:06
  • Yes Ňɏssa Pøngjǣrdenlarp, I know we shouldn't create Age as a property, I put it in this example for the sake of simplicity, as properties aren't main question. – asa Jun 29 '19 at 07:59
  • It's everything in the question Enigmativity. Answering here: (1) As deep as the primitive data types, (2) Stop by recognizing the primitive data types, I don't know how to recognise it, because of that I posted the question, (3) I don't wanna pass in a query because the models in the real solution have too many properties and I would have to maintain those always when some properties are changed. It'd be cumbersome – asa Jun 29 '19 at 08:05
  • @JimMischel It would be great if you could share some code – asa Jun 29 '19 at 10:12
  • Possible duplicate: https://stackoverflow.com/questions/20554103/recursively-get-properties-child-properties-of-a-class – Jim Mischel Jun 30 '19 at 03:48

1 Answers1

0

Here is a set of extension methods based on my FlattenToExpando and FlattenToAnonymous methods that will flatten an IEnumerable<T> to a DataTable. Note that collection types are not flattened, and classes are only flattened one level deep.

With these, you can implement your method as

public DataTable TransformIEnumerableToDataTable<T>(IEnumerable<T> IEnumerableTable) => IEnumerableTable.FlattenToDataTable();

The extension methods:

public static class DataTableExt {
    // convert a class containing members that are composite into a flattened DataTable
    // only flatten one level deep; do not flatten collections
    public static DataTable FlattenToDataTable<T>(this IEnumerable<T> src) {
        var oldMITs = typeof(T).GetMITs();
        var ans = new DataTable(nameof(T));

        foreach (var mit in oldMITs)
            if (mit.IsSimpleOrEnumerable)
                ans.Columns.Add(new DataColumn(mit.mi.Name, mit.mt));

        foreach (var mit in oldMITs)
            if (!mit.IsSimpleOrEnumerable)
                foreach (var subMI in mit.subMIs) {
                    // find unique name
                    var possibleName = subMI.Name;
                    var namect = 1;
                    while (ans.Columns.Contains(possibleName)) {
                        possibleName = $"{subMI.Name}{namect}";
                        ++namect;
                    }
                    ans.Columns.Add(new DataColumn(possibleName, subMI.GetMemberType()));
                }

        foreach (var srcObj in src) {
            var dr = ans.NewRow();
            srcObj.CopyFlattenedToDataRow(oldMITs, dr);
            ans.Rows.Add(dr);
        }

        return ans;
    }

    class MemberInfoType {
        public MemberInfo mi;
        public Type mt;
        public bool IsSimpleOrEnumerable;
        public List<MemberInfo> subMIs;
    }

    static List<MemberInfoType> GetMITs(this Type t) =>
        t.GetPropertiesOrFields().Select(mi => new { mi = mi, mt = mi.GetMemberType() })
        .Select(mit => new { mit, IsSimpleOrEnumerable = mit.mt.IsSimple() || mit.mt.IsIEnumerable() })
        .Select(mitb => new MemberInfoType { mi = mitb.mit.mi, mt = mitb.mit.mt, IsSimpleOrEnumerable = mitb.IsSimpleOrEnumerable, subMIs = mitb.IsSimpleOrEnumerable ? null : mitb.mit.mt.GetPropertiesOrFields() })
        .ToList();

    static void CopyFlattenedToDataRow<T>(this T srcObject, List<MemberInfoType> oldMITs, DataRow ansRow) {
        var colIdx = 0;

        foreach (var mit in oldMITs)
            if (mit.IsSimpleOrEnumerable)
                ansRow[colIdx++] = mit.mi.GetValue(srcObject);

        foreach (var mit in oldMITs)
            if (!mit.IsSimpleOrEnumerable) {
                var subObj = mit.mi.GetValue(srcObject);
                foreach (var subMI in mit.subMIs)
                    ansRow[colIdx++] = subMI.GetValue(subObj);
            }
    }

    // ***
    // *** Type Extensions
    // ***
    public static List<MemberInfo> GetPropertiesOrFields(this Type t, BindingFlags bf = BindingFlags.Public | BindingFlags.Instance) =>
        t.GetMembers(bf).Where(mi => mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property).ToList();

    // ***
    // *** MemberInfo Extensions
    // ***
    public static Type GetMemberType(this MemberInfo member) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.FieldType;
            case PropertyInfo mpi:
                return mpi.PropertyType;
            case EventInfo mei:
                return mei.EventHandlerType;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
        }
    }

    public static object GetValue(this MemberInfo member, object srcObject) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.GetValue(srcObject);
            case PropertyInfo mpi:
                return mpi.GetValue(srcObject);
            case MethodInfo mi:
                return mi.Invoke(srcObject, null);
            default:
                throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or MethodInfo", nameof(member));
        }
    }
    public static T GetValue<T>(this MemberInfo member, object srcObject) => (T)member.GetValue(srcObject);

    public static void SetValue(this MemberInfo member, object destObject, object value) {
        switch (member) {
            case FieldInfo mfi:
                mfi.SetValue(destObject, value);
                break;
            case PropertyInfo mpi:
                mpi.SetValue(destObject, value);
                break;
            case MethodInfo mi:
                mi.Invoke(destObject, new object[] { value });
                break;
            default:
                throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or MethodInfo", nameof(member));
        }
    }
    public static void SetValue<T>(this MemberInfo member, object destObject, T value) => member.SetValue(destObject, (object)value);

    // ***
    // *** Type Extensions
    // ***
    public static bool IsNullableType(this Type aType) =>
    // instantiated generic type only                
        aType.IsGenericType &&
        !aType.IsGenericTypeDefinition &&
        Object.ReferenceEquals(aType.GetGenericTypeDefinition(), typeof(Nullable<>));

    // Stack Overflow
    public static bool IsSimple(this Type type) =>
        type.IsNullableType() ? type.GetGenericArguments()[0].IsSimple()
                              : type.IsPrimitive ||
                                type.IsEnum ||
                                type.Equals(typeof(string)) ||
                                type.Equals(typeof(decimal)) ||
                                type.Equals(typeof(TimeSpan)) ||
                                type.Equals(typeof(DateTime));
    public static bool IsIEnumerable(this Type type) => typeof(IEnumerable).IsAssignableFrom(type);
}
NetMage
  • 26,163
  • 3
  • 34
  • 55