web-dev-qa-db-ja.com

EF7 / .NETCoreで複数のデータベースにDbContext継承を実装するにはどうすればよいですか

ASP.NET Core1.1でWebAPIを構築しています。

構成、ユーザー、グループ(全部で約25のテーブル)などの構成アイテムの共通の基本スキーマを持つ(システムごとに)さまざまなデータベースがあります。図に示すように、基本クラスから継承することで、モデルの共有部分の非常に広範なEF構成の重複を回避しようとしています。

My DbContext inheritance tree

ただし、これは、コンストラクターにパラメーターとしてDbContextOptions<DerivedRepository>を渡すためのEntity Framework(EF)要件のため、機能しません。ここで、DerivedRepositoryは、コンストラクターが呼び出されるリポジトリーのタイプと一致する必要があります。次に、:base(param)を呼び出して、パラメータをベースDbContextに渡す必要があります。

したがって、(たとえば)InvestContextがDbContextOptions<InvestContext>で初期化されると、base(DbContextOptions<InvestContext>)が呼び出され、ConfigurationContextコンストラクターの呼び出しが必要なタイプではなくタイプDbContextOptions<InvestContext>のパラメーターを受け取るため、EFはエラーをスローします。 DbContextOptions<ConfigurationContext>。 DbContextのオプションフィールドは次のように定義されているため

    private readonly DbContextOptions _options;

これを回避する方法がわかりません。

共有モデルを1回定義し、それを複数回使用するための最良の方法は何ですか?ヘルパー関数を作成して、派生したすべてのコンテキストから呼び出すことができると思いますが、継承ほどクリーンでも透過的でもありません。

13
Peter

OK、私はこれを次のように継承階層を使用する方法で機能させました(例として上記のInvestContextを使用):

前述のように、InvestContextクラスはタイプDbContextOptions<InvestContext>のコンストラクターパラメーターを受け取りますが、そのベースにDbContextOptions<ConfigurationContext>を渡す必要があります。

DbContextOptions変数から接続文字列を掘り出し、必要なタイプのDbContextOptionsインスタンスを構築するメソッドを作成しました。 InvestContextは、base()を呼び出す前に、このメソッドを使用して、オプションパラメーターを適切なタイプに変換します。

変換方法は次のようになります。

    protected static DbContextOptions<T> ChangeOptionsType<T>(DbContextOptions options) where T:DbContext
    {
        var sqlExt = options.Extensions.FirstOrDefault(e => e is SqlServerOptionsExtension);

        if (sqlExt == null)
            throw (new Exception("Failed to retrieve SQL connection string for base Context"));

        return new DbContextOptionsBuilder<T>()
                    .UseSqlServer(((SqlServerOptionsExtension)sqlExt).ConnectionString)
                    .Options;
    }

investContextコンストラクター呼び出しはこれから変更されます。

  public InvestContext(DbContextOptions<InvestContext> options):base(options)

これに:

  public InvestContext(DbContextOptions<InvestContext> options):base(ChangeOptionsType<ConfigurationContext>(options))

これまでのところ、InvestContextとConfigurationContextはどちらも単純なクエリで機能しますが、それはちょっとしたハックのようで、EF7の設計者が考えていたものではない可能性があります。

複雑なクエリや更新などを試みると、EFが結び目になるのではないかと心配しています。これは問題ではないようです。以下を参照してください)

編集:この問題をEF7チームの問題として記録しました ここ そしてチームメンバーが変更を提案しました次のようにEFコアコアに:

「TContextが現在のコンテキストタイプから派生したタイプになるようにチェックを更新する必要があります」

これで問題は解決します。

そのチームメンバー(この問題で確認できます)とさらに対話し、EFコアコードを掘り下げた後、上記で概説したアプローチは安全であり、提案された変更が実装されるまで最良のアプローチに見えます。

16
Peter

OPのGitHub号からのこの投稿 すべての人の注意を引きたいと思います:

タイプなしでDbContextOptionsを使用する保護されたコンストラクターを提供することにより、ハックなしでこれを解決することができました。 2番目のコンストラクターを保護することで、DIによって使用されないようにします。

public class MainDbContext : DbContext {
    public MainDbContext(DbContextOptions<MainDbContext> options)
        : base(options) {
    }

    protected MainDbContext(DbContextOptions options)
        : base(options) {
    }
}

public class SubDbContext : MainDbContext {
    public SubDbContext (DbContextOptions<SubDbContext> options)
        : base(options) {
    }
}
5
Mark

要件に応じて、タイプ固有ではないバージョンのDbContextOptionsを使用できます。

これらを変更します。

public ConfigurationContext(DbContextOptions<ConfigurationContext> options):base(options)    
public InvestContext(DbContextOptions<InvestContext> options):base(options)

これに:

public ConfigurationContext(DbContextOptions options):base(options) 
public InvestContext(DbContextOptions options):base(options)

次に、最初にConfigurationContextを作成すると、それを継承するクラスは同じ構成を取得しているように見えます。また、さまざまなコンテキストを初期化する順序によっても異なる場合があります。

編集:私の実例:

public class QueryContext : DbContext
{
    public QueryContext(DbContextOptions options): base(options)
    {
    }
}

public class CommandContext : QueryContext
{
    public CommandContext(DbContextOptions options): base(options)
    {
    }
}

そしてStartup.csで

services.AddDbContext<CommandContext>(options =>
                 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddDbContext<QueryContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

または、テストクラスで:

    var connectionString = "Data Source=MyDatabase;Initial Catalog=MyData;Integrated Security=SSPI;";

    var serviceProvider = new ServiceCollection()
        .AddDbContext<QueryContext>(options => options.UseSqlServer(connectionString))
        .BuildServiceProvider();

    _db = serviceProvider.GetService<QueryContext>();
3
Clicktricity