web-dev-qa-db-ja.com

.net Core 2、EFおよびMulti Tenancy-ユーザーに基づいたdbcontextスイッチ

マルチテナンシーの(ほぼ)最悪です。私はasp.netコアWebサイトを構築しています。これは、ポーキーな小さなイントラネットサイトの束を移植するものです。各サブサイトはasp.netエリアになります。 Identityに関するIdentityContextがあります。ベンダーデータベースのコピーが複数あり、それぞれに複数のテナントがあります。 ApplicationUserclassには、dbコンテキストを切り替えるために使用するOrgCodeプロパティがあります。

User.OrgCodeとAreaを接続文字列にマップする何かが必要だと思います

Stack Overflowには、これに関する部分的な例が多数あります。午後の読書の後、私はとても混乱しています。その中核は次のようになります:

  • コンストラクター引数からDI dbcontext refを削除します。
  • コントローラコンストラクタでdbcontextをインスタンス化します。
  • 以前と同じようにdbcontextを使用します。

私は正しい軌道に乗っていますか?

コヒーレントな例はありますか?


2020/07/09を編集

残念ながら、これはより緊急になっています。

Identityデータベースはテナントに依存しません。 IdentityのすべてのユーザーはOrgCode識別子を持っています。 (カスタムユーザープロパティ)。

各サーバーには、「コストセンター」を使用してマルチテナンシーが組み込まれています。サーバーには、すべてのサーバーで同じ名前のデータベースのコレクションがあります。

  1. コアベンダーデータベース
  2. 拡張機能を保存するカスタムデータベース
  3. ジョブ出力のログデータベース

ユーザーを識別するために組織コードをすでに使用している小さなアプリケーション固有のデータベースもあります

サーバーA-1つの組織コード

サーバーB-4つの組織コード

サーバーC-プロジェクトに関与している3つの組織コード、まだ50以上(ほとんど小さい)

サーバーD-現在、組織コードは関与していません。サーバー上に80以上。 (まもなく)

すべての組織を1つのサーバーに統合することはできません。法的および技術的な影響があります。各サーバーには、何百ものリモートトランスポンダがあり、更新が必要であることを報告しています。これらの供給データは、カスタムジョブで使用されるものです。

夢は、必要に応じてコンテキストを渡しながら、各ページでDIを使い続けることです。コンテキストは、ユーザー名のOrgCodeに基づいて、正しい基礎となる接続の詳細を選択するのに十分スマートです。

Wordプロキシはこの領域に負荷がかかりそうなので、使用をためらっています。

地獄、私はそれをどこに置くか知っていれば、switchステートメントを使用しても問題ありません

必要な効果組織XYZのユーザーは、ベンダーデータベースを必要とするページをロードし、XYZがマップするサーバーからページを取得します。

2020/07/13を編集

参照を整理するために、OrgCodeとServerをEnumsに切り替えました。コンテキストの継承は次のとおりです

  • DbContext
    • CustLogsContext

         public virtual ServerEnum Server 
         { 
             get 
             { 
                 return ServerEnum.None; 
             }
         }
      
         DbSet (etc)
      
      • CustLogsServerAContext

             public override ServerEnum Server 
             { 
                 get 
                 { 
                     return ServerEnum.ServerA; 
                 }
             }
        
      • CustLogsServerBContext(その他)

      • CustLogsServerCContext(その他)

      • CustLogsServerDContext(その他)

    • VendorContext

      • VendorServerAContext
      • VendorServerBContext(その他)
      • VendorServerCContext(その他)
      • VendorServerDContext(その他)

OrgCodesをサーバーにマッピングする辞書を含む静的クラスOrgToServerMappingも作成しました。現在ハードコードされており、最終的にはconfigからロードするように変更され、reloadメソッドが追加されます。

現在、コンテキストを収集するクラスが必要だと考えていますが、Dictionary<serverEnum, dbcontext>およびサービスとして登録されます。誰かが私が使用できる多態性のトリックを知っているのでない限り、継承された各dbcontextにオブジェクトのバージョンが必要であることを確認してください

10
Hecatonchires

私は次のようにマルチテナンシーの実装を作成しました(理論的には無限に拡張できます)。マルチテナンシーデータベース(tenantdbなど)を作成します。簡単です。ただし、コツは、各テナント(ターゲットデータベース)の接続文字列の詳細を格納することです。ユーザーorgCodeなどと一緒に.

