简体   繁体   English

在转换列表中<t>从 class 到 DataTable 添加 select 属性</t>

[英]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.我试图只从 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.问题是当它到达第二个属性时,它会抛出一个错误,指出列“unincludedCol”不属于表。

How do I get around this?我该如何解决这个问题?

If you need just type, id, name, and city , you can add them to a list of string:如果您只需要type、id、name 和 city ,您可以将它们添加到字符串列表中:

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

Or extract them from dataTable like:或者从dataTable中提取它们,例如:

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:并在循环中检查columnNames是否包含prop.Name ,例如:

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.Contains(prop.Name)) ,您可以使用 .Any .Any()Equals方法来忽略大小写:

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)数据 Model(标记要序列化的属性)

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'.在您的通用方法中,您似乎依赖于属性“type”、“id”、“name”和“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:保证class <T> 将具有这四个属性,请考虑在 ToDataTable 方法中约束 T ,如下所示:

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

... where IMyConstraint is declared as... ...其中IMyConstraint被声明为...

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:这使您的通用方法对于任何实现 IMyConstraint 的 class <T> 都是安全的:

class SomeClass : IMyConstraint


If (as your code implies) the four properties you want in your DataTable are known, you can still iterate the names:如果(正如您的代码所暗示的那样)您想要在 DataTable 中使用的四个属性是已知的,您仍然可以迭代名称:

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: ...或者可能更清洁,只需迭代 IMyConstraints 接口:

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.从 GitHub克隆或下载此工作示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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