[Package] 使用Dapper實現ORM願望

使用Dapper實現ORM願望

前言

 

近期筆者若有新開發案,通常會偏好使用Entity Framework做為ORM資料存取方案首選,一方面是客戶喜歡用新技術看起來比較威猛,另外自己也可針對這門浩瀚技術再多增加實戰磨練的機會。但現實總是不盡人意,現有專案維護或開發上還是存在著許多使用ADO.NET技術來實作資料存取功能的系統,一般來講透過ADO.NET取得的資料通常也會希望透過強型別物件方式傳遞給呼叫端進行下一步的處理,此時就免不了自行處理ORM繁瑣的轉換動作,好讓自DB撈出之資料可對應到物件中的屬性,而本文所介紹的Dapper就是來協助開發者處理這部分的工作。

 

 

未使用ORM工具的日子

 

一般而言,會先定義POCO類別來對應至DB資料表欄位,然後使用ADO.NET取出資料,再透過自行實作的ORM方法轉換每一列資料至先前定義之類別物件中,最後回傳至呼叫端進行後續處理(顯示),約略步驟如下。

 

首先會有一個Users資料表存在於DB中

image

 

定義對應至Users資料表的POCO類別User


public class User
{
    // Properties
    public string UserId { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }

    public string Name { get; set; }

    public DateTime RegisterOn { get; set; }

    public bool IsEnable { get; set; }
}

 

定義一個專門(寫死)用來轉換DataReader資料至User類別物件的ORM方法


static private User CreateUser(SqlDataReader reader)
{
    try
    {
        return new User
        {
            Email = Convert.ToString(reader["Email"]).Trim(),
            IsEnable = Convert.ToBoolean(reader["IsEnable"]),
            Name = Convert.ToString(reader["Name"]).Trim(),
            Password = Convert.ToString(reader["Password"]).Trim(),
            RegisterOn = Convert.ToDateTime(reader["RegisterOn"]),
            UserId = Convert.ToString(reader["UserId"]).Trim()
        };
    }
    catch (Exception)
    {
        return null;
    }
}

 

最後就是透過ADO.NET將資料撈出,使用CreateUser方法將資料轉換成User物件,回傳強型別物件給上層


