Мультитенантное приложение с ASP. NET Core 3.0 и EF Core - PullRequest
1 голос
/ 31 января 2020

Я разрабатываю мультитенантное приложение, в котором будет отдельная база данных для каждой школы / арендатора. Каждая схема будет идентична друг другу. Идея состоит в том, чтобы использовать один контекст базы данных, используя. NET Core 3 и EF Core.

Например, клиент переходит на school1.gov.uk, затем создается экземпляр SchoolContext с использованием строк подключения, хранящихся в настройках приложения. json в разделе «school1».

В настоящее время я необходимость запуска add-миграции для каждого нового добавленного контекста. Может кто-нибудь придумать решение для запуска миграций один раз, в одном контексте, т.е. SchoolContext?

Основной контекст

public class SchoolContext : DbContext
{     protected readonly IConfiguration _configuration;
      private readonly IHttpContextAccessor _httpContextAccessor;

    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }

    public SchoolContext(IConfiguration configuration, IHttpContextAccessor httpContextAccessor) 
    {
        _configuration = configuration;
        _httpContextAccessor = httpContextAccessor;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
            var subdomain = _httpContextAccessor.HttpContext.Request.Host.Host;
            var connectionString = _configuration.GetConnectionString(subdomain);
            optionsBuilder.UseSqlServer(connectionString);
    }

} 

Контекст арендатора 1

public class School1Context : SchoolContext
{
    public Schoo1Context()
    {

    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
         var connectionString = _configuration.GetConnectionString("School1");
         optionsBuilder.UseSqlServer(connectionString);
    }

} 

Контекст арендатора 2

public class School2Context : SchoolContext
{
    public School2Context()
    {

    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
         var connectionString = _configuration.GetConnectionString("School2");
         optionsBuilder.UseSqlServer(connectionString);
    }

} 

Program.cs

         using (var scope = host.Services.CreateScope())
         {
             var school1Context = scope.ServiceProvider.GetService<School1Context>();
             school1Context.Database.Migrate();
             var school2Context = scope.ServiceProvider.GetService<School2Context>();
             school2Context.Database.Migrate();
         }

Appsettings. json

"ConnectionStrings": {
    "School1": "Persist Security Info=true;Data Source=.\\SQLEXPRESS;Initial Catalog=School1;User ID=;Password=;",
    "School2": "Persist Security Info=true;Data Source=.\\SQLEXPRESS;Initial Catalog=School2;User ID=;Password=;",

   }

Ответы [ 2 ]

2 голосов
/ 31 января 2020

Самая большая проблема, которую я вижу с вашим кодом, заключается в том, что у вас должен быть 1 контекст (потому что они все одинаковые схемы, верно?). Если бы у них были разные схемы, вам потребовались бы разные контексты.

Этот единственный контекст должен создаваться с помощью указанной c строки подключения школы .

Таким образом, вы add-миграция против 1 контекста (не нескольких).

Вы в основном там, просто удалите School1Context и School2Context, потому что здесь:

SchoolContext.cs

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
   var subdomain = _httpContextAccessor.HttpContext.Request.Host.Host;
   var connectionString = _configuration.GetConnectionString(subdomain); // <--- this is tenanted already
   optionsBuilder.UseSqlServer(connectionString);
}

Вы уже захватили строку подключения и подключились к другой базе данных.

Вы сформулировали ответ в своем первоначальном вопросе:

Идея состоит в том, чтобы использовать один контекст базы данных, используя. NET Core 3 и EF Core.

Небольшое подключение к платформе, которую я люблю и ежедневно использую: ServiceStack имеет встроенную многопользовательскую работу - ServiceStack Multitenancy

0 голосов
/ 16 марта 2020

Я столкнулся с почти такой же проблемой с твоей. У меня есть один TenantDbContext.

public class TenantDbContext : DbContext
{
    private readonly Tenant _tenant;

    public DbSet<Branch> Branches { get; set; }

    public TenantDbContext(DbContextOptions<TenantDbContext> options, IHttpContextAccessor httpContextAccessor)
        : base(options)
    {
        if (httpContextAccessor.HttpContext != null)
        {
            _tenant = (Tenant)httpContextAccessor.HttpContext.Items["TENANT"];
        }
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (_tenant != null)
        {
            optionsBuilder.UseSqlServer(_tenant.ConnectionString);
        }

        base.OnConfiguring(optionsBuilder);
    }
}

Затем я обнаружил, что «Add-Migration» трудно с помощью консоли диспетчера пакетов, потому что строка подключения является динамической c, поэтому я создал заполнитель («Dev ") для строки подключения, в Startup.ConfigureServices ()

var devConnectionString = _config.GetConnectionString("Dev");
services.AddDbContext<TenantDbContext>(opt => opt.UseSqlServer(devConnectionString));

Вы знаете, есть два способа настройки DbContext, как показано в двух фрагментах кода выше соответственно. Если оба они указаны, сначала будет вызван AddDbContext (), а затем дополнительно будет вызван OnConfiguring ().

Когда вы запускаете команду «Add-Migration», миграции будут генерироваться в DbContext с помощью строка подключения заполнителя, хотя арендатор не указан. Когда вы запускаете свое приложение, вам просто нужно добавить промежуточное программное обеспечение и вызвать Migrate (), чтобы применить эти миграции к вашим базам данных-арендаторам, прежде чем вы go подключитесь к промежуточному программному обеспечению MVC.

public class TenantIdentifier
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TenantIdentifier(RequestDelegate next, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
    {
        _next = next;
        _configuration = configuration;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task Invoke(HttpContext httpContext, HostDbContext hostDbContext)
    {
        // Get tenant id from token
        var tenantId = httpContext.User.FindFirst(Constants.TENANT)?.Value;

        // Set tenant id to httpContext.Items
        if (!string.IsNullOrWhiteSpace(tenantId))
        {
            var tenant = hostDbContext.Tenants.SingleOrDefault(t => t.Id.ToString() == tenantId);
            httpContext.Items["TENANT"] = tenant;

            using(var tenantDbContext = new TenantDbContext(new DbContextOptions<TenantDbContext>(), _httpContextAccessor))
            {
                tenantDbContext.Database.Migrate();
            }
        }

        await _next.Invoke(httpContext);
    }
}

Затем, вам просто нужно запустить «Add-Migration» один раз, вообще не создавая БД-заполнитель.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...