在C#中将DataTable转换为通用列表

27

免责声明:我知道这个问题已经在 Stack Overflow 上被问了很多次。
我的问题略有不同。

编程语言:C# 3.5

我有一个名为 cardsTable 的 DataTable,它从数据库中提取数据,并且我有一个 Cards 类,它只有一些属性(没有构造函数)。

public class Cards
{
    public Int64 CardID { get; set; }
    public string CardName { get; set; }
    public Int64 ProjectID { get; set; }
    public Double CardWidth { get; set; }
    public Double CardHeight { get; set; }
    public string Orientation { get; set; }
    public string BackgroundImage { get; set; }
    public string Background { get; set; }
}

我想将cardsTable数据插入到一个List类型的对象中。
我的数据中会有空字段,因此当我转换数据时,方法不应出错。下面的方法是最好的方法吗?

DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().ToList().ConvertAll(x => new Cards { CardID = (Int64)x.ItemArray[0] });

这个问题要关闭吗?还是那是一个错误? :) - Jeff Mercado
@marc garvell:我也想操作数据表中的数据... - naveen
1
你确实有一个构造函数...默认的那个。 - Tomas Jansson
10个回答

34

你实际上可以将其大大缩短。您可以将 Select() 扩展方法视为一种类型转换器。然后,可以将转换写成这样:

List<Cards> target = dt.AsEnumerable()
    .Select(row => new Cards
    {
        // assuming column 0's type is Nullable<long>
        CardID = row.Field<long?>(0).GetValueOrDefault(),
        CardName = String.IsNullOrEmpty(row.Field<string>(1))
            ? "not found"
            : row.Field<string>(1),
    }).ToList();

@Jeff:关于列的类型不可为空,例如 CardName=row.Field<string>(0),你怎么看? - naveen
1
我提到它被大幅缩短了。表面上看起来并没有太大区别。我的意思是可以更高效地编写它。你原来的方法迭代了两次表的长度,从而创建了两个不同的列表实例。这个方法只需要一次遍历就可以完成转换。只是让你知道一下。 - Jeff Mercado
1
@naveen:字符串肯定是可空的,它们是引用类型,而不是可空值类型。你有什么特别想要做的吗? - Jeff Mercado
@jeff:我想捕获null或空值,并传递字符串“未找到”。 - naveen
@jeff:CardName = row.Field<string>(1) ? row.Field<string>(1) : "".. 这样可以吗? - naveen
1
@naveen:我会添加一个案例来演示如何处理它。 - Jeff Mercado

15
我认为如果使用一些惯例和反射,所有解决方案都可以得到改进并使方法更加通用。比如说,您在数据表中将列命名为对象属性的相同名称,然后您可以编写一些代码来查看对象的所有属性,然后查找数据表中的列以映射值。
我做了相反的事情,也就是从IList到数据表,我编写的代码可以在以下链接中查看:http://blog.tomasjansson.com/convert-datatable-to-generic-list-extension/ 转换回去应该不难,而且重载函数也不难,这样您就可以提供要包含或排除哪些属性的信息。
编辑: 因此,使其工作的代码如下:
public static class DataTableExtensions
{
    private static Dictionary<Type,IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();
    public static IList<PropertyInfo> GetPropertiesForType<T>()
    {
        var type = typeof(T);
        if(!typeDictionary.ContainsKey(typeof(T)))
        {
            typeDictionary.Add(type, type.GetProperties().ToList());
        }
        return typeDictionary[type];
    }

    public static IList<T> ToList<T>(this DataTable table) where T : new()
    {
        IList<PropertyInfo> properties = GetPropertiesForType<T>();
        IList<T> result = new List<T>();

        foreach (var row in table.Rows)
        {
            var item = CreateItemFromRow<T>((DataRow)row, properties);
            result.Add(item);
        }

        return result;
    }

    private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
    {
        T item = new T();
        foreach (var property in properties)
        {
            property.SetValue(item, row[property.Name], null);
        }
        return item;
    }

}

如果你有一个DataTable,只需编写yourTable.ToList<YourType>(),它就会为你创建列表。如果你有更复杂的类型和嵌套对象,你需要更新代码。一个建议是重载ToList方法,接受一个params string[] excludeProperties,其中包含不应该被映射的所有属性。当然,在CreateItemForRow方法的foreach循环中,你可以添加一些空值检查。 更新: 添加了静态字典来存储反射操作的结果,使其稍微快一点。我还没有编译代码,但应该可以工作 :)。

