'How to seed in Entity Framework Core 3.0?
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).
Solution 1:[1]
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.
Solution 2:[2]
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 ();
}
Solution 3:[3]
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;
}
Solution 4:[4]
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();
}
}
}
Solution 5:[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.
Solution 6:[6]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Baron |
Solution 2 | |
Solution 3 | |
Solution 4 | Shakir Ahmed |
Solution 5 | Yoss |
Solution 6 | Sergiy |