はじめに
前回は、Npgsql
を使ってPostgreSQL
に接続し、Npgsql.EntityFrameworkCore.PostgreSQL
という.NET
用のORMを使ってデータのやり取りを行った。
今回は、この部分をDapper
を使ってやり取りを行ってみる。
環境
Windows 11 Professional
Docker Desktop 4.34.3 (170107)
ASP.NET Core 8.0
PostgreSQL 17.0
Dapper 2.1.35
Dapperとは
https://github.com/DapperLib/Dapper
Dapper
は、.NET
環境で使用される軽量なORM
(オブジェクト・リレーショナル・マッピング)ライブラリの一つである。
Microsoft
が提供するEntity Framework
とは異なり、Dapper
は高速でシンプルな操作を特徴とし、開発者がSQLクエリを直接記述するスタイルをサポートする。
特徴
高速で効率的:
Dapper
は、ADO.NET
をラップする形で動作しており、SQLクエリの実行速度が速く、パフォーマンスの最適化が容易。シンプルな構文: 複雑な設定を必要とせず、数行のコードでデータベース操作が可能。
データベースの柔軟な操作: SQLクエリを直接使用できるため、開発者はデータベースの制御を細かく行える。
マッピングが簡単: データベースの行データを
C#
オブジェクトに自動でマッピングしてくれるため、コードの可読性が高まる。
準備
PostgreSQLの準備
ASP.NET Core WebアプリでNpgsqlを使いPostgreSQLに接続する#準備 を参照する。
Dapperを入れる
- プロジェクトを右クリック、「NuGetパッケージの管理」を選択する
- 「
Dapper
」をインストールする - インストール完了後、依存関係に 「
Dapper
」が入っていることを確認
Dapperを使ってPostgreSQLにアクセスをする
前回作成した、appsettings.json
の接続情報を使う。users
テーブルの一覧を列挙するということを行いたいので、リポジトリパターンで実装する。
※ASP.NET Coreのベストプラクティス的なものはわからないがとりあえず。
今回修正した内容は以下になる。
https://github.com/katsuobushiFPGA/ASPCoreNetSample/commit/1a1f17bf96879e17e03f76dd6394c0585a2a96f3
Modelsクラスを作成
前回は、Entity
ディレクトリを作成してその中に入れた。
今回は、Models
に作るのが正解っぽさそう。
※この辺あまりよくわかっていないが、使用するライブラリによって違うかも。
プロパティ
と、テーブルのカラム名が一致するように定義した。
※一致しないように定義する場合は、EntityFramework
でやっていたようにプロパティの前に、対応するカラム名をマッピング定義する必要がある。
FYI: https://qiita.com/hi_zacky/items/64fbc685e3beaac931e6
namespace WebApplication2.Models
{
public class User
{
public int id { get; set; } // ユーザーID
public string username { get; set; } // 名前
public string email { get; set; } // メールアドレス
public string password_hash { get; set; } // パスワード
public DateTime? created_at { get; set; } // 作成日時
}
}
Repositoryパターンに必要なものの作成
Repository
ディレクトリを作成し以下のファイルを作成する。
using WebApplication2.Models;
namespace WebApplication2.Repository
{
public interface IUserRepository
{
Task<IEnumerable<User>> GetUsersAsync();
Task<User> GetUserByIdAsync(int id);
System.Threading.Tasks.Task AddUserAsync(User user);
System.Threading.Tasks.Task UpdateUserAsync(User user);
System.Threading.Tasks.Task DeleteUserAsync(int id);
}
}
using Context;
using Dapper;
using WebApplication2.Models;
namespace WebApplication2.Repository
{
public class UserRepository : IUserRepository
{
private readonly ApplicationDbContext _context;
public UserRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<IEnumerable<User>> GetUsersAsync()
{
var sql = "SELECT * FROM users";
return await _context.Connection.QueryAsync<User>(sql);
}
public async Task<User> GetUserByIdAsync(int id)
{
var sql = "SELECT * FROM users WHERE Id = @Id";
return await _context.Connection.QueryFirstOrDefaultAsync<User>(sql, new { Id = id });
}
public async System.Threading.Tasks.Task AddUserAsync(User user)
{
var sql = "INSERT INTO users (username, email) VALUES (@username, @email)";
await _context.Connection.ExecuteAsync(sql, user);
}
public async System.Threading.Tasks.Task UpdateUserAsync(User user)
{
var sql = "UPDATE users SET username = @username, email = @email WHERE Id = @Id";
await _context.Connection.ExecuteAsync(sql, user);
}
public async System.Threading.Tasks.Task DeleteUserAsync(int id)
{
var sql = "DELETE FROM users WHERE Id = @Id";
await _context.Connection.ExecuteAsync(sql, new { Id = id });
}
}
}
ApplicationDbContext.csの修正
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace Context
{
public class ApplicationDbContext : DbContext
{
public DbSet<Entity.User> Users { get; set; }
public DbSet<Entity.Task> Tasks { get; set; }
private IDbConnection _connection;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public IDbConnection Connection
{
get
{
if (_connection == null)
{
_connection = Database.GetDbConnection();
}
return _connection;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity.User>()
.ToTable("users");
// tasksテーブルのUserへの外部キー制約を設定
modelBuilder.Entity<Entity.Task>()
.ToTable("tasks")
.HasOne(t => t.User)
.WithMany(u => u.Tasks)
.HasForeignKey(t => t.UserId)
.OnDelete(DeleteBehavior.Cascade);
base.OnModelCreating(modelBuilder);
}
}
}
Program.csを修正
using Microsoft.EntityFrameworkCore;
using Context;
using WebApplication2.Repository;
var builder = WebApplication.CreateBuilder(args);
// PostgreSQLへの接続を設定
var connectionString = builder.Configuration.GetConnectionString("PostgreSqlConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(connectionString));
// for Dapper DIコンテナへの登録
builder.Services.AddScoped<IUserRepository, UserRepository>();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
usersテーブルを取得するコントローラーを作成する
using Microsoft.AspNetCore.Mvc;
using WebApplication2.Repository;
namespace WebApplication2.Controllers
{
public class UsersController : Controller
{
private readonly IUserRepository _userRepository;
public UsersController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
// GET: users
public async Task<IActionResult> Index()
{
var users = await _userRepository.GetUsersAsync();
return View(users); // ビューにタスクのリストを渡す
}
}
}
コントローラーに対応するビューを作成
asp-action
のタグを設置しているが、対応するコントローラーのメソッドがないので動作しないことに注意。
@model IEnumerable<User>
@{
ViewData["Title"] = "User List";
}
<h2>User List</h2>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model)
{
<tr>
<td>@user.id</td>
<td>@user.username</td>
<td>@user.email</td>
<td>
<a asp-action="GetUserById" asp-route-id="@user.id" class="btn btn-info">View</a>
<a asp-action="UpdateUser" asp-route-id="@user.id" class="btn btn-warning">Edit</a>
<a asp-action="DeleteUser" asp-route-id="@user.id" class="btn btn-danger">Delete</a>
</td>
</tr>
}
</tbody>
</table>
動作確認
「デバッグ」→「デバッグの開始」より起動する。
https://localhost:7006/users にアクセスし、DBの内容が表示されるかを確認する。
以下のようになっていればOK
参考
Learn Dapper
https://www.learndapper.com/【C#】Dapper忘備録【基本編】
https://qiita.com/YuMo_tea/items/ee4182260a0e3216237eDapperでカラム名とプロパティ名のケースが異なる場合のマッピング
https://qiita.com/hi_zacky/items/64fbc685e3beaac931e6【C#】Dapperの使い方
https://pg-life.net/csharp/dapper/Dapperの制限事項
https://learn.microsoft.com/ja-jp/dotnet/standard/data/sqlite/dapper-limitations
おわりに
ASP.NET Core, Dapper, のベストプラクティス的なものがわからないので、これを読んで理解を進めたい。
ASP.NET Core のベスト プラクティス
https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/best-practices?view=aspnetcore-8.0Dapper Best Practices: C# Developers’ Guide to Database Management
https://hackernoon.com/dapper-best-practices-c-developers-guide-to-database-management
また、Dapper
とEntity Framework
どちらが適しているかというのもの、あまり理解できていない。
これも下記の記事を読んでライブラリの特性を理解してみたいと思う。
- Dapper vs Entity Framework (EF6 or EF Core)
https://www.learndapper.com/dapper-vs-entity-framework