CreateItemFromRow 方法中,您应该检查 if (row.Table.Columns.Contains(property.Name)),然后发出 property.SetValue(..。这将确保如果数据行上没有属性名称,则不会抛出任何异常。 - user20358
无法将类型为“System.Int64”的对象转换为类型“System.Nullable`1[System.Int32]”。 - Toolkit

9

稍作简化,我不使用ItemArray:

List<Person> list = tbl.AsEnumerable().Select(x => new Person
                    {
                        Id = (Int32) (x["Id"]),
                        Name = (string) (x["Name"] ?? ""),
                        LastName = (string) (x["LastName"] ?? "")
                    }).ToList();

谢谢伙计。它帮了我很大的忙。 - Chandan Kumar

6
.ToList() 的位置不正确,如果某些字段可以为 null,则必须处理这些字段,因为它们无法转换为 Int64。
DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().Select(
  x => new Cards { CardID = (Int64)(x.ItemArray[0] ?? 0) }).ToList();

1
ConvertAll() 是一个返回 List<T> 的方法。它不是扩展方法,也没有在 IEnumerable<T> 中明确定义。在 IEnumerable<T> 中最接近的类似方法是 Select() - Jeff Mercado

3

以下是一种在C#中使用Where条件进行通用列表转换的简单方法

List<Filter> filter = ds.Tables[0].AsEnumerable()
                        .Where(x => x.Field<int>("FilterID") == 5)
                        .Select(row => new Filter
                        {
                            FilterID = row.Field<int>("FilterID"),
                            FilterName = row.Field<string>("FilterName")
                        }).ToList();

首先定义属性并按照其使用。
public class Filter
{
    public int FilterID { get; set; }
    public string FilterName { get; set; }
}

放置包:
using System.Linq;
using System.Collections.Generic;

2

这是一个简单的解决方案。

但它取决于您是否知道数据库中的数据都是有效的,并且不会包含任何会破坏上述内容的东西。

例如,当您不希望为空的字段时 - 可能是由于生成数据的sql中的左连接。

因此,如果您已经验证了数据,那么是的 - 我本来想建议一些linq - 但您已经掌握了这个技巧。

然而,如果您需要进行某些验证,那么您应该只需遍历数据行,按照上面的方法生成对象并将其添加到集合中...这也将允许您处理一个行中的错误并仍然处理其余部分。

这是我看待问题的方式。

(该段落无关紧要)


1
你可以使用下面这个泛型类将数据表映射到模型类。 泛型类
 public static class DataTableMappingtoModel
    {
        /// <summary>
        /// Maps Data Table values to coresponded model propertise
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static List<T> MappingToEntity<T>(this DataTable dt) 
        {
            try
            {
                var lst = new List<T>();
                var tClass = typeof (T);
                PropertyInfo[] proInModel = tClass.GetProperties();
                List<DataColumn> proInDataColumns = dt.Columns.Cast<DataColumn>().ToList();
                T cn;
                foreach (DataRow item in dt.Rows)
                {
                    cn = (T) Activator.CreateInstance(tClass);
                    foreach (var pc in proInModel)
                    {


                            var d = proInDataColumns.Find(c => string.Equals(c.ColumnName.ToLower().Trim(), pc.Name.ToLower().Trim(), StringComparison.CurrentCultureIgnoreCase));
                            if (d != null)
                                pc.SetValue(cn, item[pc.Name], null);


                    }
                    lst.Add(cn);
                }
                return lst;
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

模型类

public class Item
{
    public string ItemCode { get; set; }
    public string Cost { get; set; }
    public override string ToString()
    {
        return "ItemCode : " + ItemCode + ", Cost : " + Cost;
    }
}

创建数据表格
public DataTable getTable()
{
    DataTable dt = new DataTable();
    dt.Columns.Add(new DataColumn("ItemCode", typeof(string)));
    dt.Columns.Add(new DataColumn("Cost", typeof(string)));
    DataRow dr;
    for (int i = 0; i < 10; i++)
    {
        dr = dt.NewRow();
        dr[0] = "ItemCode" + (i + 1);
        dr[1] = "Cost" + (i + 1);
        dt.Rows.Add(dr);
    }
    return dt;
}

现在我们可以像下面这样将这个 DataTable 转换为 List:
DataTable dt = getTable();
List<Item> lst = dt.ToCollection<Item>();
foreach (Item cn in lst)
{
    Response.Write(cn.ToString() + "<BR/>");
}

希望能帮到您。

类型为'System.Int64'的对象无法转换为类型'System.Nullable`1[System.Int32]'。 似乎不支持可空类型。 - Toolkit

1
这是将DataTable转换为通用类型列表的最快无循环解决方案。
public static List<T> ConvertDataTable<T>(DataTable SourceData, Func<DataRow, T> RowConverter)
{
    List<T> list = new List<T>();
    if (SourceData == null || SourceData.Rows.Count < 1)
        return list;
    IEnumerable<T> enumerable = SourceData.AsEnumerable().Select(RowConverter);
    if (enumerable == null)
        return list;
    return new List<T>(enumerable);
}

这是该函数的实现。
public static List<T> ExecuteListOfObject<T>(DataTable SourceData)
{
    return ConvertDataTable(SourceData, ConvertRecord<T>);
}
public static T ConvertRecord<T>(DataRow drData)
{
    if (drData == null || drData[0] == DBNull.Value)
        return default(T);
    return (T)drData[0];
}

0

我在Tomas Jansson的逻辑基础上添加了一个"忽略"属性。这样我就可以在加载DataTable-To-Class自身时向正在加载的类中添加其他属性而不会导致程序崩溃。

另外,我也考虑添加一个单独的参数,用于保存从DataTable读取的实际列名。在这种情况下,你将使用"row[attribute.Name]"而不是"row[property.Name]",或者类似于此属性名称的内容。

public static class DataTableExtensions
{
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
    public sealed class IgnoreAttribute : Attribute { public IgnoreAttribute() { } }

    private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();

    public static IList<PropertyInfo> GetPropertiesForType<T>()
    {
        var type = typeof(T);

        if (!typeDictionary.ContainsKey(typeof(T)))
            typeDictionary.Add(type, type.GetProperties().ToList());

        return typeDictionary[type];
    }

    public static IList<T> ToList<T>(this DataTable table) where T : new()
    {
        IList<PropertyInfo> properties = GetPropertiesForType<T>();
        IList<T> result = new List<T>();

        foreach (var row in table.Rows)
            result.Add(CreateItemFromRow<T>((DataRow)row, properties));

        return result;
    }

    private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
    {
        T item = new T();

        foreach (var property in properties)
        {
            // Only load those attributes NOT tagged with the Ignore Attribute
            var atr = property.GetCustomAttribute(typeof(IgnoreAttribute));
            if (atr == null)
                property.SetValue(item, row[property.Name], null);
        }

        return item;
    }
}

0

虽然来晚了,但这可能很有用。可以使用以下方式调用:

table.Map(); 或者使用 Func 过滤值进行调用。

您甚至可以通过在属性上设置属性来更改类型属性和 DataColumn 标头之间的映射名称。

[AttributeUsage(AttributeTargets.Property)]
    public class SimppleMapperAttribute: Attribute
    {
        public string HeaderName { get; set; }
    }


     public static class SimpleMapper
{
    #region properties
    public static bool UseDeferredExecution { get; set; } = true;
    #endregion

#region public_interface  


    public static IEnumerable<T> MapWhere<T>(this DataTable table, Func<T, bool> sortExpression) where T:new()
    {
        var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties())).Where((t)=>sortExpression(t));
        return UseDeferredExecution ? result : result.ToArray();
    }
    public static IEnumerable<T> Map<T>(this DataTable table) where T : new()
    {
        var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties()));
        return UseDeferredExecution ? result : result.ToArray();
    }
    #endregion

#region implementation_details
    private static T ConvertRow<T>(DataRow row, DataColumnCollection columns, System.Reflection.PropertyInfo[] p_info) where T : new()
    {
        var instance = new T();
        foreach (var info in p_info)
        {
            if (columns.Contains(GetMappingName(info))) SetProperty(row, instance, info);             
        }
        return instance;
    }

    private static void SetProperty<T>(DataRow row, T instance, System.Reflection.PropertyInfo info) where T : new()
    {
        string mp_name = GetMappingName(info);
        object value = row[mp_name];
        info.SetValue(instance, value);
    }

    private static string GetMappingName(System.Reflection.PropertyInfo info)
    {
        SimppleMapperAttribute attribute = info.GetCustomAttributes(typeof(SimppleMapperAttribute),true).Select((o) => o as SimppleMapperAttribute).FirstOrDefault();
        return attribute == null ? info.Name : attribute.HeaderName;
    }
#endregion
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接