【VB.NET】WindowsフォームアプリケーションでDapperを使ってみた

Windowsフォームアプリケーション(VB.NET)でDapperを使用してみました。

Dapperを使用することでデータベースを検索して得られた結果を.NETのクラスに簡単にマッピングすることができます。

Dapperとは

Dapperはデータベーステーブルと.NETオブジェクトのマッピングを行なってくれるMicro ORMと呼ばれるものです。一般的なORMの機能であるクエリの自動生成機能は無く、自前でSQLを書く必要がありますが、その分軽量で高速に動作します。

IDbConnectionインターフェースを拡張し、データベースに問い合わせるための便利な拡張メソッドを提供します。

開発環境

  • Windows 10 Pro (64bit)
  • Microsoft Visual Studio Community 2019
    言語:VB.NET
    ターゲットフレームワーク:.NET Framework 4.7.2
  • SQLite3

データベースの準備

データベースはSQLite3を使用しました。VB.NETでSQLiteを扱う方法については以前に記事を書いたので良ければこちらを参照してください。

今回のサンプルプロジェクト用にテーブルとレコードの作成を行います。

テーブルの作成

列名データ型キー制約
idINTEGERPrimary Key
nameTEXTNOT NULL
ageINTEGER
emailTEXTNOT NULL UNIQUE

レコードの作成

idnameageemail
1ユーザー130abc@example.com
2ユーザー235hoge@example.com
3ユーザー352foo123@example.com
4ユーザー426hello-world2021@example.com
5ユーザー5 NULLdapper-sample@example.com

Dapperを使用してマッピングしてみる

ここから本題、VB.NETでDapperを使用してデータベーステーブルとクラスオブジェクトのマッピングを行なっていきます。

パッケージのインストール

Windowsフォームアプリケーション(.NET Framework)の新しいプロジェクトを作成した後、[プロジェクト] メニューの [Nugetパッケージの管理] を選択します。

検索ボックスに Dapper と入力して検索し、検索結果からDapper を選択してインストールボタンをクリックします。

マッピング用クラスの作成

データベースのテーブルとマッピングするクラスを定義しておきます。

Public Class User
    Public Property id As Long = 0
    Public Property name As String = String.Empty
    Public Property age As Long? = Nothing
    Public Property email As String = String.Empty
End Class

プロパティ名はテーブルの列名と一致させておきます。

SQLite3のINTEGER型は.NETのSystem.Int64になるっぽいのでidとageはLong型にしています。

またageはテーブル設計上NULLを許容するのでNULL許容型のLong?にしています。

全レコードの取得

検索条件を指定しないで全レコードを取得して、用意しておいたクラスのオブジェクトにマッピングしてみます。

Imports System.Data.SQLite
Imports Dapper

Using con = New SQLiteConnection("data source=.\test.sqlite3")
    ' データベースオープン
    con.Open()
    ' 全レコードの検索 + 結果をオブジェクトにマッピング
    Dim result = con.Query(Of User)("select id, name, age, email from users")
    ' 結果の表示
    For Each rec In result
        Console.WriteLine($"id:{rec.id}, name:{rec.name}, age:{If(rec.age.HasValue, CStr(rec.age), "NULL")}, email:{rec.email}")
    Next
End Using

Dapperの拡張メソッドを呼び出すためにImportsステートメントで「Dapper」を指定しておきます。

IDbConnectionのQuery拡張メソッドにマッピング用クラスのUser型を指定して、SQLを第一引数に与えて実行するとIEnumerable(Of User)型で結果が戻ります。

実行結果

id:1, name:ユーザー1, age:30, email:abc@example.com
id:2, name:ユーザー2, age:35, email:hoge@example.com
id:3, name:ユーザー3, age:52, email:foo123@example.com
id:4, name:ユーザー4, age:26, email:hello-world2021@example.com
id:5, name:ユーザー5, age:NULL, email:dapper-sample@example.com

テーブルの内容がUser型のオブジェクトに正しくマッピングされました。

検索条件を指定する

検索条件を指定するときの書き方です。

Using con = New SQLiteConnection("data source=.\test.sqlite3")
    con.Open()
    Dim result = con.Query(Of User)("select id, name, age, email from users where name = @name",
                                    New With {.name = "ユーザー3"})
End Using

