Как посеять в Entity Framework Core 3.0? - PullRequest
2 голосов
/ 07 февраля 2020

Я пытаюсь заполнить базу данных некоторыми данными, используя ASP. NET CORE 3.0 и EF Core.

Я создал свой DbContext и в соответствии с документацией , онлайн-источники или даже EF Core 2.1 вопросы (я не смог найти каких-либо серьезных изменений в этой топи c).

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

Это не делает то, что я ожидаю: при запуске приложения ничего не отображается (даже если во время отладки метод вызывается откуда-то).

Однако, если я добавлю миграцию , миграция содержит соответствующий оператор вставки (это не тот тип заполнения, который я ищу).

Вопрос: Как правильно выполнить начальное выполнение базы данных? при запуске приложения?

Под заполнением базы данных я подразумеваю, что я ожидаю, что некоторые данные будут обеспечиваться в некоторых таблицах при каждом запуске приложения.


У меня есть альтернатива для создания заполнить класс и обработать его после Database.Migrate с пользовательским кодом, но это выглядит как обходной путь, потому что в документации указано, что OnModelCreating должен использоваться для заполнения данных).


Итак, насколько я понимаю после прочтения ответы и перечитывание документации, что они подразумевают под «семенем», является «инициализацией», которая может произойти сразу после к модели данных (вот почему это было странно - смешивать создание модели с частью заполнения данных).

Ответы [ 4 ]

2 голосов
/ 07 февраля 2020

если у вас есть сложная начальная функция 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 ();
         }

2 голосов
/ 07 февраля 2020

Если вы хотите заполнить при запуске приложения, в вашем методе запуска приложения вы можете выполнить проверку данных, которые вы хотите, используя условные проверки, и, если нет возврата, добавить эти классы в контекст и сохранить изменения.

Заполнение в EF Core предназначено для миграции, его инициализированных данных для базы данных, а не для времени выполнения приложений. Если вы хотите, чтобы набор данных не изменился, подумайте об использовании альтернативного метода? Как и в формате xml / json с кэшированием в памяти через свойства с проверками полей.

Вы можете использовать синтаксис удаления / создания при запуске приложения, но он обычно вызывает недовольство, поскольку состояние не является постоянным.

К сожалению, для того, что вы хотите, это должен быть обходной путь, поскольку он не соответствует ожидаемой линии функционирования EF.

1 голос
/ 13 февраля 2020

Вы можете использовать Миграции для него. Просто создайте новую миграцию (без предварительных изменений в модельных классах). Сгенерированный класс миграции будет иметь пустые методы Up () и Down (). Там вы можете сделать свой посев. Как:

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

и все.

1 голос
/ 07 февраля 2020

Я не думаю, что OnModelCreating() - лучшее место для заполнения вашей базы данных. Я думаю, что это полностью зависит от того, когда вы хотите, чтобы ваш посев логики c работал. Вы сказали, что хотите, чтобы начальное заполнение запускалось при запуске приложения, независимо от того, есть ли в вашей базе данных изменения миграции.

Я бы создал метод расширения для подключения к методу Configure() в классе Startup.cs:

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;
        }
...