分享

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

 _明心见性_ 2017-11-14
今天做了一个测试,用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转实体类的代码:



你所谓的 getdatatable 是什么呢?完全看不出来其实质,所以无法判断。

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

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

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

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

除非万不得已,否则我们可以直接写代码
C# code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    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();
    }

也就是说直接用比较明确的、不含反射的方式,产生强类型的对象集合就好了。上面少了 conn.Open()语句,请自己加上。


对于最后这几行代码,许多人还会设计一个 SqlHelper 类库来简化它。例如可以在 SqlHelper 中有这样的方法
C# code?
1
2
3
4
5
6
7
8
9
10
11
public static List<T> GetList<T>(string connectionString, string sql, Func<IDataReader, T> Converter, params Tuple<stringobject>[] 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();
    }
}

或者跨(不同类型)数据库的通用方法
C# code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public static List<T> GetList<T>(DbProviderFactory factory, string sql, Func<IDataReader, T> Converter, params Tuple<stringobject>[] 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();
        }
    }


总之,不反射。

 
如何写 SQLHelper 是另外一个话题了,我的重点是在与你玩儿“反射”上。在你的这个测试中,哪里是测试 DataTable 和 DataReader.Read 呢?

由于巨大的循环反射具有严重的性能问题,那么原本要比较的性能差距就应该约等于0了(不重要了)。优于你并没有给出实际的测试结果数值,我不知道你的比较结果(时间差)是相当于一根火柴那么值钱、还是相当于一根眼睫毛那么值钱。但是总的来说,DataTable或者 DataReade.Read 的差距基本上可以忽略,而是要从更大的原则来优化自己日常写的代码。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约