ASP - Core 在启动时迁移 EF Core SQL 数据库

ASP - Core Migrate EF Core SQL DB on Startup

是否可以让我的 ASP Core Web API 确保将数据库迁移到使用 EF Core 的最新迁移?我知道这可以通过命令行完成,但我想以编程方式完成。

您可以使用

db.Database.EnsureCreated();

让您的数据库与您当前的模型保持同步。如果要启用迁移(如果怀疑后续迁移),则使用

db.Database.Migrate();

并随着时间的推移进行后续迁移。

文档中关于调用 db.Database.EnsureCreated() 的注释:

Note that this API does not use migrations to create the database. In addition, the database that is created cannot be later updated using migrations. If you are targeting a relational database and using migrations, you can use the DbContext.Database.Migrate() method to ensure the database is created and all migrations are applied.

您可能只想致电 db.Database.Migrate()

从声明上方找到的来源中获取的评论 here

根据@steamrolla 的回答,我提出以下改进建议:

public static class EnsureMigration
{
    public static void EnsureMigrationOfContext<T>(this IApplicationBuilder app) where T:DbContext
    {
        var context = app.ApplicationServices.GetService<T>();
        context.Database.Migrate();
    }
}

有了这个你也可以保证不同上下文的迁移,例如如果您有身份数据库。

用法:

app.EnsureMigrationOfContext<context>();

使用下面的代码 运行 在

迁移
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var context = serviceScope.ServiceProvider.GetService<YourContext`enter code here`>();
        context.Database.Migrate();
    }
}

根据chintan310的回答,我是这样迁移数据库的。这确保将与数据库相关的任务分离到 Program.cs:

    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                var context = services.GetService<AppDbContext>();
                context.Database.Migrate();

                var seeder = scope.ServiceProvider.GetService<AppSeeder>();
                seeder.Seed().Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred seeding the DB.");
            }
        }

        host.Run();
    }

    private static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();

我这样做是为了通过 EF Core 2.1.2 和 SQL 服务器以编程方式迁移,基于此处的先前答案和 bailando bailando's answer on "How and where to call Database.EnsureCreated and Database.Migrate?":

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

namespace MyApp
{
    public class Startup
    {
        // ... (only relevant code included) ...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyAppContext>(options => 
                options.UseSqlServer(Configuration.GetConnectionString("MyAppContext")));
            // ...
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            using (var serviceScope = app.ApplicationServices.CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<MyAppContext>();
                context.Database.Migrate();
            }
            // ...
        }
    }
}

使用此代码的项目是available at Github

这是对先前创建扩展方法的答案的轻微修正。它修复了按照编写方式抛出的错误。

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace MyApp.Extensions
{
    public static class IApplicationBuilderExtensions
    {
        public static void SyncMigrations<T>(this IApplicationBuilder app) where T : DbContext
        {
            using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<T>();
                context.Database.Migrate();
            }
        }
    }
}

此代码适用于 .NET core 3.0

 using (var scope = app.ApplicationServices.CreateScope())
 {
     var dbContext = scope.ServiceProvider.GetService<T>();
     dbContext.Database.Migrate();
 }

这在 ASP.NET Core 3.1 中对我有用,在 ConfigureServices 方法中注册后,只需将数据库上下文作为参数注入现有 Configure 方法。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<DataContext>(x => x.UseSqlite("Data Source=LocalDatabase.db"));

    ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
    dataContext.Database.Migrate();

    ...
}

更多详细信息和完整代码示例的链接可在 https://jasonwatmore.com/post/2019/12/27/aspnet-core-automatic-ef-core-migrations-to-sql-database-on-startup

我遵循了 IStartupFilter 方法来获得迁移任何上下文的通用方法。

 public class DataContextAutomaticMigrationStartupFilter<T> : IStartupFilter
  where T : DbContext
{
    /// <inheritdoc />
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            using (var scope = app.ApplicationServices.CreateScope())
            {
                scope.ServiceProvider.GetRequiredService<T>().Database.SetCommandTimeout(160);
                scope.ServiceProvider.GetRequiredService<T>().Database.Migrate();
            }
            next(app);
        };
    }
}

现在我们可以通过以下方式注册 DataContext 和迁移:

第一个上下文

 services.AddDbContext<ConsumerDataContext>(options => options.UseSqlServer(configuration.GetConnectionString("ConsumerConnection")), ServiceLifetime.Transient);
    services.AddTransient<IStartupFilter, DataContextAutomaticMigrationStartupFilter<ConsumerDataContext>>();

第二个上下文

services.AddDbContext<UserDataContext>(options => options.UseSqlServer(configuration.GetConnectionString("UserConnection")), ServiceLifetime.Transient);
services.AddTransient<IStartupFilter, DataContextAutomaticMigrationStartupFilter<UserDataContext>>();

..等等..

IStartupFilter的罪魁祸首是它只允许同步执行代码。对于数据库迁移,这不是问题,因为我们有同步 Migrate() 方法。

使用 C# 7.1 启动 .NET Core 2,您可以对您的应用程序使用异步 Main 方法,这样您就可以在 运行 主机之前调用所有初始化逻辑,就在它具有完成建筑:

public class Program
{
  public static async Task Main(string[] args)
  {
    //first build
    var host = CreateHostBuilder(args).Build();

    //initialize
    using (var serviceScope = host.Services.CreateScope())
    {
      var serviceProvider = serviceScope.ServiceProvider;
      var isDevelopment = 
        serviceProvider.GetRequiredService<IWebHostEnvironment>().IsDevelopment();

      using var context = serviceProvider.GetRequiredService<AppDbContext>();


      if (isDevelopment)
        await context.Database.EnsureCreatedAsync();
      else
        await context.Database.MigrateAsync();

      if (isDevelopment)
      {
        using var userManager = 
          serviceProvider.GetRequiredService<UserManager<AppUser>>();
        await userManager
          .CreateAsync(new AppUser { UserName = "dummy", Email = "dummy@dumail.com" },
          password: "1234");
      }
    }

    //now run
    host.Run();
  }

  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
      .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup<Startup>();
      });
}

在 Asp core 6 中你没有 StartUp ,在以前的 asp 版本中我们有 Configure 方法允许直接访问 ServiceProvider 然后我们可以使用 GetServices 获取 DBcontext 然后调用迁移方法。

但现在 Asp 核心 6。我们应该创建一个范围然后获取 DBcontext 对象

        using (var Scope = app.services.CreateScope())
        {
            var context = Scope.Services.GetRequireService<DBContext>();
            context.Database.Migrate();
        }