EFCoreのContextクラスコンストラクタについて

# はじめに
EFCoreのContextクラスコンストラクタは3種類あります。
- `DribedDbContext(DbContextOptions<DribedDbContext> options)`
- `DribedDbContext()`
- 独自実装の`DribedDbContext(string connectionString)`など上記以外

どれを使うべきか謎になったのでメモ。
MySQL前提で書きましたが、他のRDBでも同じです。

# 前提条件
ASP.NET Core、その他ネイティブアプリどちらか一方でEFCoreを使う場合はあまり関係無い記事です。
場合によってはASP.NET Coreとそれ以外のアプリで同じコードのEFCoreを同時に使いたい時もあるでしょう。
こんなソリューション内容になるはずです。
- ASP.NET Coreプロジェクト
- コンソールアプリなどのプロジェクト
- EFCore関係のクラスプロジェクト

これでASP.NET Coreプロジェクトとその他アプリのプロジェクト、双方からEFCoreのコードを使用できますが・・・。

# いきさつ
DBファーストでスキャフォールディングすると、2個のコンストラクタ、オーバーライドのかけらが自動的に作られます。
コードファーストのサンプルコードでも同様の内容で書かれているはずです。

``` csharp:BookDbContext.cs
public partial class BookDbContext : DbContext
{
    public BookDbContext()
    {
    }

    public BookDbContext(DbContextOptions<BookDbContext> options)
       : base(options)
    {
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseMySql("スキャフォールディング時の接続文字列");
        }
    }
    //以降テーブル部分省略

```

何も考えないと接続文字列を引数にしたコンストラクタを作りたくなるでしょう。

``` csharp:BookDbContext.cs
//一応動くけど定石ではない
public partial class BookDbContext : DbContext
{
    private readonly string _connectionString;
    
    public BookDbContext(string connectionString)
    {
        _connectionString = connectionString;
    }
    
    public BookDbContext(DbContextOptions<BookContext> options)
       : base(options)
    {
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseMySql(_connectionString, 
                ServerVersion.AutoDetect(_connectionString));
        }
    }
    //以降テーブル部分省略
```

# 問題
ASP.NET Coreとその他アプリで使いたいコンストラクタが異なります。
コンストラクタの選択は下記となります。
- ASP.NET Coreは`BookDbContext(DbContextOptions<BookDbContext> options)`
- 他のアプリは`BookDbContext(string connectionString)`

後で実コードを出しますが、ASP.NET Coreは依存性注入(DI)サービスの一つ`IServiceCollection.AddDbContextFactory<T>()`が推奨されており、他のアプリは普通にコンストラクタを使用します。

しかし`BookDbContext(string connectionString)`があるとASP.NET Coreで例外が発生します。
```
InvalidOperationException: Multiple constructors accepting all given argument types have been found in type 'Shared.Database.Model.BookContext'. There should only be one applicable constructor.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.TryFindMatchingConstructor(Type instanceType, Type[] argumentTypes, ref ConstructorInfo matchingConstructor, ref Nullable<int>[] parameterMap)
```
`DribedDbContext(string connectionString)`を使えない事になりますが、どうなってしまうのでしょう。

# 結論
- `DribedDbContext(DbContextOptions<DribedDbContext> options)`が正解
- `DribedDbContext(string connectionString)`は使えない
- `DribedDbContext()`はあっても無くてもよい
- `OnConfiguring`もあっても無くてもよい
- コンストラクタ使用時は`DbContextOptionsBuilder.Option`プロパティを使う

# どうする
実際のコードを見てみましょう。

## EFCoreのクラスプロジェクト
これだけでOKです。
``` csharp:BookDbContext.cs
public partial class BookDbContext : DbContext
{
    public BookDbContext(DbContextOptions<BookContext> options)
       : base(options)
    {
    }
    //以降テーブル部分省略

```


## ASP.NET Core
こちらは公式の推奨通り`IServiceCollection.AddDbContextFactory<T>()`を使います。
``` csharp:Program.cs
var builder = WebApplication.CreateBuilder(args);
//中略
connectionString = builder.Configuration.GetConnectionString("Book") ?? string.Empty;
builder.Services.AddDbContextFactory<Shared.Database.Model.BookDbContext>(options =>
    options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
```

[ASP.NET Core の依存関係の挿入における DbContext](https://learn.microsoft.com/ja-jp/ef/core/dbcontext-configuration/#dbcontext-in-dependency-injection-for-aspnet-core)
Razorコンポーネントのコードは上記リンクを読んでください。

## ネイティブアプリ
ネイティブアプリにファクトリサービスなんてありません。
普通にコンストラクタを使いたいのですが、使い方について公式の見解が見当たりません。
これでいいようです。
``` csharp
private static BookDbContext GetDbContext()
{
    var builder = new DbContextOptionsBuilder<Shared.Database.BookDbContext>();
    var connectionString = "めんどいから自分で作って";
    builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
    return new BookDbContext(builder.Options);
}

private void Tekitou()
{
    using (var context = GetDbContext())
    {
        //context.Database.EnsureCreated();とか
    }
}
```

# おわりに
コンテキストのファクトリメソッドをサービスクラスとする手もありますが定石かは知らん。