7

我在使用EF Core時遇到了問題。我想通過模式機制將項目數據庫中不同公司的數據分開。我的問題是我如何在運行時更改模式名稱?我發現similar question關於這個問題,但它仍然回答,我有一些不同的條件。所以,我有Resolve方法授予DB-背景下,必要時在Entity Framework Core中動態更改模式

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

我可以在OnModelCreating集架構的名稱,但是,正如之前被發現,這種方法被稱爲只有一次,所以我可以globaly喜歡這裏設置模式名這

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?

我認爲,解決辦法是對OnModelCreating方法的DbContext類,像這樣的實體使用映射:

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方法的代碼會強制MapProduct的每個實例創建爲的DbContext類執行。

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; 
     } 
    } 
} 

正如你可以在上面看到,有一條線來設置表模式和名稱,您可以在一個的DbContext構造函數或類似的東西送模式名。

請不要使用魔法字符串,您可以創建一個所有可用的模式一類,例如:

using System; 

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

對於創建一個特定的模式你的DbContext,你可以這樣寫:

var humanResourcesDbContext = new AdventureWorksDbContext(Schemas.HumanResources); 

var productionDbContext = new AdventureWorksDbContext(Schemas.Production); 

很明顯,您應該根據模式的名稱參數的值設置模式名稱:

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

正如我上面所寫的,在OnModelCreating中沒有坐在模式名稱的問題,問題是這個方法只調用一次,所以下一個創建的上下文將具有相同的模式。但是,也許我錯過了一些重要的答覆? – user3272018

+0

我明白了你的觀點,你是對的,我以前的答案不包括OnModelCreating問題的解決方案,但是我在博客上搜索了這個問題的解決方案,並且我找到了OnConfiguring方法的代碼,我修改了我的答案,請檢查它並讓我知道如果是有用的[鏈接](https://github.com/Grinderofl/FluentModelBuilder/issues/9) –

+0

這可能適用於小項目,但我無法想象維護數百個表的MapProduct方法,而不是提及所有可能的設置和遷移... –

4

有幾個方法可以做到這一點:

  • 建立模型外,並通過DbContextOptionsBuilder.UseModel()
  • 通過它與一個需要的架構考慮更換IModelCacheKeyFactory服務
+2

您能否請求e提供一些文件/博客/教程的一些細節或鏈接? – user3272018

+0

@ user3272018我有同樣的問題,沒有文檔或示例如何正確實施EF Core中的IModelCacheKeyFactory。 –

+1

@ tomas-voracek 哦,最終我做到了。稍後我會提供該代碼作爲自我回答。我相信這不是一個完美的方式來達到我的目標,但它的工作原理。也許即使有人能改善我的解決方案。對不起,我以前沒做過。 – user3272018

1

您可以在固定架構表上使用Table屬性。

您不能在模式更改表上使用屬性,您需要通過ToTable fluent 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

,你可以在google ef multi tenant

編輯
還有模型緩存的問題(我忘了這一點)。 您必須禁用模型緩存或更改緩存的行爲。

+0

謝謝你的回覆,但[據我所知](http://stackoverflow.com/questions/39093542/entity-framework-6-disable-modelcaching)'OnModelCreating'方法調用一次,我沒有辦法在每個請求上通過它更改模式名稱。其實我使用EF Core,但是'OnModelCreating'的行爲是一樣的。 @bricelam說過解決我的問題的兩種方法,所以我對一些更詳細的解釋感興趣,因爲EF的開發人員應該知道EF的東西,而不是其他人,嗯? – user3272018

+0

在你發佈的SO問題中有'IDbModelCacheKeyProvider',它看起來類似於'IModelCacheKeyFactory'。也許這是解決我的問題的關鍵。我錯過了那篇文章 - 謝謝。 – user3272018

+0

你是對的!我忘了模型緩存!還要關心性能(你可以在周圍找到一些文章,也可以在Stack Overflow上找到) – 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和某些標識符緩存。

相關問題