ASP - 在启动时核心迁移 EF Core SQL DB

Posted

技术标签:

【中文标题】ASP - 在启动时核心迁移 EF Core SQL DB【英文标题】:ASP - Core Migrate EF Core SQL DB on Startup 【发布时间】:2016-10-13 07:18:21 【问题描述】:

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

【问题讨论】:

还没有完成,应该下个版本实现。您可以使用 Khan 下面发布的解决方法。 查看下面的答案。您应该使用 EnsureCreated 或 Migrate。两者都不是。 官方文档说:'不要在 Migrate() 之前调用 EnsureCreated()。 EnsureCreated() 绕过迁移来创建架构并导致 Migrate() 失败。 :docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/… 【参考方案1】:

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

请注意,此 API 不使用迁移来创建数据库。在 此外,创建的数据库以后不能使用 迁移。如果您的目标是关系数据库并使用 迁移,您可以使用 DbContext.Database.Migrate() 方法 确保创建数据库并应用所有迁移。

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

评论取自上述声明here的来源。

【讨论】:

【参考方案2】:

你可以使用

db.Database.EnsureCreated();

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

db.Database.Migrate();

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

【讨论】:

EnsureCreated() 工作正常但是“context.Database.Migrate()”没有 Migrate() 方法为什么会这样? EF 核心 @Floxy 我想你错过了using Microsoft.EntityFrameworkCore; @Michael 是的!我忘了说谢谢! 在 ASP.NET Core 应用程序中调用Database.Migrate 的正确位置在哪里? @Shimmy 它应该放在Configure 方法中。看起来像这样context.Database.Migrate();,您可以将DbContext 注入Configure 方法【参考方案3】:

使用下面的代码在

处运行迁移
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();
    

【讨论】:

从 .NET Core 2.1 升级到 3.0(隐式地从进程外托管到进程内托管)后,从启动调用 Migrate 对我来说停止工作。 Web 应用程序不会在没有错误消息的情况下启动。我已将该项目标记为&lt;AspNetCoreHostingModel&gt;OutOfProcess&lt;/AspNetCoreHostingModel&gt;,然后它再次工作。【参考方案4】:

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

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

【讨论】:

如果您只有一个或几个上下文,则此方法有效。但如果你有很多上下文,应该有另一种方式 它也适用于多种上下文。我在具有从单个基本上下文继承的多个上下文的项目中使用它 - jasonwatmore.com/post/2019/10/14/… 我的意思是在Configure()方法中添加多个上下文参数不是很方便。我按照IStartupFilter 方法在应用启动时自动完成迁移。 Configure() 方法中只需要一个上下文参数 - github.com/cornflourblue/aspnet-core-3-registration-login-api/… 它根据环境映射到不同的具体类型 - github.com/cornflourblue/aspnet-core-3-registration-login-api/… 好的,我明白了。你的用例是不同的。您只有 1 个上下文,但有两种不同的形式。我认为这是一种特殊的行为。一般来说,人们希望在应用启动时自动迁移应用内的所有数据上下文。例如,我们目前有 9 个数据上下文连接到 4 个不同的数据库(都是 SQL 服务器)。【参考方案5】:

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

此代码适用于 .NET core 3.0

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

我这样做是为了使用 EF Core 2.1.2 和 SQL Server 以编程方式迁移,基于此处先前的答案和“How and where to call Database.EnsureCreated and Database.Migrate?”上的bailando bailando's answer:

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。

【讨论】:

【参考方案11】:

这是对先前创建扩展方法的答案的轻微修正。它修复了编写方式引发的错​​误。

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<DbContext>();
                context.Database.Migrate();
            
        
    

【讨论】:

【参考方案12】:

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

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

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

【讨论】:

以上是关于ASP - 在启动时核心迁移 EF Core SQL DB的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 EF Core 在 ASP.NET Core 中取消应用迁移

用于 ASP.NET Core 应用程序的 EF Code First 迁移替代方案

如何使用 ASP.NET Core 设置 EF6 迁移

EF-Core:表“名称”已经存在 - 尝试更新数据库时

如何使用 EF Core 代码优先迁移为 ASP.NET Core MVC 配置 N 层架构

EF Core 迁移从其他上下文添加表