轻量级ORM框架Dapper

基本介绍

Dapper作为一款高性能、轻量级的ORM框架,目前支撑了国外知名网站stackoverflow的数据访问层,其知名度非常高。在众多ORM中,堪称性能之王。作为一款微型 ORM,很受国内开发者的欢迎,毕竟经过大网站 stackoverflow 的考验。很多自主开发的 ORM 做性能测试,都会选择 Dapper 作为比较对象。

Dapper的优势

  • 轻量。只有一个文件(SqlMapper.cs)
  • 速度快。Dapper的速度接近与IDataReader,取列表的数据超过了DataTable。
  • 支持多种数据库。支持多数据库的本质是因为Dapper是对IDBConnection接口进行了方法扩展,而如SqlConnection,MysqlConnection,OracleConnection等都是继承于DBConnection,而DBConnection又是实现了IDBConnection的接口。所以Dapper能够在所有Ado.net Providers下工作,包括sqlite, sqlce, firebird, oracle, MySQL, PostgreSQL and SQL Server。
  • 可以映射一对一,一对多,多对多等多种关系。
  • 性能高。通过Emit反射IDataReader的序列队列,来快速的得到和产生对象,性能不错。
  • 兼容性好。支持.NetFrameWork2.0之后所有版本

Dapper原理

Dapper是一个简单的ORM,专门从SQL查询结果中快速生成对象。Dapper支持执行sql查询并将其结果映射到强类型列表或动态对象列表。Dapper缓存每个查询的信息。这种全面的缓存有助于从大约两倍于LINQ到SQL的查询生成对象。当前缓存由两个ConcurrentDictionary对象处理,它们从不被清除。

Dapper通过扩展方法将两个映射函数添加到IDbConnection接口,这两个函数都命名为ExecuteMapperQuery。第一个映射结果是一个强类型列表,而第二个映射结果是一个动态对象列表。ExecuteMapperCommand执行并且不返回结果集。所有三个方法都将参数接受为匿名类,其中属性值映射到同名的SQL参数。

Dapper旨在仅处理结果集到对象映射。它不处理对象之间的关系,它不会自动生成任何类型的SQL查询。

  • Query()方法
1
2
Query<T>(this IDbConnection cnn, string sql, object param = null, 
IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)

Query()方法表示执行查询,返回按T输入的数据。该方法是Query()方法的泛型方法,有7个参数,第一个参数为IDbConnection扩展类,表示对IDbConnection接口进行扩展,该方法使用了可选参数,提高方法的扩展性。在Query方法的实现中,有一个CommandDefinition类,用来表示sql操作的关键方面。在该类下有一个GetInit()方法。

  • GetInit()方法
    Dapper通过Emit反射IDataReader的序列队列,来快速的得到和产生对象。GetInit()方法是一个静态方法,该方法的“Type commandType”参数表示连接关联的Command对象,返回一个Action委托。
1
if (SqlMapper.Link<Type, Action<IDbCommand>>.TryGet(commandInitCache, commandType, out action)){ return action; }

Link<TKey, TValue>是一个泛型分部类,这是一个微缓存,查看是否存在一个Action的委托。

1
2
 var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool));
var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int));

以上两个操作主要获取BindByName和InitialLONGFetchSize的获取基本属性设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (bindByName != null || initialLongFetchSize != null)
{
var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) });
var il = method.GetILGenerator();
if (bindByName != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_1);
il.EmitCall(OpCodes.Callvirt, bindByName, null);
}
if (initialLongFetchSize != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_M1);
il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null);
}
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand>)method.CreateDelegate(typeof(Action<IDbCommand>));
}

这一步是该操作的核心部分,利用Emit反射操作。根据上一步获取的对应名称的基本属性设置,采用DynamicMethod对象,定义和表示一个可以编译,执行和丢弃的动态方法。丢弃的方法可用于垃圾回收。调用该对象的GetILGenerator方法,返回方法的Microsoft中间语言(MSIL)生成器,默认的MSIL流大小为64字节。判断基本属性设置不为空后,调用ILGenerator类的Emit方法,Emit()将指定的指令放在指令流上,该方法接收一个IL流。EmitCall()将 call 或 callvirt 指令置于 Microsoft 中间语言 (MSIL) 流,以调用varargs 方法。我们看到OpCodes类,该类描述中间语言 (IL) 指令。CreateDelegate()完成动态方法并创建一个可用于执行它的委托。

通过以上的反射操作构建好对象后,就会接着执行对应的数据库操作。

  • QueryImpl()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
    {
    object param = command.Parameters;
    var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType(), null);
    var info = GetCacheInfo(identity, param, command.AddToCache);
    IDbCommand cmd = null;
    IDataReader reader = null;
    bool wasClosed = cnn.State == ConnectionState.Closed;
    try
    {
    cmd = command.SetupCommand(cnn, info.ParamReader);
    if (wasClosed) cnn.Open();
    reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
    wasClosed = false;
    var tuple = info.Deserializer;
    int hash = GetColumnHash(reader);
    if (tuple.Func == null || tuple.Hash != hash)
    {
    if (reader.FieldCount == 0)
    yield break;
    tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
    if (command.AddToCache) SetQueryCache(identity, info);
    }
    var func = tuple.Func;
    var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
    while (reader.Read())
    {
    object val = func(reader);
    if (val == null || val is T)
    {
    yield return (T)val;
    }
    else
    {
    yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
    }
    }
    while (reader.NextResult()) { }
    reader.Dispose();
    reader = null;
    command.OnCompleted();
    }
    finally
    {
    if (reader != null)
    {
    if (!reader.IsClosed) try { cmd.Cancel(); }
    catch { /* don't spoil the existing exception */ }
    reader.Dispose();
    }
    if (wasClosed) cnn.Close();
    if (cmd != null) cmd.Dispose();
    }
    }

    该方法为执行查询操作的核心方法,通过CommandDefinition类的相关操作后,获取到相应的对象后,执行这一步操作。该方法是IDbConnection的扩展方法,CommandDefinition表示sql的相关操作对象,Type表示传入的一个有效的类型。Identity对象表示Dapper中的缓存查询的标识,该类是一个分部类,可以对其进行相应的扩展。GetCacheInfo()获取缓存信息。