将DataTable转换为通用列表的最快方法

6
我有一个数据层选择方法,返回一个datatable。这个方法被业务层方法调用,应该返回一个强类型的通用列表。
我想要做的与这个问题非常相似(但不完全相同):How do you convert a DataTable into a generic list? 不同的是,我希望列表包含强类型对象而不是数据行(并且我这里还没有可用的linq)。
我担心性能问题。业务层方法将进一步从表示层调用,并迭代结果以显示给用户。在业务层添加额外的迭代,然后立即为显示再次迭代,这似乎非常浪费,因此我希望这尽可能快。
这是一个常见的任务,所以我真的正在寻找一个可以重复使用的好模式。
6个回答

9
上面示例的问题在于它非常缓慢。我的DataTable大约有400行,这个转换需要5到6秒钟的时间!
这似乎是一个相当普遍的任务,所以我很惊讶没有看到有人提供更有效率的解决方案。
* 更新!! * 为了好玩,我尝试使用LINQ而不是迭代DataTable并添加到我的列表中进行转换。下面是我所做的:
   List<MyObject> returnList = new List<MyObject>();

   MyDataTable dtMyData = new MyTableAdapter().GetMyData();

   returnLists = (from l in dtMyData
                 select new MyObject
                 {
                    Active = l.IsActive,
                    Email = l.Email,
                    //...
                    //About 40 more properties
                    //...
                    ZipCode = l.Zipcode
                  }).ToList();

第一种方法(逐行迭代)花费了5.3秒,而使用LINQ的方法只花费了1.8秒!

在查询中使用已键入的列名而不是通过数字或基于字符串的索引访问,这样做更加规范化、专业化、优雅和流畅。 - Harindaka
@shakthi,你遇到的问题是什么? - John

7
您是否了解DataTable的结构和类型化对象?如果您事先知道,可以使用委托进行映射。如果不知道(即只知道Type和属性),则有加速动态成员访问的方法(例如HyperDescriptor)。
无论哪种方式,都应考虑使用迭代器块;这样,您就不必将对象缓冲整个第二次;当然,如果您只处理较小的行数,则这不是问题。
您能澄清其中任何一点吗?我可以添加更多详细信息...
最简单的是什么?
DataTable table = new DataTable {
    Columns = {
        {"Foo", typeof(int)},
        {"Bar", typeof(string)}
     }
};
for (int i = 0; i < 5000; i++) {
    table.Rows.Add(i, "Row " + i);
}

List<MyType> data = new List<MyType>(table.Rows.Count);
foreach (DataRow row in table.Rows) {
    data.Add(new MyType((int)row[0], (string)row[1]));
}

在上述问题中可能会引导正确的方法……

没有什么问题:它只是看起来非常笨重,好像应该有更好的方法。 - Joel Coehoorn
@Joel - 好的,你可以使用 Converter<DataRow,MyType>,但是如果不知道我们要解决什么问题... - Marc Gravell

1
我知道这个答案来得很晚,但我花了一小时以上的时间准备这段代码,因为我自己需要用到它,然后我想到可能有人在寻找这个问题的解决方案时会用到它。
为了使我的答案正常工作,您需要将列表属性的名称与数据库表列(字段)或DataTable列名称相同。
解决方案:
public List<dynamic> GetListFromDT(Type className, DataTable dataTable)
        {
            List<dynamic> list = new List<dynamic>();
            foreach (DataRow row in dataTable.Rows)
            {
                object objClass = Activator.CreateInstance(className);
                Type type = objClass.GetType();
                foreach (DataColumn column in row.Table.Columns)
                {
                    PropertyInfo prop = type.GetProperty(column.ColumnName);
                    prop.SetValue(objClass, row[column.ColumnName], null);   
                }
                list.Add(objClass);
            }
            return list;
        }

如何使用?

假设您有一个填充了值的数据表,名称为dtPersons。

DataTable dtPersons; //populate this datatable

假设你有一个具有以下属性的类。
public class Persons{
    public string Name {get; set;}
    public int Age {get; set;}
}

现在将该方法打包并放入您的模型中。 按照以下方式调用该方法。
List<dynamic> dynamicListReturned = GetListFromDT(typeof(Persons), dataTable);
List<Persons> listPersons = dynamicListReturned.Cast<Persons>().ToList();

现在你已经从datatable中获取到了你的列表,存储在listPersons中。

记住:类属性和DataTable/数据库中的名称应该相同。


0
你需要遍历数据行并在某个时候将它们转换为对象,但是你可以编写一个自定义集合,并编写一个构造函数,该构造函数接受一组数据行,并在请求时将每个项目转换为您的对象类型,而不是一次性地转换所有项。
比如说你定义了一个实现IList的自定义集合。它可以有两个内部字段-对数据行集合的引用和一个List。List的长度将初始化为数据行集合的长度,但值为null。
现在,在索引器中,您可以检查List是否包含该索引处的值,如果没有,则可以使用任何有用的方式创建对象并将其存储在那里,然后再返回它。
这将延迟创建对象直到请求它们的时候,并且只会创建被请求的对象。
您的对象可能需要一个以DataRow为参数的构造函数,或者您需要一些工厂来创建它们,但这是另一个话题。

