13
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】Dapper忘備録【応用編】

Last updated at Posted at 2024-12-25

Qiita Advent Calendar 4回目の参加です。よろしくお願いします。※遅刻しました。:bow_tone2:
Dapperに関する応用的な内容をまとめました。

2年前に書いたDapper忘備録【基本編】の内容がベースとなっているため、Dapperとはなんぞや?という方はこちらを先にご覧ください。

Dapper とは 

.NETプラットフォーム用のマイクロORM(Object-relational mapping)です。
Dapperを使用する場合、SQLとマッピング用のクラスは自力で書く必要があります。SQLを書けることが前提のオブジェクトマッパーです。

:mag:マッピングのイメージ図
image.png

下準備:tokyo_yushun テーブルの作成

以下のようなテーブルをSQLiteで作成しました。
※日本語と英語バラバラですがご容赦ください。

NO カラム名 日本語名 備考
1 kaisu 回数 INTEGER NOT NULL UNIQUE制約
2 wakuban 枠番 INTEGER NOT NULL UNIQUE制約
3 umaban 馬番 INTEGER NOT NULL UNIQUE制約
4 bamei 馬名 TEXT NOT NULL
5 seibetu 性別 TEXT NOT NULL
6 barei 馬齢 INTEGER NOT NULL
7 kinryo 斤量 REAL NOT NULL
8 kisyu 騎手 TEXT NOT NULL
9 kyusya 厩舎 TEXT NOT NULL
10 createdate 作成日 TEXT NOT NULL
11 updatedate 更新日 TEXT NOT NULL

下準備:dubai_sheema_classic テーブルの作成

以下のようなテーブルをSQLiteで作成しました。
※日本語と英語バラバラですがご容赦ください。

NO カラム名 日本語名 備考
1 kaisu 回数 INTEGER NOT NULL UNIQUE制約
2 wakuban 枠番 INTEGER NOT NULL UNIQUE制約
3 umaban 馬番 INTEGER NOT NULL UNIQUE制約
4 bamei 馬名 TEXT NOT NULL
5 seibetu 性別 TEXT NOT NULL
6 barei 馬齢 INTEGER NOT NULL
7 kinryo 斤量 REAL NOT NULL
8 kisyu 騎手 TEXT NOT NULL
9 kyusya 厩舎 TEXT NOT NULL
10 createdate 作成日 TEXT NOT NULL
11 updatedate 更新日 TEXT NOT NULL

型変換の重要性

※今回使用したデータベースはSQLiteのため、SQLite⇔C#間の変換を行うことを前提とした説明になります。
データベースとクラスのマッピングでは、互いの型が変換できるかどうかが重要となります。取得(SELECT)であれ、追加(INSERT)であれ、更新(UPDATE)であれ、型変換が成功しない場合はデータのやり取りができないからです。

image.png

しかし、暗黙的型変換ができるのならば、それでいい

SQLiteのデータ型

SQLiteのデータ型は下記の4つとなります。※厳密にはNULLが含まれるようなのですが、SQLiteの仕様については解説しないため、詳しくは公式ドキュメントを参照してください。

  • INTEGER(符号付き整数)
  • REAL(浮動小数点数)
  • TEXT(文字列)
  • BLOB(入力されたままの形式で格納されるデータの塊)

暗黙的型変換

マッピングクラスのプロパティをプリミティブ型で定義している場合、SQLite⇔C#で暗黙的型変換が実施されます。
※型変換の組み合わせ一覧は下記のリンクにあります。

この場合、何も設定せずともDapperを通してデータのやり取りができるため、とても楽です。

image.png

最初に準備した tokyo_yushun テーブルのデータを暗黙的型変換でやり取りする場合、C#側は下記の変換ルールに基づいてクラスを定義しておきます。これにより、Queryでデータを取得したり、Executeでデータを追加したりすることができます。

