【问题标题】:Convert DataTable to Generic List in C#在 C# 中将 DataTable 转换为通用列表
【发布时间】:2010-11-05 08:47:13
【问题描述】:

免责声明:我知道它在 SO 的很多地方都被问到过。
我的查询有点不同。

编码语言:C# 3.5

我有一个名为cardsTable 的数据表,它从数据库中提取数据,我有一个类 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] });

【问题讨论】:

  • 这个问题是要关闭还是一个错误? :)
  • @marc garvell:我也想操作数据表数据...
  • 你确实有一个构造函数......默认的。

标签: c# linq generics lambda datatable


【解决方案1】:

您实际上可以大大缩短它。您可以将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(0)
  • 我提到它被大大缩短了。从表面上看,它看起来并没有太大的不同。我的意思是它可以写​​得更有效率。您的原始方法创建了两个不同的列表实例,迭代表的长度两次。这种方法一次性执行转换。让您知道。
  • @naveen:字符串当然可以为空,它们是引用类型,而不是可以为空的值类型。你有什么特别想做的吗?
  • @jeff:我想捕获 null 或空并传递字符串“未找到”
  • @naveen:我将添加一个案例来演示如何处理它。
【解决方案2】:

如果您使用一些约定和反射,我认为所有解决方案都可以改进并使方法更通用。假设您将数据表中的列命名为与对象中的属性相同的名称,那么您可以编写一些查看对象所有属性的内容,然后在数据表中查找该列以映射值。

我反其道而行之,就是……从IList到datatable,我写的代码可以看: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&lt;YourType&gt;(),它会为你创建列表。如果您有更复杂的嵌套对象类型,则需要更新代码。一个建议是重载ToList 方法以接受params string[] excludeProperties,其中包含所有不应映射的属性。当然你可以在CreateItemForRow方法的foreach循环中添加一些null检查。

更新:添加了静态字典来存储反射操作的结果,使其更快一点。我还没有编译代码,但它应该可以工作:)。

【讨论】:

  • CreateItemFromRow 中,您应该检查if (row.Table.Columns.Contains(property.Name)),然后发出property.SetValue(..。这将确保如果属性名称不在数据行上,则不会引发异常。
  • “System.Int64”类型的对象无法转换为“System.Nullable`1[System.Int32]”类型。)”
【解决方案3】:

只是一点点的简化。我不使用 ItemArray:

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

【讨论】:

  • 谢谢哥们。它帮助了我。
【解决方案4】:

.ToList() 位置错误,如果某些字段可能为 null,您将不得不处理这些字段,因为如果它们为 null,它们将不会转换为 Int64

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

【讨论】:

  • ConvertAll() 作为List&lt;T&gt; 方法。它不是扩展方法,IEnumerable&lt;T&gt; 也没有明确定义它。 IEnumerable&lt;T&gt; 中最接近的类似物是 Select()
【解决方案5】:

嗯,它的单线解决方案

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

例如,当您不期望它时出现一个可为空的字段 - 可能是由于生成数据的左连接 int eh sql。

所以,如果你在那之前验证了数据,是的 - 我本来打算建议一些 linq - 但你搞砸了。

如果您需要一些验证,但是您可能应该只循环遍历数据行,如上生成您的对象并将其添加到集合中......这也将允许您在一行中处理错误并继续处理其余部分。

反正我是这么看的

(该死的我投了反对票,所以我的代表是 1024)

【讨论】:

    【解决方案6】:

    您可以使用下面的通用类将数据表映射到模型类。

    泛型类

     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]”类型。似乎不适用于可空类型
    【解决方案7】:

    我在 Tomas Jansson 的逻辑之上构建了一个“忽略”属性。这允许我向正在加载的类添加其他属性,而不会破坏 DataTable-To-Class 加载本身。

    另外,我还考虑添加一个单独的参数来保存要从 DataTable 中读取的实际列名。在这种情况下,不要使用“row[property.Name]”,而是使用 row[attribute.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;
        }
    }
    

    【讨论】:

      【解决方案8】:

      来晚了,但这可能很有用。可以调用:

      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
      }
      

      【讨论】:

        【解决方案9】:

        这是在 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;
        

        【讨论】:

          【解决方案10】:

          这是将 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];
          }
          

          【讨论】:

            猜你喜欢
            • 2010-10-07
            • 2016-12-16
            • 1970-01-01
            • 2018-11-20
            • 1970-01-01
            • 2015-05-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多