BEACHSIDE BLOG

Azure と GitHub と C# が好きなエンジニアの個人メモ ( ・ㅂ・)و ̑̑

EF Core の Global Query Filters 機能で論理削除フラグをいい感じに扱う ( Entity Framework Core )

Entity Framework Core では、はるか昔 (v2.0) に Global Query Filters (グローバルクエリフィルター)という Cool な機能が追加されました。

わかりやすそうなユースケースとして

  • 論理削除されたデータはデフォルトで除外して、データを取得する
  • マルチテナントのデータベースに対して、対象のテナントのデータのみを取得する

ってのが公式ドキュメントでも挙げられてます。

今回は以下を想定してサンプルコードを書いてみます。

  • データベースのテーブルに論理削除フラグがある
  • データを取得するときはGlobal Query Filters 機能で論理削除されてないのものを取得する

サクッと Console App で実現してみましょう。データベースは、Azure SQL Database (とか SQL Server)に接続することを想定して NuGet パッケージをインストールしてますが、EF Core が利用できれば他の DB でももちろん使える機能です。また、今回はローカルDB を使ってローカルのみで動作させるサンプルです。

(ちなみに次回は ASP.NET Core でマルチテナントDB をいい感じに扱う記事でも書こうかと思ったり...)

Console App のプロジェクト作成と NuGet のインストール

VS 2019 で Console アプリを作成します。この手順は書くほどでもないので飛ばします。

プロジェクトが作成出来たら、次は今回必要な NuGet パッケージをインストールしましょうか。
キーボードでショートカットキー Ctrl + Q > カーソルが 検索 にとぶので「nuget」と入力 > ソリューションの NuGet パッケージの管理 を選択します。

f:id:beachside:20200330161807p:plain

必要なものは以下の3つです。インストールしましょう。バージョンは現時点で最新のものを書きました。

  • Microsoft.EntityFrameworkCore.SqlServer (v3.1.3)
  • Microsoft.EntityFrameworkCore.Tools (v3.1.3)
  • Microsoft.Extensions.Logging.Console (v3.1.3)

NuGet の画面で 参照 タブをクリックし、検索に上記のパッケージ名を入力してインストールします。

f:id:beachside:20200330162109p:plain

Code First でコーディング

ざくざくコーディングしましょう。

Entity の作成

まず、(特に意味はないんですがプロジェクトの直下にファイルが散在するのがきらいなので)ソリューションエクスプローラーにてプロジェクトの直下に Models ってフォルダを作っておきます。
その中に Item class を作成します。これはデータベースのテーブルのスキーマになります。こんな感じ。

DbContext の作成

次は Models フォルダーの中に、DbContext を継承したこんな class を作ります。

なんか無駄にコード多いですが、Global Query Filter を設定してるのは、29行目だけです。HasQueryFilter でフィルターするだけという簡単な仕組みです。

特徴は、データアクセスの大元である DbContext 側で制御をかけるので、使う側は意識する必要がない点です。

論理削除のような使う側に無意味(かもしれない)ものを隠蔽できます。Cool ですねー♪

他のコードは EF Core は大したものではないですが軽く触れておくと、

  • 20行目: OnConfiguring メソッドで接続文字列を直接書いてます。ローカルDB用に定義してます。(真面目にやるとして例えば ASP.NET Core で使うなら Startup で DI 時に書くし、接続文字列は UserSecret や環境変数から取得するやつですね。)
  • 27行目: テーブルの主キーを定義しておきました。8行目でプロパティ名を Items と定義しているので、テーブル名はプロパティ名と同じで Items になります。テーブル名は複数形やだやだってときは、コメントアウトしてある26行目のように書くとテーブル名を変更してマッピングできます。
  • 10-16行目、21行目: 機能の検証するなら実行されたクエリ見たいですよね。ロギング用のコードを書いておきました。あるあるですね。

もうちょいちゃんと Code First やるなら、それぞれのプロパティがデータベース側でどんな型になるかとか定義するところですが今回は省略です。

Code First: Migration 実行

パッケージマネージャーコンソールを開きましょう。

どこから開くかわからんときは、ショートカットキー Ctrl + Q > 検索で「パッケージ」と入力すれば出てきます。それ以上の文字を入力("マネージャーコンソール")しようとするときは半角スペース入れないと検索されない鬱陶しさがあります。

で、Code First でおなじみにのコマンドを打ちましょう。パラメーター(以下例では Initialize と打った部分)は適当でよいです。

add-migration Initialize

正常に終了すると、"Migrations" ってフォルダとその中にファイルが作られます。それを実行するのに以下のコマンドを打ちます。

update-database

正常に終わればテーブルが作成されているでしょう。ItemContext の接続文字列で指定したデータベース名: SampleDb ができているはずです。
Azure Data Studio とかで接続してみてみましょう。
接続の方法は、以前ブログで書きました♪

blog.beachside.dev

細かい設定してないので雑なスキーマのテーブルができてますね♪

f:id:beachside:20200330180825p:plain:w400

Code First で初期データの挿入とか書いてもいいんですがブログが無駄に長くなるので Azure Data Studio から手作業で雑にデータを入れます。 Items テーブルを右クリック > Edit Data をクリックします。

f:id:beachside:20200330181114p:plain:w400

データをエディットできる View が表示されますので、以下のようにデータをいれました。Id 1 のデータだけ論理削除フラグである IsDeleted =1 (true) にしています。

f:id:beachside:20200330181907p:plain

Console App で動作確認

コンソールアプリのエントリーポイントである Program.cs を以下のように書いてみました。

DB からデータを取得して表示してるだけのシンプルなコードです。

18行目で1秒待ってるのは、ログの表示がたまにぐちゃっとなるのできれいに見せるためなだけで深い意味はありません。

実行した結果は以下です。想定通りに IsDeleted = false のデータのみが取得されていますね。クエリも想定通りに動いています。

f:id:beachside:20200330191636p:plain

Query Filter を外すには

Program.cs にちょっとコードを追加してみました。29-31行目 で取得しているような感じで、IgnoreQueryFilters を使うと Filter をはずすことができます。

実行するとこんな感じ。

f:id:beachside:20200330194715p:plain

終わりに

これを使わないければ、データアクセス時に自分で論理削除済みのデータを取得しないってコードを毎回書けばいいんですが、クソみたいな作業ですね。忘れて Unit Test が赤くなるのも嫌だし。ということで、適材適所に使っていきましょう。

テーブル名、単数形でマッピングさせるつもりがコメントアウト直さずにコマンド実行しちゃってスクショとっちゃったから、そのまま書いたけどなんか心残り...

さて、次回は ASP.NET Core でマルチテナントなデータベースを構成するための Tips を書きます。

参考

docs.microsoft.com