How to seed in Entity Framework Core 3.0?
Asked Answered
F

7

13

I am trying seed the database with some data, using ASP.NET CORE 3.0 and EF Core.

I've created my DbContext and according to documentation, online sources, or even EF Core 2.1 questions (I could not find any breaking changes on this topic).

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

This does not do what I expect: nothing is seeded on starting the application (even if during debug the method is called from somewhere).

However, if I add a migration, the migration contains the corresponding insert statement (which is not the kind of seeding I am looking for).

Question: What is the right way to have the seed of the database being performed on application start?

By seed the database, I mean that I expect some data to be ensured in some tables everytime the application is started.


I have the alternative to create a seeding class and handle it after the Database.Migrate with custom code, but this seems like a workaround, because the documentation specifies that OnModelCreating should be used to seed data).


So to my understanding after reading the answers and re-reading the documentation, what they mean by "seed" is an "initialization" which can take place right next to the data model (which is why it felt strange - mixing the model creation with the data seeding part).

Faustena answered 7/2, 2020 at 15:36 Comment(0)
E
5

If you want to seed on application start, in your applications starting method you can do a check for the data you want using conditional checks and if no returns, add those classes to the context and save the changes.

The seeding in EF Core is designed for migration, its initialised data for the database, not for an applications runtime. If you want a set of data to be unchanged then consider utilising an alternative method? Like keeping it in xml/json format with in memory caching via properties with field checks.

You could use the deletion/creation syntax on application start but its generally frowned upon as state lacks permanence.

Unfortunately for what you want, it has to be a work around as its not in the expected line of EF's functioning.

Excavate answered 7/2, 2020 at 15:46 Comment(2)
Also wanted to show you this issue as to why its not necessarily a good idea for migrations at application start up. github.com/dotnet/efcore/issues/19587 Its better to overall control your migrations and you can add additional migration code on migration creation, then tightly control when and what databases gets modified. (since you might use inmemory for source control branches or even local databases instead). Overall as above, if you have application start changes to your database best do them with the application itself, but if you have to find an alternative.Excavate
I want to seed a few entities (user roles and such). I get what you are saying, that a custom seed should be done on my part, as EF aims to solve other types of "seed" through OnModelCreating().Faustena
F
26

if you have complex seed data default EF core feature is not a good idea to use. for example, you can't add your seed data depending on your configurations or system environment.

I'm using a custom service and dependency injection to add my seed data and apply any pending migrations for the context when the application starts. ill share my custom service hope it helps :

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

for using this service you can add it to your service collection :

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

because i want to use this service every time my program starts i'm using injected service this way :

 // 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 ();
         }
         
Forepeak answered 7/2, 2020 at 16:8 Comment(4)
This is similar to what I have done as "workaround", except that I directly call dbInitialized.SeedData(scope.ServiceProvider).Faustena
I think for now this is the best way to handle ef-core seed data in asp.net core applications. because this way you can use your configuration and change your seeding depending on it.Forepeak
How would I use this kind of startup for Testing with MSTest?Expedient
This is not recommended because it can cause concurrency issues when running multiple instancesCoimbatore
A
11

I don't think OnModelCreating() is the best place to seed your database. I think it depends entirely when you want your seeding logic to run. You said you would like your seeding to run on application start regardless of whether your database has migration changes.

I would create an extension method to hook into the Configure() method in the Startup.cs class:

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;
        }
Aloe answered 7/2, 2020 at 15:50 Comment(3)
So in the end the way to go is with custom code... I appreciate the code for AddIfNotExists/Update logic.Faustena
Microsoft documentation says "Don't call EnsureCreated() before Migrate(). EnsureCreated() bypasses Migrations to create the schema, which causes Migrate() to fail." Have you noticed that?Sha
if(development) why do you want your seed data only when running in the IDE ?Bulkhead
O
7

Just like that

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

SeedDatabase 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();
        }
    }
}
Oisin answered 18/11, 2020 at 15:14 Comment(4)
This will seed all the time when you restart your appDenial
No. It will check first then it will take action according to the database statusOisin
@AliKleit No, it won't. They are using a guard condition to check if there is any record already in the database, which would mean they're not dealing with a clean database, and will therefore abort.Lurie
@Lurie my bad! deleted my commentWadlinger
E
5

If you want to seed on application start, in your applications starting method you can do a check for the data you want using conditional checks and if no returns, add those classes to the context and save the changes.

The seeding in EF Core is designed for migration, its initialised data for the database, not for an applications runtime. If you want a set of data to be unchanged then consider utilising an alternative method? Like keeping it in xml/json format with in memory caching via properties with field checks.

You could use the deletion/creation syntax on application start but its generally frowned upon as state lacks permanence.

Unfortunately for what you want, it has to be a work around as its not in the expected line of EF's functioning.

Excavate answered 7/2, 2020 at 15:46 Comment(2)
Also wanted to show you this issue as to why its not necessarily a good idea for migrations at application start up. github.com/dotnet/efcore/issues/19587 Its better to overall control your migrations and you can add additional migration code on migration creation, then tightly control when and what databases gets modified. (since you might use inmemory for source control branches or even local databases instead). Overall as above, if you have application start changes to your database best do them with the application itself, but if you have to find an alternative.Excavate
I want to seed a few entities (user roles and such). I get what you are saying, that a custom seed should be done on my part, as EF aims to solve other types of "seed" through OnModelCreating().Faustena
M
5

You can use Migrations for it. Just create a new migration (without prior changes to the model classes). The generated migration class would have empty Up() and Down() methods. There you can do your seeding. Like:

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

and that's it.

Myna answered 13/2, 2020 at 20:26 Comment(0)
E
1

For anyone needing to seed data in .NET 6 with EF Core for test purposes (since this page seems to be the top search engine hit for this sort of thing):

Program.cs:

var app = builder.Build();

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

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

There are potentially simpler ways of doing it, but the above has the advantage of being fully customizable to suit your needs. Note that this will run every time your app runs and there are issues with concurrency, so this is really only suitable for development (but in production you'd probably run a standalone seed script anyway). You could add a condition like if (app.Environment.IsDevelopment()) for safety. Or you could add this code to a separate app that just does the seeding.

More info here.

Esculent answered 15/6, 2022 at 0:7 Comment(0)
P
0

If you want to seed your DB at the first launch with the data from an existing DB (that you have on your dev machine, for example), there is an open-source library that helps you do it with minimal effort.

As the result, the DB initialization/seeding code in your Startup.cs (I guess here it's an ASP.NET Core project) will look like this:

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

The whole process is described in detail in this article.

Disclaimer: I'm the author of that open-source library.

Polivy answered 17/12, 2021 at 5:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.