在.NET数据库访问方面的Dapper类库介绍

Posted 周金桥讲堂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在.NET数据库访问方面的Dapper类库介绍相关的知识,希望对你有一定的参考价值。

在开发带数据库的.NET系统中我使用过各种方式,包括直接使用ADO.NET、使用基于ADO.NET封装的各类工具(其中有自己封装的)、还有各类ORM类库,比如NHibernateMyBatisNetLinq to SQLEntity Framwrok等,在上面的工具或类库中,MyBatisNet一段时间曾是我的最爱:由于它基于XML的配置可以灵活适应一些特殊的场景,不过有时候在面对中小型项目时又觉得MyBatisNet有些大材小用,此外还有一个原因是MyBatisNet这个基于JavaMyBatis改造而来的项目最近几乎没有更新了。

很早就听说过Dapper这个类库了,只不过一直没有尝试使用,但是很早就知道它是国外大型IT问答社区StackOverFlow最早开发并开源的。最近用了一下,感觉确实很方便。Dapper的源代码放在github上托管,并且可以用NuGet方式添加到项目中,只不过我现在开发的桌面软件有一部分用户还在使用WindowsXP系统,因此不能使用高于.NET Framrwork4.5以上版本开发且开发工具是Visual Studio 2015,这也限制了我不能使用最新版本的Dapper,于是我自己从github上获取了Dapper的源码,从历次提交中选择了1.35这个标签:


并且使用TortoriseGit将其导出,然后使用VisualStudio编译,得到Dapper 1.35这个版本。

 

本文的描述都是针对Dapper1.35版本。

 

扩展方法介绍

在介绍Dapper之前首先要介绍一下.NE中的扩展方法。扩展方法是.NET3.5添加的一个特性,使用扩展方法可以让你为现有的类扩展方法而无需创建新的派生类。下面以一个例子来说明:

在我解析某个XML文件节点时,需要读取某个节点的属性,但是这个节点并不是一直有这个属性值,如下:

<SubNetwork name="SF-SubNetWork" type="">

为了避免name属性不存在时抛出异常,我必须先进行判断,如下:

string name=string.Empty;

if (subNetworkNode.Attributes["name"] != null)

{

   name=subNetworkNode.Attributes["name"].Value;

}

如果一个XML节点里有几个可能不存在的属性时,就需要处处这样判断了,于是我对代码进行了改进,针对此类情况定义了扩展方法,方法如下:

public static class ExtendMethodClass

{

         /// <summary>

         /// 获取指定属性的值,如果没有设置指定属性名,则返回空字符串

         /// </summary>

         /// <paramname="attributes">XML节点的属性集合</param>

         /// <paramname="attributeName">属性名</param>

         ///<returns></returns>

         public static stringGetAttributeValue(this XmlAttributeCollection attributes,string attributeName)

         {

                   if(string.IsNullOrEmpty(attributeName))

                   {

                            thrownew ArgumentNullException("attributeName", "不能为空");

                   }

 

                   if(attributes == null||attributes[attributeName]==null)

                   {

                            returnstring.Empty;

                   }

 

                   returnattributes[attributeName].Value;

         }

}

这样一来,原来的代码就可以写成如下了:

string name =subNetworkNode.Attributes.GetAttributeValue("name");

初一看,就像是XmlAttributeCollection这类原来就有GetAttributeValue(stringattributeName)这样一个方法,其实这个方式是我们自己扩展的。

定义扩展方法有几点:

1、定义扩展方法的类必须用static修饰,即必须为静态类。

2、定义的扩展方法必须用static修饰,即必须为静态方法,同时方法的第一个参数前必须加this修饰,this后必须是类名,表示为this后的类添加扩展方法,如本例中this XmlAttributeCollection attributes表示为XmlAttributeCollection这个类添加扩展方法,如果需要在方法体内访问XmlAttributeCollection这个类的实例,通过后面的attributes参数即可(注意这个参数的名称可以随便取)。

 

Dapper介绍

通过上面的介绍,大家可以初步了解扩展方法是怎么回事。其实Dapper主要也是用了扩展方法为IDbConnectionIDataReader添加扩展方法,比如在SqlMapper.cs中有如下代码为IDbConnection添加扩展方法(节选):

// <summary>

/// Execute parameterized SQL.

/// </summary>

