ASP.NET MVC 的 Model 使用 Dapper

接連三篇有關 Dapper 的文章,都是使用 LINQPad 向各位說明,對於有些開發者來說可能無法馬上可以體會,因為沒有一個明確或是簡單的應用範例,所以感覺就會有點在空談,所以這一篇就用一個簡單的 ASP.NET MVC 網站範例來做介紹,之前曾經在 twMVC #10 裡有分享過「ASP.NET MVC Model 的設計與使用」這個主題,然後又在部落格裡寫了以「ASP.NET MVC 的 Model 使用 ADO.NET」為題的五篇系列文,這一篇的範例文章就以那五篇系列文最後所完成的簡單範例做修改,在方案裡添加使用 Dapper 的 Repository 專案,並且讓 ASP.NET MVC 與 WebForm 網站都可以直接使用。


在開始進行之前,還是希望各位可以先對以下五篇文章做瞭解(之前已經有看過的就可以直接跳過)

ASP.NET MVC WebForm Repository Sample

再來就是請到以下的 Github Repository 下載上面那五篇文章的最後完成檔(其實告訴各位,我有將部分的文章範例程式上傳到 Github 上面,大家各以看看部落格上方有個「範例程式 @ GitHub」頁籤,裡面會說明放在 Github 上面的是那些範例和相關的文章,會在這邊強調是因為我發覺大家都根本不會去那個網頁裡看看,就連很多初學 ASP.NET MVC 然後卡在 DropDownList 的人在找相關資料時,都忽略我有放了一個相當齊全的 ASP.NET MVC DropDownList 範例)。

 

範例下載

https://github.com/kevintsengtw/ASPNET_MVC_WebForm_Repository_Sample

請點選該頁面右方的「Download ZIP」項目以下載範例程式。

image

 

專案內容

image

專案裡有一個 Repository.Interface 專案以及三個繼承介面的 Repository 實作專案,分別使用了ADO.NET , Entity Framework , Enterprise Library Data Access Application Block ( DAAB ) ,然後 MVC 與 WebForm 專案可以選擇其中一種的 Repository 專案進行資料存取的操作。

 

增加 Repository.Dapper 專案

新增一個 Sample.Repository.Dapper 類別庫專案,記得要加入 Sample.Domain 和 Sample.Repository.Interface 專案的參考,

image

image

再來就是專案使用 Nuget 加入 Dapper 套件

image

 

然後從 Sample.Repository.ADONET 專案裡複製「BaseRepository.cs」程式內容,所以 Sample.Repository.Dapper 專案裡也會有 BaseRepository 類別

image

 

新增 EmployeeRepository.cs 類別檔案,繼承 BaseRepository 和 Sample.Repository.Interface 的 IEmployeeRepository 介面,

image

接著實作內容,這邊只有做兩個查詢操作,一個是取得全部資料,另一個是取得指定 ID 的資料,這裡將會直接從 Sample.Repository.ADO.NET 的 EmployeeRepository 裡複製 SQL Command,

image

image

 

以下就是完成 Sample.Repository.Dapper 的 EmployeeRepository 類別的實作內容,

image

using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Dapper;
using Sample.Domain;
using Sample.Repository.Interface;
 
namespace Sample.Repository.Dapper
{
    public class EmployeeRepository : BaseRepository, IEmployeeRepository
    {
        public Employee GetOne(int id)
        {
            string sqlStatement = "select * from Employees where EmployeeID = @EmployeeID";
 
            using (var conn = new SqlConnection(this.ConnectionString))
            {
                var result = conn.Query<Employee>(
                    sqlStatement,
                    new
                    {
                        EmployeeID = id
                    }).FirstOrDefault();
 
                return result;
            }
        }
 
        public IEnumerable<Employee> GetEmployees()
        {
            string sqlStatement = "select * from Employees order by EmployeeID";
 
            using (var conn = new SqlConnection(this.ConnectionString))
            {
                var result = conn.Query<Employee>(sqlStatement);
                return result;
            }
        }
    }
}

做完上面的步驟之後,你可以跟 Sample.Repository.ADONET 與 Sample.Repository.EntLibDAAB 一樣實作 IEmployeeRepository 介面的類別做個比較,就可以發現到程式碼精簡不少,而且也省略了很多需要處理欄位對映物件屬性的處理。

Sample.Repository.ADONET - EmployeeRepository.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using Sample.Domain;
using Sample.Repository.Interface;
 
namespace Sample.Repository.ADONET
{
    public class EmployeeRepository : BaseRepository, IEmployeeRepository
    {
        /// <summary>
        /// Gets the one.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public Employee GetOne(int id)
        {
            string sqlStatement = "select * from Employees where EmployeeID = @EmployeeID";
 
            Employee item = new Employee();
 
            using (SqlConnection conn = new SqlConnection(this.ConnectionString))
            using (SqlCommand comm = new SqlCommand(sqlStatement, conn))
            {
                comm.Parameters.Add(new SqlParameter("EmployeeID", id));
 
                if (conn.State != ConnectionState.Open) conn.Open();
 
                using (IDataReader reader = comm.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            PropertyInfo property = item.GetType().GetProperty(reader.GetName(i));
 
                            if (property != null && !reader.GetValue(i).Equals(DBNull.Value))
                            {
                                ReflectionHelper.SetValue(property.Name, reader.GetValue(i), item);
                            }
                        }
                    }
                }
            }
            return item;
        }
 
        /// <summary>
        /// Gets the employees.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public IEnumerable<Employee> GetEmployees()
        {
            List<Employee> employees = new List<Employee>();
 
            string sqlStatement = "select * from Employees order by EmployeeID";
 
            using (SqlConnection conn = new SqlConnection(this.ConnectionString))
            using (SqlCommand command = new SqlCommand(sqlStatement, conn))
            {
                command.CommandType = CommandType.Text;
                command.CommandTimeout = 180;
 
                if (conn.State != ConnectionState.Open) conn.Open();
 
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        Employee item = new Employee();
 
                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            PropertyInfo property = item.GetType().GetProperty(reader.GetName(i));
 
                            if (property != null && !reader.GetValue(i).Equals(DBNull.Value))
                            {
                                ReflectionHelper.SetValue(property.Name, reader.GetValue(i), item);
                            }
                        }
                        employees.Add(item);
                    }
                }
            }
 
            return employees;
        }
    }
}

Sample.Repository.EntLibDAAB - EmployeeRepository.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Sample.Domain;
using Sample.Repository.Interface;
 
namespace Sample.Repository.EntLibDAAB
{
    public class EmployeeRepository : BaseRepository, IEmployeeRepository
    {
        /// <summary>
        /// Gets the one.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public Employee GetOne(int id)
        {
            string sqlStatement = "select * from Employees where EmployeeID = @EmployeeID";
 
            DataAccessor<Employee> accessor =
                this.Db.CreateSqlStringAccessor<Employee>(
                    sqlStatement,
                    new EmployeeIDParameterMapper(),
                    new EmployeeMapper());
 
            return accessor.Execute(new object[] { id }).FirstOrDefault();
        }
 
        /// <summary>
        /// Gets the employees.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public IEnumerable<Employee> GetEmployees()
        {
            string sqlStatement = "select * from Employees order by EmployeeID";
 
            DataAccessor<Employee> accessor =
                this.Db.CreateSqlStringAccessor<Employee>(sqlStatement, new EmployeeMapper());
 
            return accessor.Execute();
        }
 
        #region -- 基本操作 --
 
        //public Employee GetOne(int id)
        //{
        //    string sqlStatement = "select * from Employees where EmployeeID = @EmployeeID";
 
        //    Employee item = new Employee();
 
        //    using (DbCommand comm = Db.GetSqlStringCommand(sqlStatement))
        //    {
        //        var param = comm.CreateParameter();
        //        param.ParameterName = "EmployeeID";
        //        param.Value = id;
        //        comm.Parameters.Add(param);
 
        //        using (IDataReader reader = this.Db.ExecuteReader(comm))
        //        {
        //            if (reader.Read())
        //            {
        //                for (int i = 0; i < reader.FieldCount; i++)
        //                {
        //                    PropertyInfo property = item.GetType().GetProperty(reader.GetName(i));
 
        //                    if (property != null && !reader.GetValue(i).Equals(DBNull.Value))
        //                    {
        //                        ReflectionHelper.SetValue(property.Name, reader.GetValue(i), item);
        //                    }
        //                }
        //            }
        //        }
        //    }
        //    return item;
        //}
 
        //public IEnumerable<Employee> GetEmployees()
        //{
        //    List<Employee> employees = new List<Employee>();
 
        //    string sqlStatement = "select * from Employees order by EmployeeID";
 
        //    using (DbCommand comm = Db.GetSqlStringCommand(sqlStatement))
        //    using (IDataReader reader = this.Db.ExecuteReader(comm))
        //    {
        //        while (reader.Read())
        //        {
        //            Employee item = new Employee();
 
        //            for (int i = 0; i < reader.FieldCount; i++)
        //            {
        //                PropertyInfo property = item.GetType().GetProperty(reader.GetName(i));
 
        //                if (property != null && !reader.GetValue(i).Equals(DBNull.Value))
        //                {
        //                    ReflectionHelper.SetValue(property.Name, reader.GetValue(i), item);
        //                }
        //            }
        //            employees.Add(item);
        //        }
        //    }
        //    return employees;
        //} 
 
        #endregion
 
        #region -- 進階操作 - 使用 IRowMapper, MapBuilder<T>.BuildAllProperties() --
 
