关于DataTable和DataReader转换数据的效率问题

萌狼爱爪爪 2016-03-03 05:58:02
今天做了一个测试,用DataTable获取数据然后转Json和实体类,以及用DataReader获取数据然后转Json和实体类,出现了下面的一个情况:
(1)从数据库获取的数据转换成实体类时,用DataTable比用DataReader转实体快
(2)从数据库获取的数据转换成JSON字符串时,用DataReader比用DataTable转JSON字符串快

从DataReader和DataTable的性质看,
DataReader是一条一条读取,再转JSON的时候直接转换,
而DataTable是读取所有数据然后缓存在内存,在转JSON格式的时候在遍历一遍去转,
所以才会出现上面的(2)这种情况,但是为什么又会出现(1)这种情况呢,求大神帮忙指点下

测试环境:
(1)相同的电脑
(2)测试数据是10万条到100万条数据
(3)测试了上百组数据基本都是上面的结果,不存在偶然性

下面附上转实体的时候的代码:

图1是DataReader转实体类的代码:


图2是DataTable转实体类的代码:
...全文
799 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
如何写 SQLHelper 是另外一个话题了,我的重点是在与你玩儿“反射”上。在你的这个测试中,哪里是测试 DataTable 和 DataReader.Read 呢? 由于巨大的循环反射具有严重的性能问题,那么原本要比较的性能差距就应该约等于0了(不重要了)。优于你并没有给出实际的测试结果数值,我不知道你的比较结果(时间差)是相当于一根火柴那么值钱、还是相当于一根眼睫毛那么值钱。但是总的来说,DataTable或者 DataReade.Read 的差距基本上可以忽略,而是要从更大的原则来优化自己日常写的代码。
  • 打赏
  • 举报
回复
上面少了 conn.Open()语句,请自己加上。 对于最后这几行代码,许多人还会设计一个 SqlHelper 类库来简化它。例如可以在 SqlHelper 中有这样的方法
public static List<T> GetList<T>(string connectionString, string sql, Func<IDataReader, T> Converter, params Tuple<string, object>[] parameters)
{
    using (var conn = new SqlConnection(connectionString))
    {
        conn.Open();
        var cmd = new SqlCommand(sql, conn);
        foreach (var param in parameters)
            cmd.Parameters.AddWithValue(param.Item1, param.Item2);
        return (from IDataReader rd in cmd.ExecuteReader() select Converter(rd)).ToList();
    }
}
或者跨(不同类型)数据库的通用方法
    public static List<T> GetList<T>(DbProviderFactory factory, string sql, Func<IDataReader, T> Converter, params Tuple<string, object>[] parameters)
    {
        using (var conn = factory.CreateConnection())
        {
            conn.Open();
            var cmd = conn.CreateCommand();
            cmd.CommandText = sql;
            cmd.Connection = conn;
            foreach (var param in parameters)
            {
                var p = cmd.CreateParameter();
                p.ParameterName = param.Item1,
                p.Value = param.Item2;
                cmd.Parameters.Add(p);
            }
            return (from IDataReader rd in cmd.ExecuteReader() select Converter(rd)).ToList();
        }
    }
总之,不反射。
  • 打赏
  • 举报
回复
你所谓的 getdatatable 是什么呢?完全看不出来其实质,所以无法判断。 而且从你贴出的代码,也无法判断你是在哪一个代码范畴去统计时间的,以及如何(用什么具体代码)去统计时间的。 对于任何.net程序来说,第一次加载一个Assembly时都可能有很长的加载时间。第一次访问Assembly跟以后(第二次以后)访问时间相比,可能会相差20倍以上时间。因此任何测试都要考虑这点,你放在前边首先测试的代码,其第一组数据应该删除掉。或者说,把最低、最高值都应该删除掉。 如过你所谓的 GetDataTable 中使用到了 DataTable 标配的 DbDataAdapter,如果你看看其源代码就会发现,它正是一行一行地调用 DbDataReader 来填充 DataTable的。因此如果你发现人家直接生成的 DataTable 比你写的 DbDataReader.Read 方式还快,那么你应该检查一下自己的代码。具体来说,本来 DataTable 比 DataReader.Read 慢,但是你从 DataReader读取字段的方式、又慢于从 DataRow 读取字段的方式。 最后,但是其实是最关键的,我非常反对滥用反射。不仅仅是你的 GetProperties() 方法该不该循环许多次的问题,也不仅仅是 Property.SetValue(....) 方法应该先缓存为 delegate 然后直接调用委托(而不是一次次地去从 Property 反射调用 SetValue 方法)的问题,我是直接反对滥用反射的! 除非万不得已,否则我们可以直接写代码
    List<ResultType> result;
    using (var conn = new SqlConnection(SqlHelper.ConnectionString))
    {
        var cmd = new SqlCommand("select * from [table] where dv>=@dv and name<>@name", conn);
        cmd.Parameters.AddWithValue("dv", 12);
        cmd.Parameters.AddWithValue("name", "xxx");
        result = (from IDataRecord rd in cmd.ExecuteReader()
                    select new ResultType
                    {
                        Name = (string)rd["Name"],
                        Cost = (decimal)rd["Cost"]
                    })
                    .ToList();
    }
也就是说直接用比较明确的、不含反射的方式,产生强类型的对象集合就好了。
萌狼爱爪爪 2016-03-04
  • 打赏
  • 举报
