概要
LtQuery(リトルクエリ)はEF CoreのようにLINQやInclude()でSQLを構築する機能が備わっていてかつ、パフォーマンスがDapperを凌いでいるといういいとこ取りの高速O/Rマッパー(ORM)です。
クエリキャッシュが強力で一度実行したクエリはADO.NET直書きに迫る速度が出ます。キャッシュ自体も遅くはなくEF Core よりも断然速いです。更に使用メモリ量もかなり抑えられています。
ORM | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
ADO.NET | 3.883 ms | 0.0633 ms | 0.0592 ms | 296.8750 | 203.1250 | 1.44 MB |
LtQuery | 3.906 ms | 0.0380 ms | 0.0337 ms | 296.8750 | 195.3125 | 1.44 MB |
Dapper | 4.416 ms | 0.0255 ms | 0.0226 ms | 359.3750 | 187.5000 | 1.62 MB |
EF Core | 6.816 ms | 0.0720 ms | 0.0673 ms | 554.6875 | 367.1875 | 2.6 MB |
使用例
詳細はGitHub参照。
class BlogService
{
readonly ILtConnection _connection;
public BlogService(ILtConnection connection)
{
_connection = connection;
}
// クエリ生成
static readonly Query<Blog> _query = Lt.Query<Blog>()
.Include(_ => _.Posts).Where(_ => _.UserId == Lt.Arg<int>("UserId"))
.OrderByDescending(_ => _.Date).Take(20).ToImmutable();
public IEnumerable<Blog> GetNewBlogs(int userId)
{
// クエリ実行
return _connection.Select(_query, new { UserId = userId });
}
}
パラメータにしたい箇所を Lt.Arg<int>("UserId")
の様に記述し、クエリ実行時に匿名型で渡す仕組みです。クエリのキャッシュ方法は実に簡単で単にフィールドなどに保持しておくだけ。内部的に弱参照を使っているため保持していなければGC時にキャッシュごと消される仕組みです。LINQに依存しているように見えますがそれは生成の時だけで Query<>
はほぼプリミティブ型の塊です。
背景
- 「Entity Framework ってCoreになっても遅いですよね?」
- 「DapperはSQLすぎる」
ってな訳でO/Rマッパー(ORM)を自作することにしました。
Entity Framework Core の問題点
LINQ to SQLに依存しきっている
EF Core
は LINQ to SQL
が中枢に存在します。ということは Expression
に依存しきっていることになり、 Expression
はそこそこ重いためどうしても全体に影響が出てしまいます。システム的に限界が見えている訳です。
明示的クエリキャッシュがくどい
static readonly Func<TestContext, int, Blog> _selectSingle =
EF.CompileQuery((TestContext context, int id) =>
context.Set<Blog>().Single(_ => _.Id == id));
EF Coreは自動的にクエリキャッシュを行うため気にしなくてもいいです。ですが、あえて明示的に行おうとすると↑のようなコードになります。
Dapper の問題点
SQL依存
DapperはSQLファーストなライブラリです。超シンプルで素晴らしいのですが、やはり機能面が物足りない。それを補うライブラリ( SqlKata
や Dapper.FastCrud
など)がありますが、SQLが中枢部にいると拡張しづらい問題があります。
これらの問題を解決するORMのシステム
まず、中枢から Expression
と SQL
を排除します。代わりに軽量なクエリオブジェクトを中枢におき、すべての動作の核とします。そこからSQLを構築したり、読み取り処理のフロー決定に使ったりします。このクエリオブジェクトをキーにしてクエリキャッシュを行います。
そして、更にパフォーマンスを向上するため読み取り処理は完全IL実装にします。この辺はSQLファーストだったら実装しづらい機能です。 ← Dapper
に近いパフォーマンスを目標にします。Dapper
を超しました。
今後
ベンチ見てやる気が出たので本格的なフルORMにする予定です。特にValue ObjectとかDDD絡みの機能を追加してDDDに最適なORMを目指したいと思います。
コメント