接連三篇有關 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 的 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」項目以下載範例程式。
專案內容
專案裡有一個 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 專案的參考,
再來就是專案使用 Nuget 加入 Dapper 套件
然後從 Sample.Repository.ADONET 專案裡複製「BaseRepository.cs」程式內容,所以 Sample.Repository.Dapper 專案裡也會有 BaseRepository 類別
新增 EmployeeRepository.cs 類別檔案,繼承 BaseRepository 和 Sample.Repository.Interface 的 IEmployeeRepository 介面,
接著實作內容,這邊只有做兩個查詢操作,一個是取得全部資料,另一個是取得指定 ID 的資料,這裡將會直接從 Sample.Repository.ADO.NET 的 EmployeeRepository 裡複製 SQL Command,
以下就是完成 Sample.Repository.Dapper 的 EmployeeRepository 類別的實作內容,
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 的參考,
然後修改 Web.Config 裡 AppSettings 的 RepositoryType 內容,
上面兩個步驟都完成之後就可以直接執行 Sample.Web.MVC 網站,
(就這麼簡單,Web 專案的程式都沒有改到任何一行,不過要做到這樣,專案架構就得下功夫)
同樣的步驟,在 WebForm 專案裡也是做同樣的調整,
執行結果
如果你的專案已經是使用 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 練習題 - 每個查詢的結果都要定義並對映一個類別嗎?(使用 dynamic)
以上
純粹是在寫興趣的,用寫程式、寫文章來抒解工作壓力