在.NET數據庫訪問方面的Dapper類庫介紹

在開發帶數據庫的.NET系統中我使用過各類方式,包括直接使用ADO.NET、使用基於ADO.NET封裝的各種工具(其中有本身封裝的)、還有各種ORM類庫,好比NHibernate、MyBatisNet、Linq to SQL、Entity Framwrok等,在上面的工具或類庫中,MyBatisNet一段時間曾是個人最愛:因爲它基於XML的配置能夠靈活適應一些特殊的場景,不過有時候在面對中小型項目時又以爲MyBatisNet有些大材小用,此外還有一個緣由是MyBatisNet這個基於Java的MyBatis改造而來的項目最近幾乎沒有更新了。
很早就據說過Dapper這個類庫了,只不過一直沒有嘗試使用,可是很早就知道它是國外大型IT問答社區StackOverFlow最先開發並開源的。最近用了一下,感受確實很方便。Dapper的源代碼放在github上託管,而且能夠用NuGet方式添加到項目中,只不過我如今開發的桌面軟件有一部分用戶還在使用WindowsXP系統,所以不能使用高於.NET Framrwork4.5以上版本開發且開發工具是Visual Studio 2015,這也限制了我不能使用最新版本的Dapper,因而我選擇了Dapper 1.50.2這個版本。
咱們能夠在Visual Studio 2015中直接使用NuGet來添加,具體辦法就是「工具」-「NuGet包管理器」-「管理解決方案的BuGet程序包」,以下圖所示:
在.NET數據庫訪問方面的Dapper類庫介紹
而後在彈出的窗口中搜索「Dapper」,以下圖所示:
在.NET數據庫訪問方面的Dapper類庫介紹
在上述界面中能夠選擇安裝到當前解決方案的那些項目中,而且還能夠指定Dapper的版本。git

本文的描述都是針對Dapper 1.50.2版本。github

擴展方法介紹
在介紹Dapper以前首先要介紹一下.NE中的擴展方法。擴展方法是.NET3.5添加的一個特性,使用擴展方法可讓你爲現有的類擴展方法而無需建立新的派生類。下面以一個例子來講明:
在我解析某個XML文件節點時,須要讀取某個節點的屬性,可是這個節點並非一直有這個屬性值,以下:
<SubNetwork name="SF-SubNetWork" type="">
爲了不name屬性不存在時拋出異常,我必須先進行判斷,以下:sql

string name=string.Empty;
if (subNetworkNode.Attributes["name"] != null)
{
    name=subNetworkNode.Attributes["name"].Value;
}

若是一個XML節點裏有幾個可能不存在的屬性時,就須要到處這樣判斷了,因而我對代碼進行了改進,針對此類狀況定義了擴展方法,方法以下:數據庫

public static class ExtendMethodClass
{
    /// <summary>
    /// 獲取指定屬性的值,若是沒有設置指定屬性名,則返回空字符串
    /// </summary>
    /// <param name="attributes">XML節點的屬性集合</param>
    /// <param name="attributeName">屬性名</param>
    /// <returns></returns>
    public static string GetAttributeValue(this XmlAttributeCollection attributes,string attributeName)
    {
        if (string.IsNullOrEmpty(attributeName))
        {
            throw new ArgumentNullException("attributeName", "不能爲空");
        }

        if (attributes == null||attributes[attributeName]==null)
        {
            return string.Empty;
        }

        return attributes[attributeName].Value;
    }
}

這樣一來,原來的代碼就能夠寫成以下了:架構

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

初一看,就像是XmlAttributeCollection這類原來就有GetAttributeValue(string attributeName)這樣一個方法,其實這個方式是咱們本身擴展的。
定義擴展方法有幾點:
一、定義擴展方法的類必須用static修飾,即必須爲靜態類。
二、定義的擴展方法必須用static修飾,即必須爲靜態方法,同時方法的第一個參數前必須加this修飾,this後必須是類名,表示爲this後的類添加擴展方法,如本例中this XmlAttributeCollection attributes表示爲XmlAttributeCollection這個類添加擴展方法,若是須要在方法體內訪問XmlAttributeCollection這個類的實例,經過後面的attributes參數便可(注意這個參數的名稱能夠隨便取)。app

Dapper介紹
經過上面的介紹,你們能夠初步瞭解擴展方法是怎麼回事。其實Dapper主要也是用了擴展方法爲IDbConnection和IDataReader添加擴展方法,好比在SqlMapper.cs中有以下代碼爲IDbConnection添加擴展方法(節選):ide

