簡體   English   中英

如何在 Entity Framework Core 3.0 中播種?

[英]How to seed in Entity Framework Core 3.0?

我正在嘗試使用 ASP.NET CORE 3.0 和 EF Core 為數據庫播種一些數據。

我已經創建了我的 DbContext 並根據文檔在線資源甚至EF Core 2.1 問題(我找不到關於此主題的任何重大更改)。

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 來播種數據)。


因此,根據我在閱讀答案和重新閱讀文檔后的理解,“種子”的意思是“初始化”,它可以在數據 model 旁邊發生(這就是為什么感覺很奇怪 - 將 model 的創建與數據播種部分)。

如果您有復雜的種子數據,默認 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();
    }

數據庫初始化程序

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

我不認為OnModelCreating()是為您的數據庫設置種子的最佳位置。 我認為這完全取決於您希望播種邏輯何時運行。 您說過無論您的數據庫是否有遷移更改,您都希望在應用程序啟動時運行種子。

我將創建一個擴展方法來連接到 Startup.cs 類中的Configure()方法:

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

添加或更新種子數據.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 Core 中的播種是為遷移而設計的,它為數據庫初始化數據,而不是為應用程序運行時。 如果您希望一組數據保持不變,那么請考慮使用替代方法? 就像通過帶有字段檢查的屬性將其保存在 xml/json 格式並在內存中緩存一樣。

您可以在應用程序啟動時使用刪除/創建語法,但由於狀態缺乏永久性,它通常不受歡迎。

不幸的是,對於您想要的東西,它必須是一種解決方法,因為它不在 EF 的預期功能范圍內。

您可以為此使用遷移。 只需創建一個新的遷移(無需事先更改模型類)。 生成的遷移類將具有空的 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;
    }
   
       
}

種子數據庫類:

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

對於需要使用 EF Core 在 .NET 6 中播種數據以進行測試的任何人(因為此頁面似乎是這類事情的熱門搜索引擎):

程序.cs:

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    MyDbContext.SeedData(serviceScope.ServiceProvider);
}

數據庫上下文 class:

public static void SeedData(IServiceProvider serviceProvider)
{
    var appDB = serviceProvider.GetRequiredService<MyDbContext>()
    
    if (!appDB.MyEntities.Any())
    {
        // Add seed data here.  For example:
        MyEntity myEntity = new();
        appDB.MyEntities.Add(myEntity);
                
        appDB.SaveChanges();
    }
}

可能有更簡單的方法可以做到這一點,但上述方法的優點是可以完全定制以滿足您的需求。 請注意,這將在您的應用程序每次運行時運行,並且存在並發問題,因此這實際上僅適用於開發(但在生產中您可能無論如何都運行獨立的種子腳本)。 為了安全起見,您可以添加類似if (app.Environment.IsDevelopment())的條件。 或者,您可以將此代碼添加到僅執行播種的單獨應用程序。

更多信息在這里

如果您想在第一次啟動時使用現有數據庫(例如,您在開發機器上擁有的)中的數據為您的數據庫做種,有一個開源庫可以幫助您以最少的努力做到這一點。

結果,您的 Startup.cs 中的數據庫初始化/播種代碼(我猜這是一個 ASP.NET Core 項目)將如下所示:

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

本文詳細介紹了整個過程。

免責聲明:我是該開源庫的作者。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM