tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

C#史上最強なORマッパーを使ってみた

C#のORマッパーはEntity Framework(以下EF)をはじめ、Dapper、PetaPoco等が有名ですが、とにかくどれも微妙な完成度でRailsのActiveRecodeみたいなものがありません(Entity Frameworkがそれですが、とにかく遅い)。Dapperほどの速度が出てビルダーマッピングをしてくれるライブラリがないのかなと思って探したらありました。

sqlkata.com

見る限りだとビルダーを備えててDapper依存しているから確実に早いだろうと予測。Joinも含まれてるのでマッピングしてくれれば、おそらくC#史上最強なORマッパー。ってかなんて読むのこれ?えすきゅーえるかた?

使ってみます。

まずNugetから

SqlKataとSqlKata.Executionを落とします。現在(2/15時点)は1.1.6が最新です。

dotnet add package SqlKata --version 1.1.6
dotnet add package SqlKata.Execution --version 1.1.6

今回はSQLServerを使うのでSqlClientも取ってきます。

dotnet add package System.Data.SqlClient

SqlKataを使う準備

using SqlKata;
using SqlKata.Compilers;
using SqlKata.Execution;
using System.Data.SqlClient;

var connection = new SqlConnection("");
var compiler = new SqlServerCompiler();
var db = new QueryFactory(connection, compiler);

compilerはビルダーからSQLを生成するオブジェクトでdbを使ってビルダーを使います。

ビルダーを使う

まずはSELECT
db.Query("Posts").Select("Id", "Title", "CreatedAt as Date");

これが以下になります。

SELECT [Id], [Title], [CreatedAt] AS [Date] FROM [Posts]

次はJOIN

db.Query("Posts").Join("Authors", "Authors.Id", "Posts.AuthorId");

これが以下になります。

SELECT * FROM [Posts] INNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId]

ここら辺はドキュメントが豊富なので以下を参考にしてください。
sqlkata.com

データを取得する

なんどデータ取得も出来ます。Dapperにフル依存してるので。

取得にはGetを使います。

db.Query("Posts").Select("Id", "Title", "CreatedAt as Date");.Get()

これだと戻りがdynamicなのでジェネリックで指定します。

db.Query("Posts").Select("Id", "Title", "CreatedAt as Date");.Get<Posts>()

これでPostクラスの型でマッピングされます。

Joinしたデータを取得する

ビルダー備えてマッピングまでやるならJoinでいけるのか!?という疑問があったので試してみました。
クラスを用意します。

public class Hoge {
    public int Id { get; set; }
    public int PiyoId { get; set; }
    public Piyo Piyo { get; set; }
}

public class Piyo {
    public int Id { get; set; }
}

ビルダー

var query = db.Query(nameof(Hoge))
     .Join(nameof(Piyo), "Hoge.PiyoId", "Piyo.Id")
     .OrderBy("Hoge.Id").Get<Hoge>();

queryをみましたがPiyoは入ってませんでした。やっぱりC#ではまだ無理そうです。EFかDapperで頑張りましょう。

SQLを生成する

ビルダーとしてはかなり優秀なのでSQLを生成してみます。ちなみに以下ではSQLを生成出来ません。

db.Query("Posts").Select("Id", "Title", "CreatedAt as Date")

ビルダーからSQLコンパイルします。

var compiler = new SqlServerCompiler();
var query = db.Query("Posts").Select("Id", "Title", "CreatedAt as Date")
var sql = compiler.Compile(query).Sql;

これでビルダーで作ったSQLが取得出来ました。

使ってみた結果として、ビルダーとしては優秀ですがマッピングはDapperと一緒なので結局は微妙なとこです。C#インピーダンスマッチは幻想なので無理に自作ライブラリなど作らずSQLゴリゴリ書いた方が効率が良いです。マッピングまでしてくれたら自分が作ったDapperの拡張ライブラリとさよならかと思ってましたがしばらくは付き合っていくはめになりそうです。
github.com
コネクションやDapperが依存しているとはいえ、ビルダーは依存させなくても使えるようです。

var db = new QueryFactory(); // なにも指定しなくておけ
var query = db.Query(nameof(Hoge))
    .Join(nameof(Piyo), "Hoge.PiyoId", "Piyo.Id")
    .OrderBy("Hoge.Id");
var sql = compiler.Compile(query).Sql;
// sql -> SELECT [Id], [Title], [CreatedAt] AS [Date] FROM [Posts]

どうしてもSQLを書きたくない人はこんな感じで使ってみても良いかも。構文ミスとかなくなるのでそこらへんは便利かも。
今後はマルチマッピングもIssueに上がってるのでキープしといても良いかも
github.com