[ASP.NET Core 6 MVC] 使用 Dapper
這篇是參考自 Code Maze : Using Dapper with ASP.NET Core Web API 的教學,練習後自己做了一個筆記。以下使用 Visual Studio 2022,建立專案為 ASP.NET Core Web 應用程式 (Model-View-Controller)使用 .NET 6.0 架構,方案名稱為 DemoDapper。以下會講得比較瑣碎詳細,因為以前開始學 .NET 4.8 MVC 時欠缺這樣 step by step 的懶人包,希望可以幫到一些初學者。(以下忽略 null 值判斷與 try catch 處理)
安裝 Dapper 套件
在「方案總管」視窗,對「相依性」按滑鼠右鍵,點選「管理 NuGet 套件」(或是「工具」,「NuGet 套件管理員」點選「管理方案的 NuGet 套件」)
於「瀏覽」標籤頁搜尋以下兩個,選擇適合的版本安裝,下圖是已安裝好的標籤頁:
- Dapper 作者 Sam Safforn, Marc Gravell, Nick Craver
- System.Data.SqlClient 作者 Microsoft
常常看教學還要安裝一個和 Configuration 相關的,但我安裝以上兩個套件即可。
取得連線字串和設定 appsettings.json
以我的本機 MS SQL 為例,機器「.」使用Windows 驗證登入,有一個資料庫叫「dbtest」:
回到 Visual Studio,點選「檢視」,「伺服器總管」,在伺服器總管視窗的「資料連接」上或空白處按滑鼠右鍵,點選「加入連接」:
「選擇資料來源」頁面,「資料來源」選擇 Microsoft SQL Server,「資料提供者」選擇 .NET Framework Data Provider for SQL Server,點選繼續:
「加入連接」頁面在「伺服器名稱」填入「.」驗證選 Windows 驗證,然後下方「選取或輸入資料庫名稱」就可以下拉找資料庫了,我選了前面說的 dbtest 資料庫。當然如果 SQL Server 在別台機器甚至要登入的,就和登入一樣選擇 SQL Server 驗證並填入帳號密碼:
左下可以點「測試連接」,確認無誤後點「確定」。
再回到伺服器總管,會出現該資料庫,點開可確認是該資料庫的資料表,在連接名稱上按滑鼠右鍵選擇「屬性」,或是點選連接後按 F4:
在屬性視窗,有「連接字串」的值,將它全選複製。
然後在方案總管視窗找到並開啟檔案 appsettings.json:
原本的內容:
加上 "ConnectionStrings" (VS 2022 很聰明會自動幫忙列出,可以用點選的)屬性,內容自己加上一個 key value 組,key 名可以自取,value 是剛剛複製的連接字串,下面我命名為 "DemoConnection":
如果對連線字串的格式熟練,也是可以自己寫這一串。好了後將 appsettings.json 存檔即可。
新增介面與類別檔案
接下來有三種 .cs 類別檔案要新增,這裡我偷懶全部放在 ~/Models 資料夾裡。第一種是因為資料庫中有 Products 和 Orders 兩張資料表:
所以要有 Product 和 Order 兩個類別,類別屬性和資料表的欄位對應,也就是 DTO(Data Transfer Object)的概念,準備要在資料庫撈出資料後能接收並運用:
namespace DemoDapper.Models { public class Product { public int ProductId { get; set; } public string Name { get; set; } public int Price { get; set; } } public class Order { public int OrderId { get; set; } public int ProductId { get; set; } public int Quantity { get; set; } public DateTime OrderDate { get; set; } // 這個屬性名和 SQL 中的命名 Date 不同,之後撈資料要做 alias public string Name { get; set; } public int Price { get; set; } } }
第二種是 Dapper Context 物件,功用是用連線字串取得連線物件,然後連線物件有 Dapper 方法來操作資料庫:
using System.Data; using System.Data.SqlClient; // 取得 SqlConnection 連線物件需要引用的命名空間 namespace DemoDapper.Models { public class DapperContext { private readonly IConfiguration _configuration; private readonly string _connectionString; public DapperContext(IConfiguration configuration) // 建構時取得服務 { _configuration = configuration; _connectionString = configuration.GetConnectionString("DemoConnection"); } // 在 appsettings.json 中設定的連線字串名稱 public IDbConnection CreateConnection() { return new SqlConnection(_connectionString); } } }
第三種是處理 CRUD 邏輯的 service,在這裡我稱為 Factory,並且先做介面 IFactory 讓 ProductFactory 和 OrderFactory 來實作他:
namespace DemoDapper.Models { public interface IFactory<T> // 使用泛型,待實作時指定類別 { public T GetOne(int id); // 定義好三個方法,之後要實作出來 public IEnumerable<T> GetAll(); public void Create(T entity); } }
而 ProductFactory 中要實作這些方法,為了運作要使用上面寫的 DapperContext 物件來取得連線物件,而 DapperContext 物件使用注入方式,在建構時取得:
using Dapper; namespace DemoDapper.Models { public class ProductFactory : IFactory<Product> // 實作 IFactory 介面且泛型指定形別為 Product { private readonly DapperContext _context; public ProductFactory(DapperContext context) // 建構時取得 DapperContext 物件 { _context = context; } public Product GetOne(int id) // 依 ProductId 取得一個 Product 物件 { string sql = $"SELECT * FROM Products WHERE ProductId = {id}"; Product result = null; using(var connection = _context.CreateConnection()) { // 使用 DapperContext 物件回傳的連線物件 result = connection.Query<Product>(sql).FirstOrDefault(); } return result; } public IEnumerable<Product> GetAll() // 取得所有 Product 物件 { string sql = "SELECT * FROM Products"; IEnumerable<Product> result = null; using(var con = _context.CreateConnection()) { // 使用 DapperContext 物件回傳的連線物件 result = con.Query<Product>(sql); // 將取出資料依欄位與屬性名稱對應,傳回 IEnuermable<Product> 物件 } return result; } public void Create(Product entity) // 新增一個 Product 資料並寫入資料庫 { string sql = $"INSERT INTO Products (Name, Price) VALUES (@Name, @Price)"; using (var con = _context.CreateConnection()) { // 使用 DapperContext 物件回傳的連線物件 DynamicParameters paras = new DynamicParameters(); // 防 SQL Injection paras.Add("@Name", entity.Name); paras.Add("@Price", entity.Price); con.Execute(sql, paras); } return; } } }
OrderFactory 一樣要實做 GetOne、GetAll 和 Create 三個方法,但我這裡略過,增加了一個 Detail 方法,做為練習 Dapper join 語法
using Dapper; namespace DemoDapper.Models { public class OrderFactory : IFactory<Order> // 實作 IFactory 介面且泛型指定形別為 Order { private readonly DapperContext _context; public OrderFactory(DapperContext context) // 建構時取得 DapperContext 物件 { _context = context; } public IEnumerable<Order> Detail(int id) { // 因為 SQL 中 Order 資料表存放的欄位名稱叫 Date,要另外 AS 成 Order 類別的屬性 OrderDate 才收的到 string sql = "SELECT a.OrderId,a.Quantity,a.Date AS OrderDate,b.Name,b.Price FROM Orders AS a JOIN Products AS b on a.ProductId = b.ProductId"; IEnumerable<Order> result = null; using(var con = _context.CreateConnection()) { result = con.Query<Order>(sql); } return result; } public void Create(Order entity) { /* ... */ } public IEnumerable<Order> GetAll() { /* ... */ } public Order GetOne(int id) { /* ... */ } } }
要在 Program.cs 注入容器,使得 ProductFactory 和 OrderFactory 建構時能取得 DapperContext 物件:
using DemoDapper.Models; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddSingleton<DapperContext>(); // 注入服務
Controller 與 View
邏輯類別完成後,再來是 Controller 和 View,新增一個 ProductController,裡面的運作需要 ProductFactory 物件,靠建構時取得:
using DemoDapper.Models; using Microsoft.AspNetCore.Mvc; namespace DemoDapper.Controllers { public class ProductController : Controller { private readonly ProductFactory _factory; public ProductController(IFactory<Product> factory) // 建構時取得 IFactory<Product> 物件 { _factory = factory as ProductFactory; // 指定並轉型給 _factory 供使用 } public IActionResult List() // 列出所有 Product 資料 { IEnumerable<Product> result = _factory.GetAll(); return View(result); } public IActionResult Show(int id) // 依 id 取得一筆 Product 資料 { Product result = _factory.GetOne(id); return View(result); } [HttpGet] public IActionResult Add() // 新增一筆 Product 資料, { // Get 先 return View() 得畫面 return View(); } [HttpPost] public IActionResult Add(Product newProduct) { // Post 送出時新增到資料庫,並導向 List 的 View _factory.Create(newProduct); return RedirectToAction("List"); } } }
新增 OrderController ,新增一個 action Detail,一樣地需要的 OrderFactory 在建構時取得:
using DemoDapper.Models; using Microsoft.AspNetCore.Mvc; namespace DemoDapper.Controllers { public class OrderController : Controller { private readonly OrderFactory _factory; public OrderController(IFactory<Order> factory) // 建構時取得 IFactory<Order> 物件 { _factory = factory as OrderFactory; // 指定並轉型給 _factory 供使用 } public IActionResult Detail(int id) { IEnumerable<Order> result = _factory.Detail(id); return View(result); } } }
要在 Program.cs 注入容器使 Controller 建構時能取得實作了 IFactory<Product> 的 ProductFactory:
using DemoDapper.Models; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddScoped<IFactory<Product>, ProductFactory>(); builder.Services.AddScoped<IFactory<Order>, OrderFactory>();
剩下的 View 的程式碼就略過了,有需要的再上 GitHub 看,基本上就是把傳入的 model 列舉出來顯示,下面貼一些截圖:
~/Product/Add
~/Product/List
~/Order/Detail
留言
張貼留言