0

为了在简单的控制台应用程序中提供更多用户友好性,以下是对Mark答案的补充:

class Program
{
    static void Main(string[] args)
    {
        //define a DataTable obj
        DataTable table = new DataTable
        {
            Columns = {
            {"Foo", typeof(int)},
            {"Bar", typeof(string)}
         }
        };
        //populate it the DataTable 
        for (int i = 0; i < 3; i++)
        {
            table.Rows.Add(i, "Row " + i);
        }

        List<MyType> listWithTypedObjects= new List<MyType>(table.Rows.Count);
        foreach (DataRow row in table.Rows)
        {
            listWithTypedObjects.Add(new MyType((int)row[0], (string)row[1]));
        }

        Console.WriteLine(" PRINTING THE POPULATED LIST ");
        foreach (MyType objMyType in listWithTypedObjects)
        {
            Console.Write(" I have object of the type " + objMyType.ToString() + " => " );
            Console.Write(" with Prop1OfTypeInt " + objMyType.Prop1OfTypeInt.ToString() + " , ");
            Console.WriteLine(" with Prop1OfTypeInt " + objMyType.Prop2OfTypeString.ToString() + "  "); 
        }

        Console.WriteLine(" \n \n \n HIT A KEY TO EXIT THE PROGRAM ");
        Console.ReadKey();
    }
}

class MyType {

    public int Prop1OfTypeInt { get; set; }
    public string Prop2OfTypeString { get; set; } 

    /// <summary>
    /// Note the order of the passed parameters is important !!!
    /// </summary>
    public MyType( int prop1OfTypeInt , string prop2OfTypeString)
    {
        this.Prop1OfTypeInt = prop1OfTypeInt;
        this.Prop2OfTypeString = prop2OfTypeString; 

    }
}

-5
public partial class issuereceive_manageroffice_bal
{
    public int issue_id{get;set;}
    public string process{get;set;}
    public DateTime issue_date{get;set;}
    public TimeSpan issue_time{get;set;}
    public string eg_no{get;set;}
    public string lotno{get;set;}
    public string clarity{get;set;}
    public string sieves{get;set;}
    public string shape{get;set;}
    public double issue_carat{get;set;}
    public int issue_pieces{get;set;}
    public int receive_pieces{get;set;}
    public double receive_carat{get;set;}
    public int kp_pieces{get;set;}
    public decimal kp_carat{get;set;}
    public double loss{get;set;}
    public string issue_manager{get;set;}
    public string issue_by{get;set;}
    public string receive_by{get;set;}
    public int status{get;set;}
    public DateTime receive_date{get;set;}
    public string receive_time{get;set;}
    public int factory_id{get;set;}

}


List<issuereceive_manageroffice_bal> issue_receive_list = new List<issuereceive_manageroffice_bal>();
issue_receive_list =
      (from DataRow dr in DataTable.Rows
      select new issuereceive_manageroffice_bal()
           {
               issue_id = 0,
               issue_time = TimeSpan.Parse("0"),
               receive_time = null,
               shape = null,
               process = dr["process"].ToString(),
               issue_date = Convert.ToDateTime(dr["issue_date"]),
               eg_no = dr["eg_no"].ToString(),
               lotno = dr["lotno"].ToString(),
               clarity = dr["clarity"].ToString(),
               sieves = dr["sieves"].ToString(),
               issue_carat = dr["issue_carat"].ToString() != "" ? double.Parse(dr["issue_carat"].ToString()) : 0,
               issue_pieces = dr["issue_pieces"].ToString() != "" ? int.Parse(dr["issue_pieces"].ToString()) : 0,
               receive_carat = dr["receive_carat"].ToString() != "" ? double.Parse(dr["receive_carat"].ToString()) : 0,
               kp_pieces = dr["kp_pieces"].ToString() != "" ? int.Parse(dr["kp_pieces"].ToString()) : 0,
               kp_carat = dr["kp_carat"].ToString() != "" ? decimal.Parse(dr["kp_carat"].ToString()) : 0,
               loss = dr["loss"].ToString() != "" ? double.Parse(dr["loss"].ToString()) : 0,
               issue_manager = dr["lotno"].ToString(),
               issue_by = dr["issue_by"].ToString(),
               receive_by = dr["receive_by"].ToString(),
               status = dr["status"].ToString() != "" ? int.Parse(dr["status"].ToString()) : 0,
               receive_date = Convert.ToDateTime(dr["receive_date"]),
               factory_id = dr["factory_id"].ToString() != "" ? int.Parse(dr["factory_id"].ToString()) : 0,

           }).ToList();

2
请问您能否对答案进行任何澄清? - Opal
将以下与编程有关的内容从英语翻译成中文。只返回翻译文本:不要复制-粘贴代码,简单说明您所做的内容即可。 - ger

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