7

EFコアでの作業に問題があります。私は、スキーマメカニズムを介して、プロジェクトのデータベース内の異なる企業のデータを分離したいと考えています。私の質問は、実行時にスキーマ名を変更する方法です。私はこの問題についてsimilar questionを見つけましたが、まだ回答があり、いくつかの条件があります。だから私は前に発見されたとして、Entity Frameworkコアのスキーマが動的に変更される

public static void Resolve(IServiceCollection services) { 
    services.AddIdentity<ApplicationUser, IdentityRole>() 
     .AddEntityFrameworkStores<DomainDbContext>() 
     .AddDefaultTokenProviders(); 
    services.AddTransient<IOrderProvider, OrderProvider>(); 
    ... 
} 

は、私がOnModelCreatingでスキーマ名を設定することができ、必要なDB-コンテキストを付与しますが、Resolve方法、一度だけ呼ばれるこの方法を持っているので、私はグローバルに、ここのようなスキーマ名を設定することができます属性を経由して、そのモデルで

protected override void OnModelCreating(ModelBuilder modelBuilder) { 
    modelBuilder.HasDefaultSchema("public"); 
    base.OnModelCreating(modelBuilder); 
} 

または右にその

[Table("order", Schema = "public")] 
public class Order{...} 

のようなしかし、どのように私は、実行時にスキーマ名を変更できますか?各リクエストごとにefのコンテキストを作成しますが、まず、データベースのスキーマ共有テーブルへのリクエストを介して、ユーザーのスキーマ名を取得します。したがって、そのメカニズムを構成する真の方法は何ですか:

  1. ユーザーの資格情報でスキーマ名を特定します。
  2. 特定のスキーマからデータベースのユーザー固有のデータを取得します。

ありがとうございます。

P.S.私はPostgreSQLを使用しています。これは、テーブル名が小さくなった理由です。

答えて

7

すでにEF6でEntityTypeConfigurationを使用しましたか?

私は解決策は、DbContextクラスのOnModelCreatingメソッドの実体のための使用マッピングになると思い、このような何か:

using System; 
using Microsoft.EntityFrameworkCore; 
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; 
using Microsoft.Extensions.Options; 

namespace AdventureWorksAPI.Models 
{ 
    public class AdventureWorksDbContext : Microsoft.EntityFrameworkCore.DbContext 
    { 
     public AdventureWorksDbContext(IOptions<AppSettings> appSettings) 
     { 
      ConnectionString = appSettings.Value.ConnectionString; 
     } 

     public String ConnectionString { get; } 

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 
     { 
      optionsBuilder.UseSqlServer(ConnectionString); 

      // this block forces map method invoke for each instance 
      var builder = new ModelBuilder(new CoreConventionSetBuilder().CreateConventionSet()); 

      OnModelCreating(builder); 

      optionsBuilder.UseModel(builder.Model); 
     } 

     protected override void OnModelCreating(ModelBuilder modelBuilder) 
     { 
      modelBuilder.MapProduct(); 

      base.OnModelCreating(modelBuilder); 
     } 
    } 
} 

コードOnConfiguring方法には、DbContextクラスの各インスタンスの作成にMapProductの実行を強制します。 MapProduct方法の

定義:

using System; 
using Microsoft.EntityFrameworkCore; 

namespace AdventureWorksAPI.Models 
{ 
    public static class ProductMap 
    { 
     public static ModelBuilder MapProduct(this ModelBuilder modelBuilder, String schema) 
     { 
      var entity = modelBuilder.Entity<Product>(); 

      entity.ToTable("Product", schema); 

      entity.HasKey(p => new { p.ProductID }); 

      entity.Property(p => p.ProductID).UseSqlServerIdentityColumn(); 

      return modelBuilder; 
     } 
    } 
} 

あなたが上見ることができるように、テーブルのスキーマと名前を設定するための行がある、あなたは1つのDbContextでコンストラクタ、またはそのような何かのためのスキーマ名を送信することができます。

魔法の文字列を使用していない、あなたは例えば、利用可能なすべてのスキーマを持つクラスを作成することができますしてください:

var humanResourcesDbContext = new AdventureWorksDbContext(Schemas.HumanResources); 

var productionDbContext = new AdventureWorksDbContext(Schemas.Production); 
:あなたがこれを書くことができ

using System; 

public class Schemas 
{ 
    public const String HumanResources = "HumanResources"; 
    public const String Production = "Production"; 
    public const String Sales = "Production"; 
} 

をするために特定のスキーマを使用してDbContextを作成

明らかに、スキーマの名前パラメータの値に従ってスキーマ名を設定する必要があります。

entity.ToTable("Product", schemaName); 
+0

上記のように、OnModelCreatingにスキーマ名を置いても問題はありません。同じスキーマを持ちます。しかし、私はあなたの返事で何か重要なことを逃したでしょうか? – user3272018

+0

