简体   繁体   中英

In converting List<T> to DataTable add select properties from class

I am trying to convert a List to a DataTable. I have the basic outline done, but am stuck on one part. I am trying to only get a few of the properties form the class.

public static DataTable ToDataTable<T>(this IList<T> data)
{
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
    DataTable table = new DataTable();

    table.Columns.Add("type", typeof(string));
    table.Columns.Add("id", typeof(Int32));
    table.Columns.Add("name", typeof(string));
    table.Columns.Add("city", typeof(string));

    foreach (T item in data)
    {
        DataRow row = table.NewRow();
        foreach (PropertyDescriptor prop in properties)
        {
            row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
        }
        table.Rows.Add(row);
    }
    return table;
}

The problem is when it gets to the second property it throws an error saying column 'unincludedCol' does not belong to table.

How do I get around this?

If you need just type, id, name, and city , you can add them to a list of string:

List<string> columnNames = new List<string>
{
  "type","id","name","city"
};

Or extract them from dataTable like:

List<string> columnNames = new List<string>();

foreach(DataColumn column in dataTable.Columns)
{
    columnNames.Add(column.ColumnName);
}

And in the loop check if columnNames contains prop.Name , like:

foreach (T item in data)
{
    DataRow row = table.NewRow();
    foreach (PropertyDescriptor prop in properties)
    {
        if(!columnNames.Contains(prop.Name))
            continue;

        row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
    }

    table.Rows.Add(row);
}

Note: instead if(.columnNames.Contains(prop.Name)) , you can use .Any() and Equals methods to ignore case:

if (!columnNames.Any(c => c.Equals(prop.Name, StringComparison.OrdinalIgnoreCase)))
    continue;

I hope you find this helpful.

I believe using a an attribute is attribute will be more generic. Also Lets you assign property name aliases.

Attribute

[AttributeUsage(AttributeTargets.Property)]
public class UseAsTableColumn : Attribute
{
    public string Alias { get; private set; }
    public UseAsTableColumn(string columnAlias = null)
    {
        this.Alias = columnAlias;
    }
}

A helper - Altered your method (Using Property name as the Column Name)

public static class Helper
{
    public static string GetDisplayName(PropertyInfo pi)
    {
        var uatAttrib = pi.GetCustomAttribute(typeof(UseAsTableColumn), true) as UseAsTableColumn;
        return uatAttrib.Alias == null ? pi.Name : uatAttrib.Alias;
    }
    public static DataTable ToDataTable<T>(this IList<T> data)
    {
        var mappableAttrirbs = typeof(T).GetProperties().Where(x => x.GetCustomAttributes(typeof(UseAsTableColumn), true).FirstOrDefault() != null);
        DataTable table = new DataTable();

        foreach (var property in mappableAttrirbs)
        {
            table.Columns.Add(GetDisplayName(property), property.PropertyType);
        }

        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (var property in mappableAttrirbs)
            {
                row[GetDisplayName(property)] = property.GetValue(item) ?? DBNull.Value;
            }
            table.Rows.Add(row);
        }
        return table;
    }
}

Data Model (Mark Properties you want to serialize)

public class DataModel
{
    [UseAsTableColumn]
    public string Type { get; set; }

    [UseAsTableColumn]
    public int Id { get; set; }

    [UseAsTableColumn("name")]
    public string Name { get; set; }

    [UseAsTableColumn]
    public string City { get; set; }

    public string _InternalValue { get; private set; }
}

Runner

public class Program
{
    static void Main(string[] args)
    {
        List<DataModel> data = new List<DataModel>()
        {
            new DataModel() { Type="Magic", Id= 1, Name = "foo", City = "bar" },
            new DataModel() { Type="Magic", Id= 2, Name = "foo1", City = "bar1" }
        };


        Helper.ToDataTable(data);
        Console.ReadKey();
    }
}

May I suggest an additional fine point to an already-good accepted answer?

In your generic method, you seem to be relying on the existence of the properties 'type', 'id', 'name' and 'city'. Such things should not be left to chance.

To guarantee that the class <T> will have those four properties, consider constraining T in your ToDataTable method like this:

public static DataTable ToDataTable<T>(this IList<T> data) where T : IMyConstraint
{}

... where IMyConstraint is declared as...

interface IMyConstraint
{
    string type { get; }
    int id { get; }
    string name { get; }
    string city { get; }
}

This makes your generic method safe for any class <T> that implements IMyConstraint:

class SomeClass : IMyConstraint


If (as your code implies) the four properties you want in your DataTable are known, you can still iterate the names:

private static DataTable ToDataTable<T>(IEnumerable<T> data) where T : IMyConstraint
{
    DataTable table = new DataTable();
    Type type = typeof(T);
    // "If" you already know the names of the properties you want...
    string[] names = new string[] { "type", "id", "name", "city" };

    foreach (var name in names)     // Add columns 
    {
        table.Columns.Add(name, type.GetProperty(name).PropertyType);
    }            
    foreach (var item in data)      // Add rows 
    {
        object[] values =
            names
            .Select(name => type.GetProperty(name).GetValue(item))
            .ToArray();
        table.Rows.Add(values);
    }
    return table;
}

... or possibly cleaner-still, just iterate the IMyConstraints interface instead:

private static DataTable ToDataTableAlt<T>(IEnumerable<T> data) where T : IMyConstraint
{
    DataTable table = new DataTable();
    PropertyInfo[] propertyInfos = typeof(IMyConstraint).GetProperties();

    foreach (var propertyInfo in propertyInfos)     // Add columns 
    {
        table.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
    }
    foreach (var item in data)      // Add rows 
    {
        object[] values =
            propertyInfos
            .Select(propertyInfo => propertyInfo.GetValue(item))
            .ToArray();
        table.Rows.Add(values);
    }
    return table;
}

Clone or Download this working example from GitHub.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM