Mohsen Mirshahreza

DDL を明示的に扱うデータベース中心の ORM を .NET で — ランタイムで emit される型、JOIN を使わない eager loading、CDC ベースの push 通知、schema から直接生成される GraphQL/OpenAPI。

Node.js の世界で Sequelize.js を使ったことがある方なら、その感覚を思い浮かべられるでしょう。データベースを指し、モデルを定義(または既存のデータベースから抽出)すると、数分以内に Model.findAll({ where: {...}, include: [...] }) でクエリを実行できます。アソシエーション、ライフサイクルフック、read replica — すべてが揃い、すべてが SQL に十分近く感じられるため、抽象化と戦っているという感覚がありません。

.NET における ORM のストーリーは異なります。Entity Framework Core は素晴らしいライブラリですが、基本的に code-first で動作します。DbContext/entity クラスを定義し、migration がコードに合わせてデータベースをどのように変更すべきかを記述します。このモデルは、アプリケーションが schema を所有している場合に最適です。しかし、データベースがすでに存在し、他のチーム(あるいは別のコードベース、場合によってはまったく異なる言語)に属している場合、またはアプリケーションコードに schema の形状を決定させたくない場合は、このモデルはうまく機能しません。

まさにこのギャップが SequelizeDotNet を構築する理由となりました。

このライブラリは何をするのか
SequelizeDotNet は、6 つのリレーショナルデータベース — SQL Server、SQLite、PostgreSQL、MySQL/MariaDB、Oracle、DB2 — 向けの database-first 哲学を持つ ORM で、3 つの譲れない設計原則に基づいて構築されています。

データベースは常に真実の源です。自分で書いた schema からコードが「どうあるべきか」を推測することはありません。モデルはデータベースから来るもので、その逆ではありません。
DDL は常に明示的で、自動 diff はありません。構造的な変更は、RenameColumnAsync、AddColumnAsync、DropTableAsync といった明確な名前のメソッドを通じてのみ行われ、開発者が意図的に呼び出します。破壊的な操作には force: true が必要で、明示的にオーバーライドされない限り本番環境ではロックされます。Sequelize の sync({ alter: true })(「データベースを必要に応じてモデルに合わせる」)のようなものは存在しません — なぜなら、まさにこの種の機能が本番環境での自動同期を危険にし、Sequelize 自身のドキュメントもその理由で使用を推奨していないからです。
ランタイム型、コード生成ではありません。ここでは .NET が JavaScript にはできないことを行います。EntityTypeFactory は System.Reflection.Emit を使用して各テーブルに対して実際の CLR 型を生成し、遅延ロードとスレッドセーフを保証してテーブルごとにキャッシュされます(同時実行負荷下でも正確に 1 回 emit されます)。データベースの列を変更すると、次のクエリでその変更を反映した新しく emit された型が配信されます — リビルド、再起動、または同期を維持する必要のある生成 .cs ファイルはありません。
Sequelize にないいくつかの機能
.NET のランタイムと型システムが構築を可能にするためです。

ネストされた include と many-to-many、常に JOIN なし。Include("Author.Publisher.Country") は 3 層の外部キーを 3 つの別々のクエリとして処理し、IN (...) でフィルタリングしてキャッシュします。IncludeMany("Tags", "dbo", "PostTags", "FK_PostTags_Posts", "FK_PostTags_Tags") は明示的に名前を付けられた中間テーブルを通じて同じことを行います。曖昧で自動生成されたクエリプランに何も隠されません。
生の SQL も理解する Read/Write ルーティング。Sequelize の read replication サポートには、生のクエリが静かに replica pool を通過する繰り返し発生するバグレポートの歴史があります。StatementIntentClassifier は生のクエリの先頭キーワードを検査し、同じショートカットパスも正しくルーティングされるようにします。
schema のホットリロード、再起動不要。SchemaWatcher はポーリングし、比較し、変更されたテーブルのランタイム型をアトミックにバージョン管理して置き換えます — プロセスを再起動することなく。
SQL Server Change Data Capture をサブスクライブ可能なイベントとして。ほとんどの ORM は CDC の組み込みサポートを持っていません。CdcChangeWatcher は SQL Server の change テーブルを C# イベントに変換し、一度サブスクライブすれば完了です。
イントロスペクションされた schema から直接 GraphQL と OpenAPI を生成。別途モデリングステップは不要 — schema を一度読み込み、データベースがすでに知っている内容から軽量な GraphQL schema または OpenAPI 3.0 ドキュメントを直接構築します。
現在の状況 — 正直に
これは若いプロジェクト(バージョン 0.1.0)であり、何が実装されたかだけでなく、どこまで実際にテストされたかを正確に述べたいと思います。

SQL Server と SQLite は完全にライブテスト済み — この 2 つで 140 以上のテストがあり、すべてのデータ型、すべてのクエリオペレーター、トランザクション、フック、schema drift、hot-reload、bulk insert をカバーし、実在の LocalDB と一時データベースで実行されています。
PostgreSQL、MySQL、Oracle、DB2 は同じ IDialect 契約の下で完全に実装されていますが、unit-test レベル(クエリトランスレーター)でのみテストされています — これらの end-to-end 動作を確認するライブサンプルはまだありません。もしお持ちでしたら、ご参加は非常に歓迎します — 既存の SQL Server/SQLite ライブテストをこれらのデータベースのいずれかに移植することは、現在可能な最も価値のある貢献です。
合計 223 の自動テスト、0 件の失敗、このバージョンで。

コードベースがすでに自身の schema を所有しており、コンパイル時にチェックされた LINQ を必要とする場合、おそらく EF Core が依然としてデフォルトのより良い選択肢です — より成熟しており、Microsoft によってサポートされており、はるかに大きなエコシステムを持っています。SequelizeDotNet は、データベースが真実の源であり、Sequelize が 1 世代の開発者に教えた方法で作業することを好むという特定のシナリオのためのものです。

試してみる
dotnet add package SequelizeDotNet.Core
dotnet add package SequelizeDotNet.Providers.SqlServer
var engine = new QueryEngine(new SqlServerDialect(), new ModelRegistry());

var query = QueryEngine.Query("dbo", "Posts")
.Where("AuthorId", QueryOperator.Equal, 42)
.Include("FK_Posts_Authors");

var rows = await engine.ToListAsync(query, connection);
完全なドキュメント: Developer Guide · ソースと issue: github.com/mirshahreza/SequelizeDotNet · ライセンス: MIT。

これまで Sequelize.js に貢献してくださったすべての方々に心より感謝します — このプロジェクトが存在するのは、そのプロジェクトが 10 年以上にわたって、このスタイルのデータベースとの関わり方がどの言語でも価値があることを証明したからです。

もしこれまで、誤った推測をしたマイグレーションツールに苦しめられたことがあったり、他のチームが所有するデータベースを持つ .NET サービスをメンテナンスしている場合は、フィードバックをお待ちしています — また、PostgreSQL、MySQL、Oracle、または DB2 のサンプルをお持ちで、test suite を試すことができる場合は、さらに喜んでお待ちしています。

SequelizeDotNet · ライセンス MIT · github.com/mirshahreza/SequelizeDotNet