接連三篇有關 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.NETASP.NET MVC 的 Model 使用 Enterprise Library 6 Data Access Application Block
ASP.NET MVC - 使用 Simple Injector 讓 Model 三選一
再來就是請到以下的 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)
以上
沒有留言:
張貼留言