/// <param name="cnn">The connection to queryon.</param>

/// <param name="sql">The SQL to execute for thisquery.</param>

/// <param name="param">The parameters to use forthis query.</param>

/// <param name="transaction">The transaction to usefor this query.</param>

/// <param name="commandTimeout">Number of secondsbefore command execution timeout.</param>

/// <param name="commandType">Is it a stored proc ora batch?</param>

/// <returns>The number of rows affected.</returns>

public static int Execute(this IDbConnection cnn, string sql, objectparam = null, IDbTransaction transaction = null, int? commandTimeout = null,CommandType? commandType = null)

{

         var command = newCommandDefinition(sql, param, transaction, commandTimeout, commandType,CommandFlags.Buffered);

         returnExecuteImpl(cnn, ref command);

}

 

/// <summary>

/// Execute parameterized SQL.

/// </summary>

/// <param name="cnn">The connection to executeon.</param>

/// <param name="command">The command to execute onthis connection.</param>

/// <returns>The number of rows affected.</returns>

public static int Execute(this IDbConnection cnn, CommandDefinitioncommand) => ExecuteImpl(cnn, ref command);

 

SqlMapper.IDataReader.csIDataReader添加扩展方法的代码(节选):

/// <summary>

/// Parses a data reader to a sequence of data of the supplied type.Used for deserializing a reader without a connection, etc.

/// </summary>

/// <typeparam name="T">The type to parse from the<paramref name="reader"/>.</typeparam>

/// <param name="reader">The data reader to parseresults from.</param>

public static IEnumerable<T> Parse<T>(this IDataReaderreader)

{

         if (reader.Read())

         {

                   var deser =GetDeserializer(typeof(T), reader, 0, -1, false);

                   do

                   {

                            yieldreturn (T)deser(reader);

                   } while(reader.Read());

         }

}

 

/// <summary>

/// Parses a data reader to a sequence of data of the supplied type(as object). Used for deserializing a reader without a connection, etc.

/// </summary>

/// <param name="reader">The data reader to parseresults from.</param>

/// <param name="type">The type to parse from the<paramref name="reader"/>.</param>

public static IEnumerable<object> Parse(this IDataReaderreader, Type type)

{

         if (reader.Read())

         {

                   var deser =GetDeserializer(type, reader, 0, -1, false);

                   do

                   {

                            yieldreturn deser(reader);

                   } while(reader.Read());

         }

}

 

在本人2011725日写的一篇名为《利用ADO.NET的体系架构打造通用的数据库访问通用类》的博客当中介绍了ADO.NET的体系架构,如下图:


就是首先定义了一系列的借口,如IDbConnection之类的,任何基于数据库访问只要实现了接口的定义,就都能在.NET访问,包括了微软自己对SQL ServerAccess等数据库的实现以及mysqlOracle针对这个定义的第三方实现(其实JDBC也是这个道理,只不过是基于Java实现罢了)。因为包括SQL Server/MySQL/Oracle/PostgreSQL/SQLite在内的数据库都实现了IDbConnection的定义,而Dapper又是基于IDbConnection的扩展,因此使用Dapper理论上可以访问任何支持ADO.NET访问的数据库(前提是需要相关的数据库驱动,dll形式)。

在使用Dapper的实际开发中,用得较多的还是针对IDbConnection的扩展方法,主要有:

int Execute():相当于Command.ExecuteNonQuery(),指定增加、删除、修改SQL语句,返回受影响的行数。

object ExecuteScalar():相当于Command. ExecuteScalar(),返回结果集第一行第一列,用于聚合函数等。

T ExecuteScalar<T>():相当于Command. ExecuteScalar(),返回结果集第一行第一列,不过返回的结果指定了具体类型。

IDataReader ExecuteReader():相当于Command.ExecuteReader()

IEnumerable<dynamic> Query()

IEnumerable<T> Query<T>()

IEnumerable<object> Query()

上面的结果都是返回一个实现了IEnumerable的结果集,如果需要返回单个结果需要做一下转换。

Dapper的较高版本中还有更多方法,使用更便捷,其中部分方法如下:

IEnumerable<dynamic> Query()

dynamic QueryFirst()

dynamic QueryFirstOrDefault()

dynamic QuerySingle()

dynamic QuerySingleOrDefault()

IEnumerable<T> Query<T>()