回复
 //第一次测试数据作废
            object x = Console.ReadLine();
            int y = Convert.ToInt32(x == "" ? 0 : x);
            Stopwatch swT = new Stopwatch();
            swT.Start();
            List<TestTable> listT = DbHelper.GetDataToList<TestTable>("SELECT * FROM test_table LIMiT 0, " + y);
            swT.Stop();
            TimeSpan tsT = swT.Elapsed;
            Console.WriteLine("SuperRocky算法总共消耗{0}ms.", tsT.TotalMilliseconds);


            while (true)
            {
                object a = Console.ReadLine();
                object b = Console.ReadLine();
                double sum = 0;

                if (2 == Convert.ToInt32(a == "" ? 0 : a))
                {
                    
                }
                int c = Convert.ToInt32(a == "" ? 0 : a);
                int d = Convert.ToInt32(b == "" ? 0 : b);
                TimeSpan ts;
                for (int i = 0; i < d; i++)
                {
                    Stopwatch sw = new Stopwatch();

                    sw.Start();
                    //string strJson = DbHelper.GetDataToJson("SELECT * FROM test_table LIMiT 0, " + b);
                    List<TestTable> list = DbHelper.GetDataToList2<TestTable>("SELECT * FROM test_table LIMiT 0, " + c);
                    sw.Stop();
                    ts = sw.Elapsed;
                    Console.WriteLine("SuperRocky算法总共消耗{0}ms.", ts.TotalMilliseconds);
                    sum += ts.TotalMilliseconds;
                }
                Console.WriteLine("SuperRocky算法执行总共执行{0}次,平均消耗{1}ms", d, sum / d);
            }
关于测试方法我重新弄了一个,这个应该比较准确一点
萌狼爱爪爪 2016-03-04
  • 打赏
  • 举报
回复
引用 8 楼 sp1234 的回复:
如何写 SQLHelper 是另外一个话题了,我的重点是在与你玩儿“反射”上。在你的这个测试中,哪里是测试 DataTable 和 DataReader.Read 呢? 由于巨大的循环反射具有严重的性能问题,那么原本要比较的性能差距就应该约等于0了(不重要了)。优于你并没有给出实际的测试结果数值,我不知道你的比较结果(时间差)是相当于一根火柴那么值钱、还是相当于一根眼睫毛那么值钱。但是总的来说,DataTable或者 DataReade.Read 的差距基本上可以忽略,而是要从更大的原则来优化自己日常写的代码。
你说的这个还没了解过,刚刚搜了一篇优化反射的文章http://www.cnblogs.com/fish-li/archive/2013/02/24/2924673.html,不过目前看的一头雾水,方不方便加你为好友,以后有不懂的地方可以请教下前辈
萌狼爱爪爪 2016-03-04
  • 打赏
  • 举报
回复
引用 6 楼 sp1234 的回复:
你所谓的 getdatatable 是什么呢?完全看不出来其实质,所以无法判断。

而且从你贴出的代码,也无法判断你是在哪一个代码范畴去统计时间的,以及如何(用什么具体代码)去统计时间的。

对于任何.net程序来说,第一次加载一个Assembly时都可能有很长的加载时间。第一次访问Assembly跟以后(第二次以后)访问时间相比,可能会相差20倍以上时间。因此任何测试都要考虑这点,你放在前边首先测试的代码,其第一组数据应该删除掉。或者说,把最低、最高值都应该删除掉。

如过你所谓的 GetDataTable 中使用到了 DataTable 标配的 DbDataAdapter,如果你看看其源代码就会发现,它正是一行一行地调用 DbDataReader 来填充 DataTable的。因此如果你发现人家直接生成的 DataTable 比你写的 DbDataReader.Read 方式还快,那么你应该检查一下自己的代码。具体来说,本来 DataTable 比 DataReader.Read 慢,但是你从 DataReader读取字段的方式、又慢于从 DataRow 读取字段的方式。

最后,但是其实是最关键的,我非常反对滥用反射。不仅仅是你的 GetProperties() 方法该不该循环许多次的问题,也不仅仅是 Property.SetValue(....) 方法应该先缓存为 delegate 然后直接调用委托(而不是一次次地去从 Property 反射调用 SetValue 方法)的问题,我是直接反对滥用反射的!

除非万不得已,否则我们可以直接写代码
    List<ResultType> result;
using (var conn = new SqlConnection(SqlHelper.ConnectionString))
{
var cmd = new SqlCommand("select * from [table] where dv>=@dv and name<>@name", conn);
cmd.Parameters.AddWithValue("dv", 12);
cmd.Parameters.AddWithValue("name", "xxx");
result = (from IDataRecord rd in cmd.ExecuteReader()
select new ResultType
{
Name = (string)rd["Name"],
Cost = (decimal)rd["Cost"]
})
.ToList();
}

也就是说直接用比较明确的、不含反射的方式,产生强类型的对象集合就好了。


你好,下面这块是GetDataTable 的方法:


这个是关于测试的代码:


另外我测试的时候已经排除掉第一个数据了。
puler 2016-03-03
  • 打赏
  • 举报
回复
一般都是先将数据全读取出来,再进行转换
qbilbo 2016-03-03
  • 打赏
  • 举报
回复
如果可能的话,把dr[p.Name]换成dr[int]这样来试试呢?
萌狼爱爪爪 2016-03-03
  • 打赏
  • 举报
回复
引用 2 楼 qbilbo 的回复:
从你举的例子来说,应该总是用DataReader 快,之所以第一个慢,我估计是因为你把GetProperties放到了循环中。
你好,你确实说对了其中一点,在循环里面new对象会减慢速度,但是两个案例都是放在循环里面的,我把两个案例中的GetProperties都放在循环外面,两个程序都变快了,不过还是会出现我前面说的第一种情况。
qbilbo 2016-03-03
  • 打赏
  • 举报
回复
从你举的例子来说,应该总是用DataReader 快,之所以第一个慢,我估计是因为你把GetProperties放到了循环中。
萌狼爱爪爪 2016-03-03
  • 打赏
  • 举报
回复
先感谢各位大神了

111,083

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