// <summary>
/// Execute parameterized SQL.
/// </summary>
/// <param name="cnn">The connection to query on.</param>
/// <param name="sql">The SQL to execute for this query.</param>
/// <param name="param">The parameters to use for this query.</param>
/// <param name="transaction">The transaction to use for this query.</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns>The number of rows affected.</returns>
public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
    var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
    return ExecuteImpl(cnn, ref command);
}

/// <summary>
/// Execute parameterized SQL.
/// </summary>
/// <param name="cnn">The connection to execute on.</param>
/// <param name="command">The command to execute on this connection.</param>
/// <returns>The number of rows affected.</returns>
public static int Execute(this IDbConnection cnn, CommandDefinition command) => ExecuteImpl(cnn, ref command);

在SqlMapper.IDataReader.cs爲IDataReader添加擴展方法的代碼(節選):函數

/// <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 parse results from.</param>
public static IEnumerable<T> Parse<T>(this IDataReader reader)
{
    if (reader.Read())
    {
        var deser = GetDeserializer(typeof(T), reader, 0, -1, false);
        do
        {
            yield return (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 parse results from.</param>
/// <param name="type">The type to parse from the <paramref name="reader"/>.</param>
public static IEnumerable<object> Parse(this IDataReader reader, Type type)
{
    if (reader.Read())
    {
        var deser = GetDeserializer(type, reader, 0, -1, false);
        do
        {
            yield return deser(reader);
        } while (reader.Read());
    }
}

在本人2011年7月25日寫的一篇名爲《利用ADO.NET的體系架構打造通用的數據庫訪問通用類》的博客當中介紹了ADO.NET的體系架構,以下圖:
在.NET數據庫訪問方面的Dapper類庫介紹
就是首先定義了一系列的藉口,如IDbConnection之類的,任何基於數據庫訪問只要實現了接口的定義,就都能在.NET訪問,包括了微軟本身對SQL Server和Access等數據庫的實現以及MySQL和Oracle針對這個定義的第三方實現(其實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<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以後,在插入或者查詢時默認是按照數據庫字段名與類屬性名不區分大小寫的狀況下對應。
加入有在SQL Server中有以下表:開發工具

CREATE TABLE IF NOT EXISTS tblBay (
    Id                   integer              NOT NULL PRIMARY KEY AUTOINCREMENT 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. QueryFirstOrDefault<Bay>(sqlQuery,new { @Id = id });

字段與屬性不一致狀況下關聯
可是在某些狀況下,好比使用MySQL數據庫時咱們可能會在由多個單詞構成的字段名之間如下劃線分割,如」user_id」、」user_name」等,而定義實體類時咱們又將實體類的屬性定義爲UserId、UserName,那麼就須要爲他們之間創建關聯,比較簡單的一種方式就是在select的時候使用as。
假定在MySQL中存在以下表:

CREATE TABLE IF NOT EXISTS tblperson (
    user_id               integer              NOT NULL PRIMARY KEY AUTOINCREMENT 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; }
    public string Email { get; set; }
}

那麼插入能夠這麼寫:

string connectionString = ".";//將這裏改爲你本身的數據庫鏈接字符串
string sqlInsert = "INSERT INTO tblperson(user_name,email)VALUES(@Name,@Email)";
SqlConnection connection = new SqlConnection(connectionString);
Person person = new Person { UserName = "test", Email = "email@email.com" };
DynamicParameters parameters = = new DynamicParameters();
parameters.Add("@Name", person.UserName);
parameters.Add("@Email", person.Email);
connection.Execute(sqlInsert, parameters);

查詢能夠這麼寫:

string connectionString = ".";//將這裏改爲你本身的數據庫鏈接字符串
string sqlQuery = "select user_id as userId,user_name as username,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. QueryFirstOrDefault<Person>(sqlQuery, parameters);

也就是數據庫字段名與實體類屬性名若是忽略大小寫的狀況下是一致的,則咱們無需單獨處理它們之間的映射關係,若是數據庫字段名與實體類屬性在忽略大小寫的狀況下仍然不一致,那麼咱們須要手動處理映射:在INSERT、DELETE、UPDATE時能夠經過DynamicParameters來處理;在SELECT時能夠經過在SQL語句中使用AS來處理。
有關Dapper的更進一步用法能夠查看Dapper的用戶手冊或直接查看源代碼。

周金橋2018/04/22

相關文章
相關標籤/搜索