SQLite .NET
INTEGER int
REAL double
TEXT string
CREATE TABLE tokyo_yushun
(
	kaisu INTEGER NOT NULL,  
	wakuban INTEGER NOT NULL,
	umaban INTEGER NOT NULL,
	bamei TEXT NOT NULL,
	seibetu TEXT NOT NULL,
	barei INTEGER NOT NULL,
	kinryo REAL NOT NULL, -- REAL型で小数点以下の桁数を指定
	kisyu TEXT NOT NULL,
	kyusya TEXT NOT NULL,
	createdate TEXT NOT NULL,
	updatedate TEXT NOT NULL,
	UNIQUE(kaisu, wakuban, umaban) -- UNIQUE制約で回数、枠番、馬番の組み合わせが一意になるようにする
);
DTOクラス
/// <summary>
/// 東京優駿(日本ダービー)のデータを格納するクラス(取得のみ使用)
/// </summary>
public class TokyoYushunDTO()
{
    /// <summary>
    /// 回数
    /// </summary>
    public int Kaisu { get; set; }

    /// <summary>
    /// 枠番
    /// </summary>
    public int Wakuban { get; set; }

    /// <summary>
    /// 馬番
    /// </summary>
    public int Umaban { get; set; }

    /// <summary>
    /// 馬名
    /// </summary>
    public string Bamei { get; set; }

    /// <summary>
    /// 性別
    /// </summary>
    public string Seibetu { get; set; }

    /// <summary>
    /// 馬齢
    /// </summary>
    public int Barei { get; set; }

    /// <summary>
    /// 斤量
    /// </summary>
    public double Kinryo { get; set; }

    /// <summary>
    /// 騎手
    /// </summary>
    public string Kisyu { get; set; }

    /// <summary>
    /// 厩舎
    /// </summary>
    public string Kyusya { get; set; }

    /// <summary>
    /// 作成日付
    /// </summary>
    public string CreateDate { get; set; }

    /// <summary>
    /// 更新日付
    /// </summary>
    public string UpdateDate { get; set; }
}

型変換とカスタム型ハンドラー

暗黙的型変換が使えるようにすべてプリミティブ型で定義してやり取りをしたいところですが、実際はそうもいかないことが多いと思います。プリミティブ型で定義した場合、必要な情報や機能を加えることができないからです。

カスタム型(自作クラス)を作る

tokyo_yushunwakuban を例にして考えます。競馬における「枠番」のことです。

  • 内枠、外枠がある
  • 枠によって帽子の色が異なる
枠番クラス
/// <summary>
/// 枠番
/// </summary>
public record struct Wakuban
{
    /// <summary>
    /// 馬番の値
    /// </summary>
    public readonly int Value;

    /// <summary>
    /// 内枠かどうか
    /// </summary>
    public readonly bool IsInnerFrame;

    /// <summary>
    /// 枠番の色
    /// </summary>
    public WakubanColor Color;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="value">数値</param>
    public Wakuban(int value)
    {
        Value = value;
        IsInnerFrame = value <= 4;
        Color = (WakubanColor)value;
    }

    /// <summary>
    /// 枠番に変換します。
    /// </summary>
    /// <param name="value">変換対象の数値</param>
    /// <returns>枠番</returns>
    public static Wakuban Parse(int value)
    {
        return new Wakuban(value);
    }
}

さらに tokyo_yushunumaban についても考えます。競馬における「馬番」のことです。

  • 奇数番、偶数番がある
  • 基本は奇数番からゲートインする
馬番クラス
/// <summary>
/// 馬番
/// </summary>
public record struct Umaban
{
    /// <summary>
    /// 馬番の値
    /// </summary>
    public readonly int Value;

    /// <summary>
    /// 先入れかどうかを示す値 <br />
    /// 奇数番の馬から先にゲートに入ります。
    /// </summary>
    public readonly bool IsFirstInserted;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="value">数値</param>
    public Umaban(int value)
    {
        Value = value;
        IsFirstInserted = value % 2 != 0;
    }

    /// <summary>
    /// 馬番に変換します。
    /// </summary>
    /// <param name="value">変換対象の数値</param>
    /// <returns>馬番</returns>
    public static Umaban Parse(int value)
    {
        return new Umaban(value);
    }
}

カスタム型ハンドラーを作る

カスタム型を作成しただけでは、Dapperを通じてやり取りすることができません。カスタム型の変換ルールを作成してDapperに伝える必要があります。

image.png

変換ルールの作成はDapperの SqlMapper.TypeHandler<T> または SqlMapper.StringTypeHandler<T> を使用します。StringTypeHandler<T>TypeHandler<T> を継承しています。
抽象クラスかつ抽象メソッドを持つため、一部のメソッドはオーバーライドをして実装します。

クラス名 概要 実装するメソッド
SqlMapper.TypeHandler<T> 汎用型ハンドラ SetValue(IDbDataParameter parameter, T? value)Parse(object value)
SqlMapper.StringTypeHandler<T> 文字列専用の型ハンドラ Parse(string xml)Format(T xml)

ここでは SqlMapper.TypeHandler<T> を使用します。

メソッド名 概要 主な処理
Parse カスタム型の値をデータベースに保存するため、適切なデータベース型に変換する INSERT
SetValue データベースから取得した値をカスタム型に変換する SELECT
/// <summary>
/// Wakubanの型ハンドラー
/// </summary>
public class WakubanTypeHandler : SqlMapper.TypeHandler<Wakuban>
{
    /// <summary>
    /// カスタム型をdapperの型に変換します。
    /// </summary>
    /// <param name="parameter">パラメータのDbType</param>
    /// <param name="value">設定する値</param>
    public override void SetValue(IDbDataParameter parameter, Wakuban value)
    {
        parameter.DbType = DbType.Int64;
        parameter.Value = value.Value;
    }

    /// <summary>
    /// dapperの型をカスタム型に変換します。
    /// </summary>
    /// <param name="value">データベースから取得した値</param>
    /// <returns><see cref="Wakuban"/>が戻ります。</returns>
    public override Wakuban Parse(object value)
    {
        if (value is int intValue)
        {
            return new Wakuban(intValue);
        }
        else if (value is long longValue)
        {
            return new Wakuban((int)longValue);
        }
        else
        {
            throw new ArgumentException("対応していない型です。");
        }
    }
}
/// <summary>
/// UmaBanの型ハンドラー
/// </summary>
public class UmabanTypeHandler : SqlMapper.TypeHandler<Umaban>
{
    /// <summary>
    /// カスタム型をdapperの型に変換します。
    /// </summary>
    /// <param name="parameter">パラメータのDbType</param>
    /// <param name="value">設定する値</param>
    public override void SetValue(IDbDataParameter parameter, Umaban value)
    {
        parameter.DbType = DbType.Int32;
        parameter.Value = value.Value;
    }

    /// <summary>
    /// dapperの型をカスタム型に変換します。
    /// </summary>
    /// <param name="value">データベースから取得した値</param>
    /// <returns><see cref="Umaban"/>が戻ります。</returns>
    public override Umaban Parse(object value)
    {
        if (value is int intValue)
        {
            return new Umaban(intValue);
        }
        else if (value is long longValue)
        {
            return new Umaban((int)longValue);
        }
        else
        {
            throw new ArgumentException("対応していない型です。");
        }
    }
}

カスタム型ハンドラーの呼び出し

Dapperでクエリを実行する前にカスタム型ハンドラーを追加しておく必要があります。
Program.cs などの上位で呼び出すようにします。

/// <summary>
/// Dapperのカスタム型ハンドラーを設定するクラス
/// </summary>
public class TypeHandlerSettings
{
    /// <summary>
    /// Dapperのカスタム型ハンドラーを設定します。
    /// </summary>
    public static void ConfigureDapperTypeHandlers()
    {
        // UmaBanの型ハンドラーを追加
        SqlMapper.AddTypeHandler(new UmabanTypeHandler());

        // Wakubanの型ハンドラーを追加
        SqlMapper.AddTypeHandler(new WakubanTypeHandler());
    }
}
class Program
{
    static void Main(string[] args)
    {
        // 型ハンドラーの設定
        TypeHandlerSettings.ConfigureDapperTypeHandlers();

        // 型ハンドラーを使用した取得、追加
        TokyoYushunRepository.Execute();
    }

}

カスタムマッピング

追記します。

まとめ

記事内で使用したサンプルコードはこちら▶ CSharpDapperAndSQLSample

最後まで見ていただきありがとうございました。

参考URL

13
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?