User.OrgCodeとAreaを接続文字列にマップするものが必要だと思います

したがって、コードでそれをマッピングする方法は、tenantdbから取得したターゲットテナント接続文字列を使用してdbcontextをフィードすることです。そのため、tenantdbにはanohter dbcontextが必要です。したがって、最初にtenantdbを呼び出して、ユーザーのorgcodeでフィルタリングすることにより、正しいテナント接続文字列を取得します。次に、それを使用して新しいターゲットdbcontextを作成します。

夢は、必要に応じてコンテキストを渡しながら、各ページでDIを使い続けることです。コンテキストは、ユーザー名のOrgCodeに基づいて、基礎となる正しい接続の詳細を選択するのに十分なほどスマートになります。

私はこれをDIで使用しています。

このtenantdbのクラッド操作用のUI要素を作成したので、削除、追加、接続文字列の詳細、およびその他の必要なデータを更新できます。パスワードは保存時に暗号化され、取得時にターゲットdbcontextに渡される直前に復号化されます。

だから私は私の設定ファイルに2つの接続文字列があります。 1つはtenantdb用で、もう1つはデフォルトのターゲットdb用です。空文字列またはダミー文字列になる可能性があります。接続文字列を自動検索する可能性が高いため、DIコードがない場合にDIコードによってスローされるアプリケーション起動エラーが発生する可能性があります。

スイッチコードもあります。これは、ユーザーがanohterテナントに切り替えることができる場所です。したがって、ここでユーザーは、権限を持つすべてのテナントから選択できます(権限はtenantdbに保存されます)。そして、これは再び上記のコードステップをトリガーします。

乾杯。

出発点として このRazorページのチュートリアル を採用しました。

このようにして、ターゲットデータベースを非常に粗く結合することができます。唯一の重複はユーザーIDである可能性があります。 (またはAzure、Google、AWSなどからのトークンも)

起動。

 public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();

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

        //your dummy (empty) target context.
        services.AddDbContext<TargetContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("TargetContext")));
    }

IndexModel(テナントページ)。

public class IndexModel : PageModel
{
    private readonly ContosoUniversity.Data.TenantContext _context;
    private ContosoUniversity.Data.TargetContext _targetContext;

    public IndexModel(ContosoUniversity.Data.TenantContext context, ContosoUniversity.Data.TargetContext targetContext)
    {
        _context = context;
        //set as default targetcontext -> dummy/empty one.
        _targetContext = targetContext;
    }

    public TenantContext Context => _context;

    public TargetContext TargetContext { get => _targetContext; set => _targetContext = value; }

    public async Task OnGetAsync()
    {
        //get data from default target.
        var student1 = _targetContext.Students.First();

        //or
        //switch tenant
        //lets say you login and have the users ID as guid.
        //then return list of tenants for this user from tenantusers. 
        var ut = await _context.TenantUser.FindAsync("9245fe4a-d402-451c-b9ed-9c1a04247482");
        
        //now get the tenant(s) for this user.
        var SelectedTentant = await _context.Tenants.FindAsync(ut.TenantID);
        
        DbContextOptionsBuilder<TargetContext> Builder  = new DbContextOptionsBuilder<TargetContext>();
        Builder.UseSqlServer(SelectedTentant.ConnectionString);
        _targetContext = new TargetContext(Builder.Options);

        //now get data from the switched to database.
        var student2 = _targetContext.Students.First();
    }
}

テナント。

 public class Tenant
{
    public int TenantID { get; set; }
    public string Name { get; set; }
    //probably could slice up the connenctiing string into props.
    public string ConnectionString { get; set; }

    public ICollection<TenantUser> TenantUsers { get; set; }
}

TenantUser。

public class TenantUser
{
    [Key]
    public Guid UserID { get; set; }
    public string TenantID { get; set; }
}

デフォルトのconnstrings。

{"AllowedHosts": "*"、 "ConnectionStrings":{"TenantContext": "Server =(localdb)\ mssqllocaldb; Database = TenantContext; Trusted_Connection = True; MultipleActiveResultSets = true"、 "TargetContext": "Server =(localdb)\mssqllocaldb; Database = TargetContext; Trusted_Connection = True; MultipleActiveResultSets = true "}

1
ivw