        //public Employee GetOne(int id)
        //{
        //    string sqlStatement = "select * from Employees where EmployeeID = @EmployeeID";
 
        //    using (DbCommand comm = Db.GetSqlStringCommand(sqlStatement))
        //    {
        //        var param = comm.CreateParameter();
        //        param.ParameterName = "EmployeeID";
        //        param.Value = id;
        //        comm.Parameters.Add(param);
 
        //        using (IDataReader reader = this.Db.ExecuteReader(comm))
        //        {
        //            if (reader.Read())
        //            {
        //                // 把 reader 物件中的欄位值塞給 Category 物件的對應屬性
        //                IRowMapper<Employee> mapper = MapBuilder<Employee>.BuildAllProperties();
        //                Employee item = mapper.MapRow(reader);
        //                return item;
        //            }
        //            return null;
        //        }
        //    }
        //}
 
        //public IEnumerable<Employee> GetEmployees()
        //{
        //    List<Employee> employees = new List<Employee>();
 
        //    string sqlStatement = "select * from Employees order by EmployeeID";
 
        //    using (DbCommand comm = Db.GetSqlStringCommand(sqlStatement))
        //    using (IDataReader reader = this.Db.ExecuteReader(comm))
        //    {
        //        while (reader.Read())
        //        {
        //            IRowMapper<Employee> mapper = MapBuilder<Employee>.BuildAllProperties();
        //            Employee item = mapper.MapRow(reader);
        //            employees.Add(item);
        //        }
        //    }
        //    return employees;
        //} 
 
        #endregion
    }
 
    public class EmployeeIDParameterMapper : IParameterMapper
    {
        public void AssignParameters(DbCommand command, object[] parameterValues)
        {
            var param = command.CreateParameter();
            param.ParameterName = "EmployeeID";
            param.Value = parameterValues[0];
            command.Parameters.Add(param);
        }
    }
 
    public class EmployeeMapper : IRowMapper<Employee>
    {
        public Employee MapRow(IDataRecord reader)
        {
            Employee item = new Employee();
 
            for (int i = 0; i < reader.FieldCount; i++)
            {
                PropertyInfo property = item.GetType().GetProperty(reader.GetName(i));
 
                if (property != null &&
                    !reader.GetValue(i).Equals(DBNull.Value))
                {
                    ReflectionHelper.SetValue(property.Name, reader.GetValue(i), item);
                }
            }
            return item;
        }
    }
}

 

Web 專案裡使用 Sample.Repository.Dapper

先在 Sample.Web.MVC 專案裡加入 Sample.Repository.Dapper 的參考,

image

然後修改 Web.Config 裡 AppSettings 的 RepositoryType 內容,

image

上面兩個步驟都完成之後就可以直接執行 Sample.Web.MVC 網站,
(就這麼簡單,Web 專案的程式都沒有改到任何一行,不過要做到這樣,專案架構就得下功夫)

image

 

同樣的步驟,在 WebForm 專案裡也是做同樣的調整,

image

執行結果

image

 


 

如果你的專案已經是使用 ADO.NET 進行開發,當有改版的需求時(尤其是舊有 WebForm 的 Web Site 專案或 Web Application 專案要改成 ASP.NET MVC),應該有很多人在苦惱 Model 這個部分,因為舊有的專案有大部分都是從頭到尾使用弱型別,所以當要轉換成強型別時就會有很多問題,尤其是 ADO.NET 執行後的資料對映以及執行資料異動時的參數指定等,用傳統方式做資料與類別的對映,簡直是在考驗開發者的耐心與專注力,所以當你有遇到網站要從 WebForm 改版為 MVC 或 Web API 時,可以考慮改用 Dapper 作為處理資料存取的替代方案,尤其是你有滿山滿谷的 T-SQL 必須要沿用的時候,你就會知道使用 Dapper 可以簡化不少程序與節省很多時間。

 

延伸閱讀

ASP.NET MVC Model 的設計與使用 twMVC#10(簡報)

ASP.NET MVC Model 的設計與使用 twMVC#10(影片)

ASP.NET MVC 的 Model 使用 ADO.NET

ASP.NET MVC 的 Model 使用 Enterprise Library 6 Data Access Application Block

ASP.NET MVC - 使用 Simple Injector 讓 Model 三選一

ASP.NET WebForm 使用分層的 Repository 類別庫專案

ASP.NET WebForm 使用 Simple Injector 選擇不同的 Repository

Github - kevintsengtw/ASPNET_MVC_WebForm_Repository_Sample

 

系列文章

另一種資料存取對映處理方式的選擇 - Dapper

Dapper 練習題 - 新增多筆或大量資料

Dapper 練習題 - 每個查詢的結果都要定義並對映一個類別嗎?(使用 dynamic)

 

以上

純粹是在寫興趣的,用寫程式、寫文章來抒解工作壓力