出会い
2017年末に久しぶりにC#でWebアプリ開発のPJに加わることになった。
SilverLight × Spring .NETで書いたように、前回は一切O/Rマッパーを使わないPJだったので、
初の.NETでのO/Rマッパーになる。
Dapperとは
Dapper is a micro ORM born in 2011, ... It is one of the fastest and simplest around, yet it is very extensible and, above all, well adopted and used in very high-performance websites. More specifically has been developed and is maintained by the guys behind StackOverflow, so you can be sure it is battle-tested. Source code is on GitHub and development is very active. All these reasons made it my micro ORM of choice.
- 2011年に誕生したO/Rマッパーで、高速でシンプルだが、とても拡張性がある。
- とてもハイパフォーマンスなウェブサイトで採用され、使用されている。
- また特にStackOverflowのメンバーによって開発・メンテナンスされている。
- なので、実践の場でよくテストされていると思ってもらって大丈夫だ。
- ソースコードはGitHubにあり、開発の状況も活発だ。
- そういった理由からこの軽量なORMを選択した。
.NETのアプリケーションで使用できるO/RマッパーでASP.NET MVCなどの.NET系のフレームワークで動作する。
SQLは、C#のクラス側に書いて使用する。SQL実行時にパラメータをマッピングしてくれたり、Select結果をモデルにマッピングしてくれる。Javaでいえば、JDBCにSelect結果のマッピング機能が付いたぐらい感覚で理解している。
派生形でDapper PlusやDapper Contribもあるようだが、こちらはBulkInsertできたり、Entity単位での操作といったJPAっぽい機能もある。
Dapper Plusは、
This library is NOT FREE, but a monthly trial is available at the start of every month.
と記載があり、今のところオープンソースではない。
振り返ってみて
参画して2年弱経つが、直前のPJがSpring Data JPAでの開発だったので、
JPAのネイティブクエリ実装ぐらいの感覚でいる。
JavaもC#もサーバーサイドの特にDB寄りのレイヤーの実装って書き方が違うぐらいで、
基本やってることはあんまり変わらないと思っている。
それって根本的な技術要素のレベルで、サーバーサイドの実装が固定化されているからなのかもしれない。
・データを格納するのは、RDMS。
・データをDBに対してCRUDするロジック部分は、MVCモデル。
SQLも直接書くか、エンティティやルールに基づいて自動発行されるかの2通りしかなく、小規模な改善は続けられているものの、根本的なところでは行き着くところまで行き着いた世界なのかもしれない。
そんなことを考えながら、ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところを見させていただいたところ、javaであれば、JOOQが今風なO/Rマッパーとのこと、以下引用させていただくと、
- タイプセーフなJavaコーディングでCRUDを書く
- ミスったらコンパイルエラー
- DBにピッタリ合わせたJavaコードでCRUDを書く
- DB変更の影響範囲がコンパイルエラーでわかる
- 上記を実現するために、
- テーブル作成済みのDBサーバから自動的にメタデータを読み取って、
- Javaソースコードを自動生成
確かにSQLを直書きする実装だと実行するまで、SQL自体のミスに気が付かないし、
DB側の変更に追随し忘れていたりすることは、複数名でPJやってるとよくあることだったりする。
実務で使ってないので何とも言えないし、言語は違うが、Dapperより進化した場所にあるものかもしれない。
サンプルで実装してみる。
こちらの記事を参考にさせていただきました。
- 公式チュートリアル
- 【Dapper】公式サイトのチュートリアルを翻訳・まとめ
- C#でApp.ConfigのConnectionStringを取得する
- [ C# ] アプリケーション構成ファイル(App.config)のパラメータ値を読み込む
接続文字列は、App.Configに記載しておきます。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="Npgsql" connectionString="User ID=postgres;Password=xxx;Host=localhost;Port=5432;Database=postgres;Pooling=true;" />
</connectionStrings>
</configuration>
Dapperクラスは、こんな感じです。
NuGetにて「Dapper」「Npgsql」「Configuration」を追加しています。
using Dapper;
using DapperSample.Model;
using Npgsql;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
namespace DapperSample
{
class SampleDapper
{
public static SampleDapper Instance = new SampleDapper();
private SampleDapper() { }
public Worker Select(string id)
{
var worker = new Worker();
using (var connection = new NpgsqlConnection(ConfigurationManager.ConnectionStrings["Npgsql"].ConnectionString))
{
var sql = "select * from worker where id = @id";
worker = connection.Query<Worker>(sql, new { id}).FirstOrDefault();
}
return worker;
}
public IEnumerable<Worker> Select() {
var workerList = new List<Worker>();
using (var connection = new NpgsqlConnection(ConfigurationManager.ConnectionStrings["Npgsql"].ConnectionString))
{
var sql = "select * from worker";
workerList = connection.Query<Worker>(sql).ToList();
}
return workerList;
}
public int Insert(string id, string name, int age, string department)
{
var result = 0;
using (var connection = new NpgsqlConnection(ConfigurationManager.ConnectionStrings["Npgsql"].ConnectionString))
{
var sql = "insert into worker (id, name, age, department) values (@id, @name, @age, @department)";
result = connection.Execute(sql, new { id, name, age, department });
}
return result;
}
public int Update(string id, int age)
{
var result = 0;
using (var connection = new NpgsqlConnection(ConfigurationManager.ConnectionStrings["Npgsql"].ConnectionString))
{
var sql = "update worker set age = @age where id = @id";
result = connection.Execute(sql, new { id, age});
}
return result;
}
public int Delete(string id)
{
var result = 0;
using (var connection = new NpgsqlConnection(ConfigurationManager.ConnectionStrings["Npgsql"].ConnectionString))
{
var sql = "delete from worker where id = @id";
result = connection.Execute(sql, new { id });
}
return result;
}
}
}
呼び出し側のServiceクラスです。
using System;
using System.Linq;
namespace DapperSample.Service
{
class SampleService
{
public static SampleService Instance = new SampleService();
private static SampleDapper SampleDapper = SampleDapper.Instance;
private SampleService() { }
public void Execute()
{
//新規登録
SampleDapper.Insert("0001", "k.jarrett", 73, "music");
SampleDapper.Insert("0002", "c.corea", 77, "music");
//検索
var workerList = SampleDapper.Select();
foreach (var worker in workerList)
{
Console.WriteLine($"Id = {worker.Id } Name = {worker.Name } Age = {worker.Age } Department = {worker.Department }");
}
//更新
SampleDapper.Update("0001", 74);
SampleDapper.Update("0002", 78);
//検索
var jarrett = SampleDapper.Select("0001");
var corea = SampleDapper.Select("0002");
Console.WriteLine($"jarrett => Id = {jarrett.Id } Name = {jarrett.Name } Age = {jarrett.Age } Department = {jarrett.Department }");
Console.WriteLine($"corea => Id = {corea.Id } Name = {corea.Name } Age = {corea.Age } Department = {corea.Department }");
//削除
SampleDapper.Delete("0001");
SampleDapper.Delete("0002");
workerList = SampleDapper.Select();
if (workerList.Count() == 0) {
Console.WriteLine("No Worker.");
}
}
}
}
実行結果です。
Id = 0001 Name = k.jarrett Age = 73 Department = music
Id = 0002 Name = c.corea Age = 77 Department = music
jarrett => Id = 0001 Name = k.jarrett Age = 74 Department = music
corea => Id = 0002 Name = c.corea Age = 78 Department = music
No Worker.
サンプルプロジェクトごとGitHubにプッシュしています。
https://github.com/TsJazz27Sumin/DapperSample/tree/master/DapperSample