Dapper是一款非常方便的轻量级的ORM工具,
这里放一个它的文档:
Dapper帮助文档
它拓展了IConnection接口的方法,使其能够查询Model的List,不需要提前的Mapper设置,也支持多种写入参数的方式
与其他ORM框架相比,确实很方便.
但在实际的使用中,当数据量很大时,它的执行效率比原生的ADO.NET低了很多倍.
有需求要把大量的数据查出然后导入ExceL中,
开始是使用的Dapper进行List查询,大概在执行上花了2分钟左右(只是需要的数据的一部分)
后来改做DataTable查询,执行的时间并没有快多少,
最后换了原生的ADO.NET的方式进行查询,
在时间上直接进入10秒以内
这种差距感到很惊讶,故准备进Dapper的源码进行调试
顺便测试一下
在三行不同的查询上打上断点
List<POCO> selList = _SqlDapper.QueryList<POCO>(SQL,null);//这个QueryList是自行封装的,故不显示其内容
DataTable Test = _SqlDapper.QueryDataTable(SQL,null);
DataTable dt = SQLCommand.ExecuteDataTable(SQL);
(在工具---->设置—>Debuger里把Just My Code给关闭,不然Debug不会进去,至于它会要求链接或下载源码可以不管,点击反编译选项即可)
单步1,进入数据库的连接配置
public ProfiledDbConnection(DbConnection connection, IDbProfiler profiler)
{
_connection = (connection ?? throw new ArgumentNullException("connection"));
_connection.StateChange += StateChangeHandler;
if (profiler != null)
{
_profiler = profiler;
}
}
此处源码进行了数据库的连接配置,和查询关联不大,
单步2,查询前的参数配置等等…
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
CommandDefinition command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
//配置SQL语句,参数,事务,超时时间等等
IEnumerable<T> data = cnn.QueryImpl<T>(command, typeof(T)); //真正的查询在这里
if (!command.Buffered)
{
return data;
}
return data.ToList();
}
这一步是主要配置参数
单步3,执行SQL进行查询,及类型转化
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
{
//获取参数
object param = command.Parameters;
//验证Sql和参数等的信息
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
//设置缓存
CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand cmd = null;
IDataReader reader = null; //Attention Here 它是使用DataReader来进行数据的读取
//这里就是问题的所在
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed)
{
cnn.Open();
}
//在这里执行了SQL语句,这个方法只是把查询简单的包装一下,我把它放到下面
reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SingleResult | CommandBehavior.SequentialAccess);
( //这就是那个方法,它不在这个位置,我为了方便把它放到里面来
private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior)
{
try
{ //执行SQL,
return cmd.ExecuteReader(GetBehavior(wasClosed, behavior));
}
catch (ArgumentException ex)
{
if (Settings.DisableCommandBehaviorOptimizations(behavior, ex))
{
//执行SQL
return cmd.ExecuteReader(GetBehavior(wasClosed, behavior));
}
throw;
}
}
)
wasClosed = false;
DeserializerState tuple = info.Deserializer; //设置反序列化器
int hash = GetColumnHash(reader);
if (tuple.Func != null && tuple.Hash == hash)
{
goto IL_0174; //这个地方应该是反编译的问题,没有正常显示(无关紧要)
}
if (reader.FieldCount != 0)
{
//获取反序列化状态
DeserializerState deserializerState2 = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, returnNullIfFirstMissing: false));
tuple = deserializerState2;
if (command.AddToCache)
{ //设置重庆讯缓存
SetQueryCache(identity, info);
}
goto IL_0174;
}
goto end_IL_00a0;
IL_0174:
//设置反序列化的方法委托
Func<IDataReader, object> func = tuple.Func;
Type convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
while (reader.Read()) //直接去遍历了整个DataReader
{
object val = func(reader); //读取'一行'数据,利用方法委托转化为Object对象
if (val == null || val is T)
{
yield return (T)val; //再对Obj进行强转
}
else
{
//再对Obj进行强转
yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
}
while (reader.NextResult()) //检查DataReader是否处理完
{
}
reader.Dispose();
reader = null;
command.OnCompleted();
end_IL_00a0:;
}
finally //关闭连接操作
{
if (reader != null)
{
if (!reader.IsClosed)
{
try
{
cmd.Cancel();
}
catch
{
}
}
reader.Dispose();
}
if (wasClosed)
{
cnn.Close();
}
cmd?.Dispose();
}
}
由于Dapper没有返回DataTable的方法,这条语句实际上是把ExecuteReader的dataReader结果转型的DataTable,故效率和上一种的方法差别应该不大,单页需要进入查看一下步骤
单步1:还是用DataReader执行的
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
CommandDefinition command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType);
IDbCommand dbcmd;
IDataReader reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd);
return WrappedReader.Create(dbcmd, reader);
}
单步进入执行方法:
private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand cmd)
{ //因为返回的是DataReader,不需要去处理类型信息,所以不需要(正反)序列化,类型检查,强制转型...
//因此效率比上一个方法要好一点
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
bool disposeCommand = true;
try
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed)
{
cnn.Open();
}
IDataReader reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, commandBehavior);
wasClosed = false;
disposeCommand = false;
return reader;
}
finally
{
if (wasClosed)
{
cnn.Close();
}
if (cmd != null && disposeCommand)
{
cmd.Dispose();
}
}
}
第三条语句是微软自己的工具,VS当然反编译不了,但它很简单,
只是用原生的DataAdapter.Fill(DataTable table)
SqlDataAdapter adapter = new SqlDataAdapter(); //大概就是使用DataAdapter.F
adapter.SelectCommand = cmd;
adapter.Fill(DataTable);
执行的时间对比(数据量10w+)
3>2>1
时间大致为
7s : 40s : 50s+
可以很明显的看出Dapper单单使用DataReader,而原生的ADO.NET中用的是DataAdapter
原因归根结底还是DataAdapter和DataReader的适用性问题
根据查到的信息,DataAdapter.Fill()是短连接,一次加载所有数据到DataTable中,
而DataReader是长连接,一行一行的读取数据库查询信息,(通常在一个while循环里读取,这点像JDBC的ResultSet),这样就很容易进行ORM操作,好做类型的转化,加之Dapper的参数设置,类型检查等等,反序列化操作等等…导致了大量数据查询时的效率低下.
这里给一篇作参考的文章
https://blog.csdn.net/u012927285/article/details/44095195