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

Вы уже используете EntityTypeConfiguration в EF6?

Я думаю, что решение будет использовать отображение для субъектов по методу 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, я изменил свой ответ, пожалуйста, проверьте это и дайте мне знать, если это полезно [link] (https://github.com/Grinderofl/FluentModelBuilder/issues/9) –

+0

Это может работать для небольших проектов, но я не могу представить, как поддерживать метод MapProduct для сотен таблиц, не говоря уже о все возможные настройки и миграции ... –

4

Есть несколько способов сделать это:

  • Построить модель внешне и передать его в через DbContextOptionsBuilder.UseModel()
  • Заменить IModelCacheKeyFactory обслуживание с одним, который принимает схему во внимание
+2

Не могли бы вы просить e предоставить некоторые подробности или ссылки на некоторые документы/блоги/учебники? – user3272018

+0

@ user3272018 У меня такая же проблема, нет документации или образцов, как правильно реализовать IModelCacheKeyFactory в EF Core. –

+1

@ tomas-voracek О, в конце концов, я сделал это. Я немного позже предоставлю этот код как автоответ. Я уверен, что это не идеальный способ достичь моей цели, но он работает. Может быть, даже кто-то может улучшить мое решение. Извините, я раньше этого не делал. – user3272018

1

Вы можете использовать атрибут Table в таблицах фиксированной схемы.

Вы не можете использовать атрибут в таблицах смены схемы, и вам нужно настроить это через свободный API ToTable.
Если вы отключите кеш модели (или напишите свой собственный кеш), схема может измениться по каждому запросу, поэтому при создании контекста (каждый раз) вы можете указать схему.

Это базовая идея

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

EDIT
Существует также проблема кэширования модели (я забыл об этом). Вы должны отключить кэширование модели или изменить поведение кэша.

+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

Вы правы !!! Я забыл о кэшировании модели! Также обратите внимание на действия (вы можете найти некоторые статьи вокруг, также в разделе «Переполнение стека») – bubi

2

Я нашел этот блог полезным для вас. Отлично! :)

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

Этот блог основан на EF4, я не уверен, будет ли она нормально работать с ядром эф.

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 по другой схеме и кэшировать их с определенными идентификаторами.