SQLに@で始まるパラメータを定義しておき、Queryメソッドの第2引数に匿名型でパラメータの値を設定します。SQLのパラメータ名と匿名型のメンバ変数名を合わせておく必要があります。

この方法はSQLインジェクションに対して安全になっています。

マッピングするクラスのプロパティを読み取り専用にするには

クラスのプロパティのパラメータを受け取るコンストラクタを定義しておけば、読み取り専用プロパティにもマッピングしてくれるようです。

' マッピング用クラス
Private Class User
    Public ReadOnly Property id As Long
    Public Property name As String
    Public Property age As Long?
    Public Property email As String

    Public Sub New(id As Long, name As String, age As Long?, email As String)
        Me.id = id
        Me.name = name
        Me.age = age
        Me.email = email
    End Sub
End Class

プロパティ「id」を読み取り専用に変更しました。更にコンストラクタで全パラメータを受け取り、プロパティにセットするコードを追加します。

コンストラクタの引数名と順番はデータベースのテーブル定義と一致させておかないとエラーになりました。また、上記サンプルコードではコンストラクタをPublicで宣言していますが、Privateにしてもマッピングしてくれました。

Dapperの使い方に変更はありません。↓

Imports System.Data.SQLite
Imports Dapper

Using con = New SQLiteConnection("data source=.\test.sqlite3")
    ' データベースオープン
    con.Open()
    ' 全レコードの検索 + 結果をオブジェクトにマッピング
    Dim result = con.Query(Of User)("select id, name, age, email from users")
End Using

オブジェクトをイミュータブル(状態が不変)にしたい場合はこの方法を採用すれば良いと思います。

テーブルの列名とクラスのプロパティ名について

テーブルの列名とクラスのプロパティ名を一致させなくても、SQLで列に対して別名(クラスのプロパティと同じ名前)を付けてやればマッピングできます。

列名データ型キー制約
idINTEGERPrimary Key
nameTEXTNOT NULL
ageINTEGER
emailTEXTNOT NULL UNIQUE
テーブルの定義情報
' マッピング用クラス
Public Class User
    Public Property id As Long = 0
    Public Property name As String = String.Empty
    Public Property age As Long? = Nothing
    Public Property mailAddress As String = String.Empty
End Class
Dim result = con.Query(Of User)("select id, name, age, email as mailAddress from users")

ちなみに、前項の「マッピングするクラスのプロパティを読み取り専用にする場合」のように、コンストラクタで全パラメータを受け取ってプロパティにセットするようにしておけば上記のような措置をしなくてもマッピングできました。

Dapperを使用しない場合の書き方

Dapperを使用しないで手動でマッピングを行なう場合のソースコードも載せておきます。

Using con = New SQLiteConnection("data source=.\test.sqlite3")
    ' データベースオープン
    con.Open()
    Using cmd = con.CreateCommand()
        cmd.CommandText = "select id, name, age, email from users"
        ' 検索結果格納用リスト
        Dim result = New List(Of User)()
        ' SQL実行
        Using dr = cmd.ExecuteReader()
            ' 1レコードずつ、フィールドの数だけ型変換してマッピングしていく
            While (dr.Read())
                result.Add(New User() With {.id = CLng(dr("id")),
                                            .name = CStr(dr("name")),
                                            .age = If(dr("age") Is DBNull.Value, Nothing, CType(dr("age"), Long?)),
                                            .email = CStr(dr("email"))})
            End While
        End Using

        ' 結果の表示
        For Each rec In result
            Console.WriteLine($"id:{rec.id}, name:{rec.name}, age:{If(rec.age.HasValue, CStr(rec.age), "NULL")}, email:{rec.email}")
        Next
    End Using    
End Using

Dapperを使用したときと比べてコード量がかなり増えました。今回のサンプルではテーブルの列数が少ないのでマシですが、列数が多いと書くのが大変です。

最後に

普段は自分でSQLを書いて、手動でクラスにマッピングするコードを書いていましたがDapperを使ってみて便利だったので今後使用していきたいと思いました。

ORMのEntityFrameworkは使用したことがないのですが、少し調べたところ複雑そうで取っつきにくい印象を受けました。Dapperは直感的でわかりやすく、SQLは自力で書くという点が自分には合っていました。

今回はDapperの簡単なマッピングの部分だけを試しましたが、動的なSQLの書き方など他にも色々調べてやってみようと思います。EntityFrameworkもそのうち試してみたいです。

タイトルとURLをコピーしました