私はあなたのポイントを得た、あなたは正しい私の以前の答えは、OnModelCreatingの問題の解決策は含まれていませんが、私はブログでこの問題の解決策を探し、OnConfiguringメソッドのコードを見つけました。それが役に立つかどうかを教えてください。[リンク](https://github.com/Grinderofl/FluentModelBuilder/issues/9) –

+0

これは小規模なプロジェクトではうまくいくかもしれませんが、数百のテーブルのMapProductメソッドを維持しているとは思いませんすべての可能な設定と移行... –

4

これを行うにはカップルの方法があります。

  • 外部モデルを構築し、それを渡すDbContextOptionsBuilder.UseModel()
  • を経由して、アカウントに
+2

いくつかの詳細やいくつかのドキュメント/ブログ/チュートリアルへのリンクを提供していますか? – user3272018

+0

@ user3272018私は同じ問題を抱えていますが、EFコアにIModelCacheKeyFactoryを適切に実装する方法のドキュメントやサンプルはありません。 –

+1

@ tomas-voracek ああ、やっと私はそれをやった。ちょっと後でそのコードを自己回答として提供します。私はそれが私の目標に到達するための完全な方法ではないと確信していますが、それは動作します。多分誰かが私の解決策を改善するかもしれません。私は早くそれをやっていないため申し訳ありません。 – user3272018

1

をスキーマをとる1とIModelCacheKeyFactoryサービスを置き換え固定スキーマ表では、表属性を使用できます。

スキーマ変更テーブルの属性を使用することはできません。また、ToTableの流暢なAPIを使用して属性を設定する必要があります。
モデルキャッシュを無効にする(または独自のキャッシュを作成する)と、スキーマは毎回変更されるため、コンテキスト作成時(毎回)にスキーマを指定できます。

これは今、あなたがコンテキストを作成する前にスキーマ名を決定するために、いくつかの異なる方法を持つことができる基本アイデア

class MyContext : DbContext 
{ 
    public string Schema { get; private set; } 

    public MyContext(string schema) : base() 
    { 

    } 

    // Your DbSets here 
    DbSet<Emp> Emps { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<Emp>() 
      .ToTable("Emps", Schema); 
    } 
} 

です。
たとえば、システムテーブルを使用してユーザー名からスキーマ名を取得し、正しいスキーマに作業コンテキストを作成する(コンテキスト間でテーブルを共有することができます)場合は、別のコンテキストで「システムテーブル」を作成できます)。
システムテーブルをコンテキストから切り離し、ADO .Netを使用してアクセスすることができます。
恐らく他にもいくつかの解決策があります。

あなたはまた、
Multi-Tenant With Code First EF6

ここで見ることができ、あなたがef multi tenant

EDIT
モデルのキャッシュの問題もあり、グーグルができます(私はそれについて忘れてしまいました)。 モデルキャッシングを無効にするか、キャッシュの動作を変更する必要があります。

+0

返信いただきありがとうございますが、[私が知る限り](http://stackoverflow.com/questions/39093542/entity-framework-6-disable-modelcaching) 'OnModelCreating'メソッドは一度しか呼び出されていません。リクエストごとにスキーマ名を変更します。実際に私はEFコアを使用していますが、 'OnModelCreating'の動作は同じです。 @bricelamは私の問題を解決する方法が2つあると言っていますので、EFの開発者はEFについてもっと知っておく必要があるので、もっと詳しい説明に興味があります。 – user3272018

+0

投稿した質問には、 'IModelCacheKeyFactory'に似ている' IDbModelCacheKeyProvider'があります。多分私の問題を解決する鍵です。私はその記事を見逃しました - ありがとう。 – user3272018

+0

あなたは正しいです!私はモデルキャッシュについて忘れてしまった!パフォーマンスについても気をつけてください(スタックオーバーフローについてもいくつかの記事があります) – bubi

2

このブログが役立つかもしれません。パーフェクト!:)

https://romiller.com/2011/05/23/ef-4-1-multi-tenant-with-code-first/

このブログはEF4に基づいており、私はそれがEFコアと正常に動作するかどうかはわかりません。

public class ContactContext : DbContext 
{ 
    private ContactContext(DbConnection connection, DbCompiledModel model) 
     : base(connection, model, contextOwnsConnection: false) 
    { } 

    public DbSet<Person> People { get; set; } 
    public DbSet<ContactInfo> ContactInfo { get; set; } 

    private static ConcurrentDictionary<Tuple<string, string>, DbCompiledModel> modelCache 
     = new ConcurrentDictionary<Tuple<string, string>, DbCompiledModel>(); 

    /// <summary> 
    /// Creates a context that will access the specified tenant 
    /// </summary> 
    public static ContactContext Create(string tenantSchema, DbConnection connection) 
    { 
     var compiledModel = modelCache.GetOrAdd(
      Tuple.Create(connection.ConnectionString, tenantSchema), 
      t => 
      { 
       var builder = new DbModelBuilder(); 
       builder.Conventions.Remove<IncludeMetadataConvention>(); 
       builder.Entity<Person>().ToTable("Person", tenantSchema); 
       builder.Entity<ContactInfo>().ToTable("ContactInfo", tenantSchema); 

       var model = builder.Build(connection); 
       return model.Compile(); 
      }); 

     return new ContactContext(connection, compiledModel); 
    } 

    /// <summary> 
    /// Creates the database and/or tables for a new tenant 
    /// </summary> 
    public static void ProvisionTenant(string tenantSchema, DbConnection connection) 
    { 
     using (var ctx = Create(tenantSchema, connection)) 
     { 
      if (!ctx.Database.Exists()) 
      { 
       ctx.Database.Create(); 
      } 
      else 
      { 
       var createScript = ((IObjectContextAdapter)ctx).ObjectContext.CreateDatabaseScript(); 
       ctx.Database.ExecuteSqlCommand(createScript); 
      } 
     } 
    } 
} 

これらのコードの主な考え方は、異なるスキーマによって異なるDbContextを作成し、特定の識別子でキャッシュする静的メソッドを提供することです。

関連する問題