2015年9月30日 星期三

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

ASP.NET MVC 與 ASP.NET WebForm 使用 Simple Injector 切換選擇不同 Repository.
文章:
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 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)

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。