static public List<User> GetAllUsers(string connStr)
{
    List<User> users = new List<User>();

    // connection
    using (var conn = new SqlConnection(connStr))
    {
        // command
        SqlCommand command = conn.CreateCommand();
        command.CommandText = "Select * from Users";

        try
        {
            // open connection
            conn.Open();

            // execute
            SqlDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                // ORM
                User user = CreateUser(reader);
                if(user != null) users.Add(user);
            }
            reader.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    return users;
}

 

呼叫端就可以享受到ORM後操作物件來取得特定資料的便捷性


static void Main(string[] args)
{
    var connStr = GetConnectionStringByName("Demo");

    foreach (var user in GetAllUsers(connStr))
    { Console.WriteLine("UserId:{0}, UserEmail:{1}", user.UserId, user.Email); }


    Console.Read();
}

 

在整個過程中,我想會令開發者困擾的部分將會在將DB資料轉換至POCO類別物件的過程,因為需要針對撈出的資料逐一手動對應至POCO類別中的屬性,當類別數量多且屬性又多時將難以避免錯誤的發生。其實許多前輩都會自行實作一個ORM工具來因應這類繁瑣事務,但小弟將先以目前比較多人推薦的ORM工具Dapper來介紹。

 

 

安裝ORM工具 – Dapper

 

開啟NuGet管理視窗下載Dapper套件如下

image

 

真的小好厲害,果然是Micro-ORM

image

 

 

開始擁有ORM工具(Dapper)的美好日子

 

使用Dapper前先約略了解一下他所提供的功能。Dapper函式庫會新增IDbConnection擴充方法如下,分別為查詢取得強型別泛型List資料、查詢取得動態物件List資料及無資料回傳執行功能,以下將會針對各項擴充方法進行說明及套用。其中比較需要注意的特性是,Dapper的使用都是在DbConnection已經開啟的狀況為前提(關閉時會報錯),也就表示DbConnection開啟與關閉需要自行處理


// Execute a query and map the results to a strongly typed List
public static IEnumerable<T> Query<T>(this IDbConnection cnn, 
	string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

// Execute a query and map it to a list of dynamic objects
public static IEnumerable<dynamic> Query (this IDbConnection cnn, 
	string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

// Execute a Command that returns no results
public static int Execute(this IDbConnection cnn, 
	string sql, object param = null, SqlTransaction transaction = null)

 

查詢取得強型別泛型List資料

 

調整過後可以看到程式碼相當精簡,所有ORM的工作已經被Dapper取代,我們只要確定撈出資料名稱與先前定義之User類別屬性相同即可,範例程式如下。


static public List<User> GetAllUsers(string connStr)
{
    List<User> users = new List<User>();

    // connection
    using (var conn = new SqlConnection(connStr))
    {
        try
        {
            // open connection
            conn.Open();

            // execute
            users = conn.Query<User>("Select * from Users Where IsEnable=@IsEnable", 
                                        new { IsEnable = 1 }).ToList();
           
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }


        /*

        // command
        SqlCommand command = conn.CreateCommand();
        command.CommandText = "Select * from Users Where IsEnable=1";

        try
        {
            // open connection
            conn.Open();

            // execute
            SqlDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                // ORM
                User user = CreateUser(reader);
                if(user != null) users.Add(user);
            }
            reader.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        */

    }

    return users;
}

static void Main(string[] args)
{
    var connStr = GetConnectionStringByName("Demo");

    foreach (var user in GetAllUsers(connStr))
    { Console.WriteLine("UserId:{0}, UserEmail:{1}", user.UserId, user.Email); }

    Console.Read();
}

 

image

 

 

查詢取得動態物件List資料

 

若使用動態物件List資料將會失去強型別所帶來的好處,但對於臨時性的資料取得是較為方便的,使用情境有可能是要回傳Dictionary Key-Value Pair來作為下拉式選單的資料來源,此時就不須定義POCO類別來承接資料。注意當動態物件的屬性不存在於資料表中,會預設回傳Null值取代之。


static public Dictionary<string, string> GetAllUserIdNamePair(string connStr)
{
    Dictionary<string, string> dictionary = new Dictionary<string, string>();

    // connection
    using (var conn = new SqlConnection(connStr))
    {
        try
        {
            // open connection
            conn.Open();

            // execute
            var dynamicUsers = conn.Query("Select * from Users Where IsEnable=@IsEnable",
                                        new { IsEnable = 1 });

            // prepare string data
            foreach (var user in dynamicUsers)
            {
                dictionary[user.UserId] = user.Name;

                // 注意: 若無對應資料會回傳Null值
                // ex. var temp = user.XxxOoo; 
                // 因未XxxOoo欄位不存在於撈取的資料名單中
                // 所以temp 將會為Null值
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    return dictionary;
}

static void Main(string[] args)
{
    var connStr = GetConnectionStringByName("Demo");

    var userIdNamePair = GetAllUserIdNamePair(connStr);
    foreach (var key in userIdNamePair.Keys)
    {
        Console.WriteLine("Key[UserId]:{0}, Value[UserName]:{1}", 
        		key, userIdNamePair[key]);
    }

    Console.Read();
}

 

image

 

 

無資料回傳執行功能

 

嚴格來講是有回傳值的,執行後會傳回影響的筆數。此處Dapper提供了重複執行同一SQL作業功能,倘若需一次寫入好幾筆資料時,可利用其特性將實作IEnumerable清單資料轉為陣列格式當作參數傳入,Dapper會自動Mapping SQL語法中參數與類別物件屬性的關係,幫忙開發者一次把所有資料寫入資料庫,範例程式如下。


static public int InsertUsers(string connStr, List<User> users)
{
    int effectCounter = 0;

    // connection
    using (var conn = new SqlConnection(connStr))
    {
        try
        {
            // open connection
            conn.Open();

            // execute
            effectCounter = conn.Execute(@"

                    INSERT INTO [dbo].[Users]
                               ([UserId]
                               ,[Email]
                               ,[Password]
                               ,[Name]
                               ,[RegisterOn]
                               ,[IsEnable])
                         VALUES
                               ( @UserId
                               , @Email
                               , @Password
                               , @Name
                               , @RegisterOn
                               , @IsEnable)
                    ",
                   users.ToArray());

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    return effectCounter;

}

static void Main(string[] args)
{
    var connStr = GetConnectionStringByName("Demo");

    List<User> insertUsers = new List<User>();
    insertUsers.Add(new User {
                        UserId = "wasi.test1",
                        Email = "wasi.test1@gmail.com",
                        Name = "test1 chen",
                        Password = "test1",
                        IsEnable = true,
                        RegisterOn = DateTime.Now
                    });
    insertUsers.Add(new User {
                        UserId = "wasi.test2",
                        Email = "wasi.test2@gmail.com",
                        Name = "test2 chen",
                        Password = "test2",
                        IsEnable = true,
                        RegisterOn = DateTime.Now
                    });


	// insert users to db
    int effectCounter = InsertUsers(connStr, insertUsers);
    Console.WriteLine("counter: " + effectCounter );

    Console.Read();
}

 

image

 

DB確實新增了2筆資料

image

 

 

後記

 

初次使用Dapper的感覺其實還滿方便順手,可以節省以往實作資料轉換所花費(打字)的時間,且使用Dapper的包袱並不重,只需要引用一個小小的Dll即可實現筆者所需之ORM功能。另外,據官方說明顯示其效能也是絲毫不遜色的,所以總評之下是相當不錯的ORM解決方案。

 

如需要更完整教學範例的朋友,可以參考以下官方教學文章

https://github.com/StackExchange/dapper-dot-net


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !