闲来无事搬出以前一个一直没时间弄的ORM框框,折腾了半天,结果却令人大跌眼镜。使用正常,新增10000条记录用了8秒左右(还好),读取到List<Entity>用了1秒左右(也还行),遍历集合里的10000 条记录并保存居然花了300秒还不止(天啊,这不可能吧),最后是删除10000条记录还是300秒左右(郁闷了)。
怎么会呢?我使用的方法也还好吧。为了能针对不同的数据库,利用DbProviderFactory创建DbConnection、DbCommand;根据CustomAttribute定义映射;利用反射将Entity的数据映射到DbCommand的Parameter上;最后执行ExecuteNonQuery。流程没有错,结果也是正确的,可为什么那么慢呢?难道是根据CustomAttribute生成CommandText和填充Parameter的速度慢?于是不执行ExecuteNonQuery,仅进行10000次InsertCommand,UpdateCommand,DeleteCommand的生成,速度很快啊。看来问题不在这里。难道是SQL语句执行效率的问题。看看我生成的Update语句,很简单“where id = @pId and version = @pVersion”。病急乱投医了,加上索引,速度还是慢。-_-b,咋回事呢?
静下心来思考了一下。以前用的方法是将实体的数据映射到DataTable,利用DbDataAdapter来完成保存操作。DataAdapter我仅提供了SelectCommand,然后就用DbCommandBuilder,很简单,效率也不错。后来觉得,既然通过Entity了,为什么我要经过DataTable来浪费资源呢?所以改为直接利用DbCommand来搞定了。对比一下二者的UpdateCommandText吧,DbCommandBuilder那个乐观并发生成的SQL语句比我的复杂多了。可它比我快!搬出SQL事件探查器,发现用DbCommmandBuilder生成的SQL语句一下刷下去一片,偶的那个可怜的一条条往下长。但这样还是没能看出个所以然来。于是狠下心,把条件改为“where id = ‘xxxx’ and version = 0”,速度那个贼快啊。可是这样写不就变得不那么通用了吗?(如:Oracle的日期是:to_date,但SQL server直接’’就好了)。再做一个尝试,将DbParameter改为SqlParameter,也不快。难道还有什么玄机?设置DbType,没有任何作用。设置了SqlDbType,终于变快了。最后得出的结论是对于SQL Server2000(其他的没有试),如果没有为SqlParameter设置SqlDbType就慢得要死。如果其他数据库也是这样,那我不是也得设置XXDbType=XX?
看来原因已经找到,但是该如何解决呢?不同数据提供程序的XXDbType似乎没有什么规律可循。要是我用硬编码写上XXType=XX?不实际,这么写了更不通用了。既然用DbDataAdapter时速度很快,那DbCommandBuilder里总该有个什么解决的方法吧。通过Reflector分析源代码,终于发现了关键的地方。首先DbCommandBuilder利用DataReader.GetSchemaTable获取元数据,然后在CreateParameterForValue中利用ApplyParameterInfo设置参数的属性。而ApplyParameterInfo是在派生类中实现的。
现在就好办了。我也依样画葫芦,用DbProviderFactory创建一个DbCommandBuilder,用反射调用ApplyParameterInfo,果然一切顺利啊。修改过之后,初次运行10000条记录新增耗时7秒左右,读取耗时2秒左右,更新耗时3秒左右,删除耗时2秒左右,第二次还会再快一些。呵呵,心情不错。
但是,仍然有一个原因不明白,为什么设置了SqlDbType后速度会差这么多呢?新增的时候,没有设置SqlDbType和设置后的速度不相上下,但Update时却天差地别。革命尚未成功啊。
补充说明下:
可能题目定的不是很好,主要的意思是想和大家分享一下我的这个经验,以及我发现并解决这个问题的过程。因为此前我有在baidu、google上搜索过, 但是没有发现什么有用的东西。至于如何应用,那就得根据实际情况了。而且,框架这种东西每个人都会有自己的实现方法,我也就没有把具体的代码放出来,只是 提供DbCommandBuilder中具体调用ApplyParameterInfo设置参数的属性的地方,希望帮大家省点事。
写得比较仓促,希望观众见谅