しばやん御大の Entity Framework Core についての以下の記事を読んで、
コンソールアプリでも、 ソースコードに 接続文字列 や ログの設定を書かずに、設定ファイルから Dependency Injection (依存性の注入: 以下DI) するにはどうしたらよいのかな? と思ったので、 ASP.NET Core の流儀を参考にしながら やってみようと思う。
データベースは、扱いが簡単な SQLite にする。
記事の最後に、 Visual Studio 2015 ですぐに使えるサンプルプロジェクトを用意しているので、 手っ取り早く結果を見たければ、 そのサンプルプロジェクトを見てみてほしい。
実現すること
まずは、何を実現させるのかをハッキリさせておこう。
- 接続文字列と ログ表示の設定を、外のファイルから指定すること
- マイグレーションなどを行うため、 EF Tools からも、上記設定が利用されるようにすること
ここで言う EF Tools とは、Entity Framework Core の コマンドラインツール のことだ。
このツールを使うと、 パッケージマネージャーコンソールから Add-Migration
とか Update-Database
と実行したり、 dotnet.exe から dotnet ef
コマンドを 実行することで、 コード生成やマイグレーションなど を利用することができる。
EF Tools でデータベースを取り扱う際、その接続文字列は DbContext に設定されたものが使用される。
コード内に接続文字列を書いてしまうと、マイグレーションするためのデータベースファイルが決め打ちになってしまい、変更ができなくなる。
このため、 EF Tools を実行した際も、依存性の注入が行えるようにしたい。
前準備
まず、 Visual Studio を使って、 .NET Framework 4.5.1 以降か、 .NET Core の どちらかの コンソールアプリのプロジェクトを作成しよう。
パッケージマネージャーコンソールで、以下のパッケージとその依存パッケージをインストールする。
Install-Package Microsoft.Extensions.Logging.Console -Pre
Install-Package Microsoft.Extensions.Configuration.Json -Pre
Install-Package Microsoft.EntityFrameworkCore.Tools -Pre
Install-Package Microsoft.EntityFrameworkCore.Sqlite -Pre
DbContext への依存性の注入の方法
実際にコードを書いていく。
設定ファイルからの読み取りについては、 ASP.NET のサンプルに倣って、 ConfigurationBuilder
と AddJsonFile
を使ったものにしよう。
さて、本題の 依存性 の注入については、 IServiceCollection
に対して、 フレームワークが提供する拡張メソッド を使いながら 依存性を定義してゆき、 ActivatorUtilities などを使って依存関係が解決されたインスタンスを取得するのが、おおざっぱな流れとなる。
DbContext を例に、もうちょっと具体的に説明すると、
new Microsoft.Extensions.DependencyInjection.ServiceCollection()
してIServiceCollection
を得るIServiceCollection.AddDbContext()
の拡張メソッドを利用して、依存性を定義する- EF のモデリングに使う DbContext (以降
BloggingContext
とする) には、DbContextOptions<BloggingContext> options
を引数にしたコンストラクタを用意し、 基底クラスにoptions
を引き渡すことで、設定された依存性を反映させる。 - ActivatorUtilities.CreateInstance
(serviceProvider) メソッドを使って、 BloggingContext
のインスタンスを取得する…となる。
コードにしてみると、以下のようになる。
EF Tools はどのように DbContext への注入を行うのか
さて、上記のような処理を Main 関数に書いたとしても、 EF Tools で Add-Migration
とか Update-Database
と実行した際に、
No parameterless constructor was found on 'BloggingContext'. Either add a parameterless constructor to 'BloggingContext' or add an implementation of 'IDbContextFactory' in the same assembly as 'BloggingContext'.
とエラーになってしまうだろう。
これは、 EF Tool が Main 関数を実行せずに BloggingContext
を初期化しようとするため、 「BloggingContext
のコンストラクタに DbContextOptions<BloggingContext> options
を指定する」 という依存性が定義されないまま BloggingContext
を初期化してしまうためだ。
では、 EF Tools が実行してくれるように 依存性を注入の定義を行う ためにはどうしたらよいのだろうか?
その答えは、 EF Tools の 「初期化クラスっぽい名前のクラスに、 依存性の注入を行っていそうな名前のメソッドがあると、 それを実行する」 という機能を利用するのだ。
…えーと、どういうこと?
もうちょっと具体的に言うと、
↑この StartupInvoker が、$"Startup{environment}"
,"Startup"
,"Program"
,"App"
の順番で、アセンブリからクラスを探す- 上記のうち最初に見つかったクラスから、
"ConfigureServices"
,$"Configure{environment}Services"
メソッドを探す - このメソッドが static メソッドなら そのまま呼び出し、 そうでなければ コンストラクタで初期化してから呼び出す
- このメソッドが
IServiceProvider
を返せばそれを、 返さなかったら メソッドの引数に指定したIServiceCollection
をIServiceCollection.BuildServiceProvider()
したものを、 これらのどちらか使って、DbContext
の依存性の解決を行う
といった動きをする。 (ASP.NET Core RC2, Tools Preview 1 の場合)
つまるところ、 ConfigureServices という名前のメソッドを持った、 "Startup"
とか "Program"
という名前のクラス用意して、 その中に DI のコードを書いてやれば良いのだ。
上記を満たす書き方はいろいろあるが、たとえば 先ほどの Program クラスを、以下のように書き換えてみよう。
こうすることで、 EF Tools も使用でき、 DB のマイグレなどがコンソールアプリでも行えるようになる。
試しに、 パッケージマネージャーコンソールで Add-Migration InitialMigration
と実行して正しくマイグレーションコードが作成されることと、 Update-Database
と実行して appsettings.json
で指定した接続文字列の通りのファイルに DB が初期化されることを確認してみよう。
サンプルコード
今回使用したプロジェクトは、そのまま GitHub にも上げておいたので、良かったら適当に clone して使ってほしい。
Visual Studio 2015 でソリューションを開き、 .NET 4.5.1 用、 .NET Core 用のそれぞれのプロジェクトでパッケージを復元すれば、利用できるようになるはずだ。
それぞれのプロジェクトで、 Add-Migration <マイグレーション名>
, Update-Database
と実行し、データベースファイルを作成した後、 コンソールアプリをデバッグ実行してみよう。
ピンバック: DbContext.OnConfiguring(EF Core)で接続文字列を書きたくない時 | 株式会社システムキューブ
ピンバック: System.Data.SQLite の NuGet パッケージ のうち どれをインストールするべきか | Aqua Ware つぶやきブログ
ピンバック: System.Data.SQLite でどれをインストールするべきか | Aqua Ware つぶやきブログ