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

91

我可以在ASP Core Web API中使用EF Core确保数据库已迁移到最新的迁移吗?我知道可以通过命令行完成此操作,但我想以编程方式实现。


仍未完成,应在下一个版本中实现。您可以使用Khan下面发布的解决方法。 - Bassam Alugili
1
请看下面的答案。您应该只使用EnsureCreated或Migrate中的一个,而不是两者都使用。 - Matt Varblow
14
官方文档中提到:“在调用Migrate()之前不要调用EnsureCreated()。调用EnsureCreated()将绕过迁移以创建架构,导致Migrate()失败。” 参考链接:https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/#applying-migrations-at-runtime - Oswin
13个回答

58

db.Database.EnsureCreated() 的文档中有一条注释:

请注意,此 API 不使用迁移来创建数据库。此外,使用此方法创建的数据库无法在以后使用迁移进行更新。如果你要使用迁移针对关系型数据库进行操作,可以使用 DbContext.Database.Migrate() 方法来确保已创建数据库并应用所有迁移。

你可能只需要调用 db.Database.Migrate()

上面的注释摘自声明的源代码,点击此处可以找到源代码。


52

您可以使用

db.Database.EnsureCreated();

如果您希望将数据库更新到当前模型,请使用以下命令。如果您想启用迁移(如果存在后续迁移),则使用

要使数据库与当前模型同步,请运行以上命令。如果需要启用迁移(若有后续迁移),请使用:

db.Database.Migrate();

并在随后的迁移中逐步实现。


2
EnsureCreated() 运行正常,但是为什么 "context.Database.Migrate()" 没有 Migrate() 方法?EF Core。 - Floxy
11
我认为你漏掉了 using Microsoft.EntityFrameworkCore; 的引用。 - Michael
@Michael 是的!!我忘了提到,不过还是谢谢你!! <3 - Floxy
2
在 ASP.NET Core 应用程序中,调用 Database.Migrate 的正确位置是什么? - Shimmy Weitzhandler
@Shimmy 它应该放置在 Configure 方法中。并且看起来像这样context.Database.Migrate(); 你需要将 DbContext 注入到 Configure 方法中。 - Newteq Developer

30

使用以下代码来运行迁移:

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

3
从.NET Core 2.1升级到3.0(并隐式地从外部进程托管转换为内部进程托管)后,我发现从启动时调用的Migrate不再起作用。网络应用程序无法正常启动且没有错误消息。我将项目标记为<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>,然后它又能正常工作了。 - Dejan

19

根据@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>();

18

在ASP.NET Core 3.1中,我使用将db上下文作为参数注入到现有的Configure方法中(在ConfigureServices方法中注册后)解决了此问题。

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找到。


如果你只有一个或几个上下文,这个方法是可行的。但是如果你有很多上下文,应该有另一种方法。 - Sven
它也可以与多个上下文一起使用。我在一个项目中使用它,该项目具有从单个基本上下文继承的多个上下文 - https://jasonwatmore.com/post/2019/10/14/aspnet-core-3-simple-api-for-authentication-registration-and-user-management - Jason Watmore
我的意思是,在Configure()方法中添加多个上下文参数并不是很方便。我遵循了IStartupFilter的方法,在应用程序启动时自动完成迁移。 - Sven
Configure()方法中只需要一个上下文参数 - https://github.com/cornflourblue/aspnet-core-3-registration-login-api/blob/master/Startup.cs#L90它根据环境映射到不同的具体类型 - https://github.com/cornflourblue/aspnet-core-3-registration-login-api/blob/master/Startup.cs#L33-L36 - Jason Watmore
好的,我明白了。你的使用情况不同。你只有一个上下文但是有两种不同的形式。我认为这是一种特殊的行为。一般来说,人们希望在应用程序启动时自动迁移应用程序中的所有数据上下文。例如,我们目前有9个数据上下文连接到4个不同的数据库(全部为SQL Server)。 - Sven
啊,是的,那是一个不同的用例。 - Jason Watmore

6

我采用了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);
        };
    }
}

现在我们可以按照以下方式注册DataContexts和迁移:

第一个上下文

 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() 方法。


.SetCommandTimeout(160); 这是暂时的还是会持久存在? - sommmen
@sommen 在阅读文档(https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.setcommandtimeout?view=efcore-6.0)时,此设置在DbContext中是持久的。 - Sven
我也这么想 - 如果是这样的话,或许默认设置在这里并不好... - sommmen
@sommmen 可根据您的需求自由修改代码示例。 - Sven

5

使用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>();
      });
}

5
在Asp core 6中,没有StartUp。在以前的版本中,我们有Configure方法,允许我们直接访问ServiceProvider,然后可以使用GetServices获取DBcontext,然后调用Migrate方法。
但是现在在Asp core 6中,我们应该创建一个作用域,然后获取DBcontext对象。
        using (var Scope = app.services.CreateScope())
        {
            var context = Scope.Services.GetRequireService<DBContext>();
            context.Database.Migrate();
        }

1
使用 (var Scope = app.Services.CreateScope()) { var context = Scope.ServiceProvider.GetRequiredService<KyloDbContext>(); context.Database.Migrate(); } - pyRabbit

5
这段代码适用于.NET Core 3.0版本。
 using (var scope = app.ApplicationServices.CreateScope())
 {
     var dbContext = scope.ServiceProvider.GetService<T>();
     dbContext.Database.Migrate();
 }

4

根据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();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接