如何在 Entity Framework Core 3.0 中播种?

How to seed in Entity Framework Core 3.0?

我正在尝试使用 ASP.NET CORE 3.0 和 EF Core 为数据库添加一些数据。

我已经根据 documentation, online sources, or even EF Core 2.1 questions 创建了我的 DbContext(我找不到关于此主题的任何重大更改)。

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Band>().HasData(
            new Band()
            {
                Id = Guid.Parse("e96bf6d6-3c62-41a9-8ecf-1bd23af931c9"),
                Name = "SomeName",
                CreatedOn = new DateTime(1980, 2, 13),
                Description = "SomeDescription"
            });

        base.OnModelCreating(modelBuilder);           
    }

这没有达到我的预期:启动应用程序时没有播种任何内容(即使在调试期间从某处调用该方法)。

但是,如果我添加一个迁移,迁移包含相应的插入语句(这不是我要找的那种种子)。

问题:在应用程序启动时执行数据库种子的正确方法是什么?

通过种子数据库,我的意思是我希望每次启动应用程序时都能确保某些表中的某些数据。


我可以选择创建播种 class 并在 Database.Migrate 之后使用自定义代码处理它,但这似乎是一种解决方法,因为文档指定应该使用 OnModelCreating 来播种数据)。


因此,根据我在阅读答案和重新阅读文档后的理解,"seed" 的意思是 "initialization" 可以紧邻数据模型(这就是为什么感觉很奇怪——将模型创建与数据播种部分混合在一起。

如果您想在应用程序启动时播种,在您的应用程序启动方法中,您可以使用条件检查来检查您想要的数据,如果没有 returns,请将那些 类 添加到上下文并保存更改。

EF Core 中的播种是为迁移而设计的,其初始化数据用于数据库,而不是用于应用程序运行时。如果您希望一组数据不变,那么考虑使用替代方法?就像通过带有字段检查的属性将其保存在内存缓存中的 xml/json 格式一样。

您可以在应用程序启动时使用 deletion/creation 语法,但由于状态缺乏持久性,它通常不受欢迎。

不幸的是,对于您想要的,它必须是一种解决方法,因为它不在 EF 功能的预期范围内。

我认为 OnModelCreating() 不是为您的数据库设置种子的最佳位置。我认为这完全取决于您希望播种逻辑何时达到 运行。您说您希望在应用程序启动时将播种 运行 无论您的数据库是否有迁移更改。

我会创建一个扩展方法来连接 Startup.cs class 中的 Configure() 方法:

Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.MigrateAndSeedDb(development: true);
            }
            else
            {
                 app.MigrateAndSeedDb(development: false);
            }           

            app.UseHttpsRedirection();
 ...

MigrateAndSeedDb.cs

 public static void MigrateAndSeedDb(this IApplicationBuilder app, bool development = false)
        {
            using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
            using (var context = serviceScope.ServiceProvider.GetService<GatewayDbContext>())
            {
                //your development/live logic here eg:
                context.Migrate();
                if(development)
                    context.Seed();
            }                
        }

        private static void Migrate(this GatewayDbContext context)
        {
            context.Database.EnsureCreated();
            if (context.Database.GetPendingMigrations().Any())
                context.Database.Migrate();
        }

        private static void Seed(this GatewayDbContext context)
        {
            context.AddOrUpdateSeedData();
            context.SaveChanges();
        }

AddOrUpdateSeedData.cs

internal static GatewayDbContext AddOrUpdateSeedData(this GatewayDbContext dbContext)
        {
            var defaultBand = dbContext.Bands
                .FirstOrDefault(c => c.Id == Guid.Parse("e96bf6d6-3c62-41a9-8ecf-1bd23af931c9"));

            if (defaultBand == null)
            {
                defaultBand = new Band { ... };
                dbContext.Add(defaultBand);
            }
            return dbContext;
        }

如果你有复杂的种子数据,默认的 EF 核心功能不是一个好主意。例如,根据您的配置或系统环境,您无法添加种子数据。

我正在使用自定义服务和依赖项注入来添加我的种子数据,并在应用程序启动时为上下文应用任何挂起的迁移。我将分享我的定制服务,希望对您有所帮助:

IDbInitializer.cs

    public interface IDbInitializer
    {
        /// <summary>
        /// Applies any pending migrations for the context to the database.
        /// Will create the database if it does not already exist.
        /// </summary>
        void Initialize();

        /// <summary>
        /// Adds some default values to the Db
        /// </summary>
        void SeedData();
    }

DbInitializer.cs

    public class DbInitializer : IDbInitializer {
        private readonly IServiceScopeFactory _scopeFactory;

        public DbInitializer (IServiceScopeFactory scopeFactory) {
            this._scopeFactory = scopeFactory;
        }

        public void Initialize () {
            using (var serviceScope = _scopeFactory.CreateScope ()) {
                using (var context = serviceScope.ServiceProvider.GetService<AppDbContext> ()) {
                    context.Database.Migrate ();
                }
            }
        }

        public void SeedData () {
            using (var serviceScope = _scopeFactory.CreateScope ()) {
                using (var context = serviceScope.ServiceProvider.GetService<AppDbContext> ()) {
                   
                    //add admin user
                    if (!context.Users.Any ()) {
                        var adminUser = new User {
                            IsActive = true,
                            Username = "admin",
                            Password = "admin1234", // should be hash
                            SerialNumber = Guid.NewGuid ().ToString ()
                        };
                        context.Users.Add (adminUser);
                    }

                    context.SaveChanges ();
                }
            }
        }
    }

要使用此服务,您可以将其添加到您的服务集合中:

 // StartUp.cs -- ConfigureServices method
 services.AddScoped<IDbInitializer, DbInitializer> ()

因为我想在我的程序每次启动时都使用此服务,所以我以这种方式使用注入服务:

 // StartUp.cs -- Configure method
         var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory> ();
         using (var scope = scopeFactory.CreateScope ()) {
            var dbInitializer = scope.ServiceProvider.GetService<IDbInitializer> ();
            dbInitializer.Initialize ();
            dbInitializer.SeedData ();
         }
         

您可以为其使用迁移。只需创建一个新迁移(无需事先更改模型 classes)。生成的迁移 class 将具有空的 Up() 和 Down() 方法。 在那里你可以做你的播种。喜欢:

protected override void Up(MigrationBuilder migrationBuilder)
{
  migrationBuilder.Sql("your sql statement here...");
}

就是这样。

就这样

 public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            try
            {
                SeedDatabase.Initialize(services);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occured seeding the DB");
            }
        }
        host.Run();
    }

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

种子数据库class:

  public static class SeedDatabase
{
    public static void Initialize(IServiceProvider serviceProvider)
    {
        using (var context = new HospitalManagementDbContext(serviceProvider.GetRequiredService<DbContextOptions<HospitalManagementDbContext>>()))
        {
            if (context.InvestigationTags.Any())
            {
                return;
            }

            context.InvestigationTags.AddRange(
                new Models.InvestigationTag
                {
                    Abbreviation = "A1A",
                    Name = "Alpha-1 Antitrypsin"
                },

                new Models.InvestigationTag
                {
                    Abbreviation = "A1c",
                    Name = "Hemoglobin A1c"
                },


                new Models.InvestigationTag
                {
                    Abbreviation = "Alk Phos",
                    Name = "Alkaline Phosphatase"
                }
                );
            context.SaveChanges();
        }
    }
}

如果您想在首次启动时使用现有数据库(例如,您在开发机器上的数据库)中的数据为您的数据库播种,open-source library 可以帮助您完成此操作最小的努力。

因此,您 Startup.cs 中的数据库 initialization/seeding 代码(我猜这是一个 ASP.NET 核心项目)将如下所示:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    .     .     .     .

    app.UseMvc();

    using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    using (var context = scope.ServiceProvider.GetService<AppDbContext>()) {
        if (context.Database.EnsureCreated()) { //run only if database was not created previously
            Korzh.DbUtils.DbInitializer.Create(options => {
                options.UseSqlServer(Configuration.GetConnectionString("MyDemoDb")); //set the connection string for our database
                options.UseFileFolderPacker(System.IO.Path.Combine(env.ContentRootPath, "App_Data", "SeedData")); //set the folder where to get the seeding data
            })
            .Seed();
        }
    }
}

整个过程在this article中有详细描述。

免责声明:我是该开源库的作者。