2

I have a DataGrid and I need to convert it to a DataTable. The problem is that I need To be able to get the type of my DataSource dynamically. The grid DataSource is of type 'Observable' which is a class I have in my project, but my task is to be able to dynamically create the DataTable without needing to specify the type only using the DataSource. How can I generate a method that I can use to place in <T> without getting the error " 'mytype' is a variable but is used like a type" .

Type mytype = Grid_Job.DataSource.GetType();

DataTable dt = CreateDataTable<mytype>((IEnumerable<mytype>)Grid_Job.DataSource);

public static DataTable CreateDataTable<T>(IEnumerable<T> list)
        {
            Type type = typeof(T);
            var properties = type.GetProperties();

            DataTable dataTable = new DataTable();
            foreach (PropertyInfo info in properties)
            {
                dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
            }

            foreach (T entity in list)
            {
                object[] values = new object[properties.Length];
                for (int i = 0; i < properties.Length; i++)
                {
                    values[i] = properties[i].GetValue(entity);
                }

                dataTable.Rows.Add(values);
            }

            return dataTable;
        }

My Observable class is

public class Observable 
{
    public int JobNo { get; set; }
    public string JobName { get; set; }
    public string JobDescription { get; set; }
    public string Job_Type { get; set; }
    public string Job_Status { get; set; }
}
Nour Mawla
  • 23
  • 3
  • Can you show your `Observable` class. Does it implement `IEnumerable'? – Jasper Kent Nov 05 '20 at 08:26
  • yes when I use IEnumerable the code works just fine and Type mytype = Grid_Job.DataSource.GetType(); returns "Observable" correctly but I can't use it in the code. – Nour Mawla Nov 05 '20 at 08:30
  • What are the *actual* types that might be in the `DataSource` here? it matters, because there are a range of ways of doing this, and a few particularly relevant abstractions that need to be used in some cases. So: if you did `Grid_Job.DataSource.GetType()`: what is it? – Marc Gravell Nov 05 '20 at 08:32
  • so I used string s = mytype.ToString(); and string s returned literally "System.Collections.ObjectModel.ObservableCollection' 1 [ERP_UI.Job.Observable]" where ERP_UI is the name of my project and Job is the folder containing the class Observable – Nour Mawla Nov 05 '20 at 08:39
  • and myType.Name returns "Observable" – Nour Mawla Nov 05 '20 at 08:40
  • k, that type doesn't implement any of the abstractions I had in mind, so that means you need to look for the `T` via reflection; there are some options here: https://stackoverflow.com/questions/1043755/c-sharp-generic-list-t-how-to-get-the-type-of-t – Marc Gravell Nov 05 '20 at 08:48
  • again same problem with the code provided in the link...I get the type in a variable but I can't insert it to the list CreateDataTable((IEnumerable)Grid_Job.DataSource); I can't use the type I get to generate a list of this type.... – Nour Mawla Nov 05 '20 at 09:11
  • @NourMawla agreed; then *just don't* - your method doesn't need to be generic - posting an answer that shows this – Marc Gravell Nov 05 '20 at 10:01
  • Note that in `CreateDataTable` you are not using generic type `T` for anything useful. You can just make method non-generic and accept `Type type` argument instead. – Evk Nov 05 '20 at 10:03

1 Answers1

0

Effectively, you need to discard the generics and think in terms of the collection at runtime - discovering the element type itself. For example:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Reflection;

static class P
{
    static object GetData()
        => new ObservableCollection<Observable>
        {
            new Observable { JobName = "abc", JobNo = 123 },
            new Observable { JobName = "def", JobNo = 456 },
            new Observable { JobName = "ghi", JobNo = 789 },
        };
    static void Main()
    {
        // note that we don't know *anything* about the data source here
        object dataSource = GetData();
        DataTable dt = CreateDataTable((IEnumerable)dataSource);

        foreach (DataColumn col in dt.Columns)
        {
            Console.Write(col.ColumnName);
            Console.Write("\t");
        }
        Console.WriteLine();
        foreach (DataRow row in dt.Rows)
        {
            foreach (DataColumn col in dt.Columns)
            {
                Console.Write(row[col]);
                Console.Write("\t");
            }
            Console.WriteLine();
        }
    }

    public static DataTable CreateDataTable(IEnumerable list)
    {
        Type type = GetElementType(list.GetType());
        var properties = type.GetProperties();

        DataTable dataTable = new DataTable();
        foreach (PropertyInfo info in properties)
        {
            dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
        }

        object[] values = new object[properties.Length];
        foreach (object entity in list)
        {   
            for (int i = 0; i < properties.Length; i++)
            {
                values[i] = properties[i].GetValue(entity);
            }
            dataTable.Rows.Add(values);
        }
        return dataTable;
    }
    static Type GetElementType(Type type)
    {
        foreach (Type interfaceType in type.GetInterfaces())
        {
            if (interfaceType.IsGenericType &&
                interfaceType.GetGenericTypeDefinition()
                == typeof(IList<>))
            {
                return  type.GetGenericArguments()[0];
            }
        }
        return null;
    }
}
public class Observable
{
    public int JobNo { get; set; }
    public string JobName { get; set; }
    public string JobDescription { get; set; }
    public string Job_Type { get; set; }
    public string Job_Status { get; set; }
}

Note that in some cases you should prefer the TypeDescriptor model over reflection, as this allows runtime definition of properties; this is a niche area and probably doesn't apply to you, but it relevant to know about. As an example, this is how DataTable itself chooses to expose the properties it has as discoverable at runtime to general purpose tools. There are also a few list indirection APIs that need to be considered in those cases.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900