T QueryFirst<T>()

T QueryFirstOrDefault<T>()

T QuerySingle<T>()

T QuerySingleOrDefault<T>()

IEnumerable<object> Query()

object QueryFirst()

object QueryFirstOrDefault()

object QuerySingle()

object QuerySingleOrDefault()

对于上面各种类型的Query和返回结果,就是分几种情况:返回一个实现IEnumerable接口的结果集,返回单个结果,返回单个结果或在没有找到匹配结果下返回默认值(引用类型、数值类型、枚举、日期等的默认值)

 

基本用法

使用了Dapper之后,在插入或者查询时默认是按照数据库字段名与类属性名不区分大小写的情况下对应。

加入有在SQLServer中有如下表:

CREATE TABLE IF NOT EXISTS tblBay (

    Id                   integer              NOT NULL PRIMARY KEYAUTOINCREMENT UNIQUE,

    Name                 nvarchar(50)         not null,

    Desc                 nvarchar(100)        not null

    )

同时有如下类定义:

public class Bay

{

         public int ID { get;set; }

         public string Name {get; set; }

         public string Desc {get; set; }

}

 

那么插入可以这么写:

string connectionString = ".";//将这里改成你自己的数据库连接字符串

string sqlInsert = "INSERT INTO tblBay(Name,Desc)VALUES(@Name,@Desc)";

SqlConnection connection = new SqlConnection(connectionString);

Bay bay = new Bay { Name = "test", Desc = "desc"};

connection.Execute(sqlInsert, bay);

 

查询可以这么写:

string connectionString = ".";//将这里改成你自己的数据库连接字符串

string sqlQuery = "select * from tblBay where Id=@Id";

int id = 1;

SqlConnection connection = new SqlConnection(connectionString);

IEnumerable<Bay> bayList =connection.Query<Bay>(sqlQuery,new { @Id = id });

 

字段与属性不一致情况下关联

但是在某些情况下,比如使用MySQL数据库时我们可能会在由多个单词构成的字段名之间以下划线分割,如”user_id””user_name”等,而定义实体类时我们又将实体类的属性定义为UserIdUserName,那么就需要为他们之间建立关联,比较简单的一种方式就是在select的时候使用as

假定在MySQL中存在如下表:

CREATE TABLE IF NOT EXISTS tblperson (

    user_id               integer              NOT NULL PRIMARY KEYAUTOINCREMENT UNIQUE,

    user_name            nvarchar(50)         not null,

    email                 nvarchar(100)        not null

    )

而对应的实体类为:

public class Person

{

         public int UserId {get; set; }

         public string UserName{ get; set; }

}

 

那么插入可以这么写:

string connectionString = ".";//将这里改成你自己的数据库连接字符串

SqlConnection connection = new SqlConnection(connectionString);

DynamicParameters parameters = = new DynamicParameters();

parameters.Add("@Name", person.UserName);

connection.Execute(sqlInsert, parameters);

 

查询可以这么写:

string connectionString = ".";//将这里改成你自己的数据库连接字符串

string sqlQuery = "select user_id as userId,user_name asusername,email from tblperson where user_id=@UserId";

int userId = 1;

SqlConnection connection = new SqlConnection(connectionString);

DynamicParameters parameters = = new DynamicParameters();

parameters.Add("@UserId ", userId);

IEnumerable<Person> bayList = connection.Query<Person>(sqlQuery,parameters);

 

也就是数据库字段名与实体类属性名如果忽略大小写的情况下是一致的,则我们无需单独处理它们之间的映射关系,如果数据库字段名与实体类属性在忽略大小写的情况下仍然不一致,那么我们需要手动处理映射:在INSERTDELETEUPDATE时可以通过DynamicParameters来处理;在SELECT时可以通过在SQL语句中使用AS来处理。

有关Dapper的更进一步用法可以查看Dapper的用户手册或直接查看源代码。

 

周金桥

2018/04/22

以上是关于在.NET数据库访问方面的Dapper类库介绍的主要内容,如果未能解决你的问题,请参考以下文章

[开源] .NET数据库ORM类库 Insql

[译]Dapper教程

.NET Core开发日志——Dapper与MySQL

[开源]MasterChief 快速开发辅助类库

NET Core 实战 Dapper 扩展数据访问

ORM框架之------Dapper,Net下无敌的ORM